ThreeJs:裁剪( Path编)挖洞和贴图

本文详细介绍了使用Three.js制作墙体模型的过程,包括如何创建带洞的形状、应用纹理并调整UV坐标,以及如何通过BufferGeometry实现对墙体大小的动态调整。通过实例演示了如何将纹理精确地映射到模型的每个面,展示了BufferGeometry在复杂模型制作中的强大功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本内容是裁剪( Path编)的延申

裁剪( Path编) : https://blog.youkuaiyun.com/jinold/article/details/91345585

<!DOCTYPE html>
<html lang="en">
	<head>
		<title>three.js webgl - geometry - shapes</title>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<style>
			body {
				font-family: Monospace;
				background-color: #f0f0f0;
				margin: 0px;
				overflow: hidden;
			}
		</style>
	</head>
	<body>
		<dir>
			<input type="text" id = "qiangSize">
			<input type="button" id = "qiangSizeBtn" value="设置" onclick="reSizeQiang()">
		</dir>
		<dir id="3DCon">
		</dir>
		<script src="js/three.js"></script>
		<script src="js/controls/MapControls.js"></script>
		<script>
 
			var container;
 
			var camera, scene, renderer;
 
			var controls;
 
			var targetRotation = 0;
			var targetRotationOnMouseDown = 0;
 
			var mouseX = 0;
			var mouseXOnMouseDown = 0;
 
			var windowHalfX = window.innerWidth / 2;
			var loader = new THREE.TextureLoader();
			var texture = loader.load( "module/brick_diffuse.jpg" );
 
			// it's necessary to apply these settings in order to correctly display the texture on a shape geometry
 
			texture.wrapS = texture.wrapT = THREE.RepeatWrapping;
			texture.repeat.set( 0.008, 0.008 );
 
			init();
			animate();
			function reSizeQiang() {
				const newSize = Number(document.getElementById( 'qiangSize' ).value)
				for (let idx = scene.children.length; idx--; idx >= 0 ) {
					let objMesh = scene.children[idx]
					if (objMesh.type2 === "qiang") {
					
						var geometry = objMesh.geometry
						for (let idx2 in geometry.attributes.position.array) {
							if (geometry.attributes.position.array[idx2] === 320) {
								geometry.attributes.position.array[idx2] = newSize;
							}
						}
						for (let idx2 in geometry.attributes.uv.array) {
							if (geometry.attributes.uv.array[idx2] === 320) {
								geometry.attributes.uv.array[idx2] = newSize;
							}
						}
						var mesh = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { side: THREE.DoubleSide, map: texture } ) );
						mesh.position.set( objMesh.position.x, objMesh.position.y, objMesh.position.z);
						mesh.rotation.set( objMesh.rotation.x, objMesh.rotation.y, objMesh.rotation.z );
						mesh.scale.set( objMesh.scale.x, objMesh.scale.y, objMesh.scale.z );
						mesh.type2 = "qiang"
						scene.add( mesh );
						
						objMesh.geometry.dispose(); // 删除几何体
						objMesh.material.dispose(); // 删除材质
						scene.remove(objMesh);
						debugger
					}
				}
			}
			
			function addExtrudeShape( bGeometry,  pi, rt, s ) {
				
 
				// 原先是geometry, 但是bGeometry也可以
				var mesh = new THREE.Mesh( bGeometry, new THREE.MeshBasicMaterial( { side: THREE.DoubleSide, map: texture } ) );
				mesh.position.set( pi.x, pi.y, pi.z - 10 );
				mesh.rotation.set( rt.x, rt.y, rt.z );
				mesh.scale.set( s, s, s );
				mesh.type2 = "qiang"
				scene.add( mesh );
 
			}
			function addShape( shape,  pi, rt, s ) {
				var geometry = new THREE.ShapeBufferGeometry( shape );
				var bGeometry = new THREE.BufferGeometry();
				bGeometry.attributes.position = geometry.attributes.position;
				bGeometry.attributes.uv = geometry.attributes.uv;
				bGeometry.index = geometry.index
				var mesh = new THREE.Mesh( bGeometry, new THREE.MeshBasicMaterial( { side: THREE.DoubleSide, map: texture } ) );
				mesh.position.set( pi.x, pi.y, pi.z );
				mesh.rotation.set( rt.x, rt.y, rt.z );
				mesh.scale.set( s, s, s );
				// group.add( mesh );
				scene.add( mesh );
			}
			function QiangBufferGeometry(wdth, height, depth) {
 
				var squareShape = new THREE.Shape();
				squareShape.moveTo( 0, 0 );
				squareShape.lineTo( 0, height);
				squareShape.lineTo( wdth, height);
				squareShape.lineTo( wdth, 0 );
				squareShape.lineTo( 0, 0 );
				
				var squareShapePath = new THREE.Path();
				squareShapePath.moveTo( 10, 10 );
				squareShapePath.lineTo( 10, height -10);
				squareShapePath.lineTo( wdth - 10, height - 10);
				squareShapePath.lineTo( wdth -10 , 10 );
				squareShapePath.lineTo( 10, 10 );
				
				squareShape.holes.push( squareShapePath );// 挖洞
 
				
				var extrudeSettings = { depth: depth, bevelEnabled: true, bevelSegments: 0, steps: 1, bevelSize: 0, bevelThickness: 0 };
				
				// extruded shape
 
				var geometry = new THREE.ExtrudeBufferGeometry( squareShape, extrudeSettings );
				// ExtrudeBufferGeometry转换BufferGeometry
				var bGeometry = new THREE.BufferGeometry();
				// bGeometry.attributes.normal = geometry.attributes.normal;
				bGeometry.attributes.position = geometry.attributes.position;
				bGeometry.attributes.uv = geometry.attributes.uv;
				return bGeometry;
			}
			function init() {
 
				container = document.getElementById( '3DCon' );
				
				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0xf0f0f0 );
				var axes = new THREE.AxesHelper(10);
				scene.add(axes);
				
				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 1, 1000 );
				camera.position.set( 0, 100, 500 );
				camera.up.x = 0;
				camera.up.y = 0;
				camera.up.z = 1;//相机以Z方向为上方
				// scene.add( camera );
				controls = new THREE.MapControls( camera, container );	
 
				var light = new THREE.PointLight( 0xffffff, 0.8 );
				camera.add( light );
 
				
				// Square
				const sqLength = 80;
				const squareShape = QiangBufferGeometry(sqLength * 4, sqLength, 8);
				
				addExtrudeShape( squareShape, {x: -sqLength * 2, y: -sqLength * 2, z: 0}, {x: Math.PI / 2, y: 0, z: 0}, 1 );
				addExtrudeShape( squareShape, {x: -sqLength * 2, y: sqLength * 2, z: 0}, {x: Math.PI / 2, y: 0, z: 0}, 1 );
				addExtrudeShape( squareShape, {x: -sqLength * 2, y: -sqLength * 2, z: 0}, {x: Math.PI / 2, y: Math.PI / 2, z: 0}, 1 );
				addExtrudeShape( squareShape, {x: sqLength * 2-8, y: -sqLength * 2, z: 0}, {x: Math.PI / 2, y: Math.PI / 2, z: 0}, 1 );
				
				// Square
 
				var sqDibanLength = 80;
 
				var squareDibanShape = new THREE.Shape();
				squareDibanShape.moveTo( -sqDibanLength, -sqDibanLength );
				squareDibanShape.lineTo( -sqDibanLength, sqDibanLength );
				squareDibanShape.lineTo( sqDibanLength, sqDibanLength );
				squareDibanShape.lineTo( sqDibanLength, -sqDibanLength );
				squareDibanShape.lineTo( -sqDibanLength, -sqDibanLength );
				
				addShape( squareDibanShape, {x: - sqDibanLength / 2, y: 0, z: 0}, {x: 0, y: 0, z: 0}, 1 );
				//
 
				renderer = new THREE.WebGLRenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				container.appendChild( renderer.domElement );
 
				//document.addEventListener( 'mousedown', onDocumentMouseDown, false );
				//document.addEventListener( 'touchstart', onDocumentTouchStart, false );
				//document.addEventListener( 'touchmove', onDocumentTouchMove, false );
 
				//
 
				window.addEventListener( 'resize', onWindowResize, false );
 
			}
 
			function onWindowResize() {
 
				camera.aspect = window.innerWidth / window.innerHeight;
				camera.updateProjectionMatrix();
				renderer.setSize( window.innerWidth, window.innerHeight );
 
			}
 
			//
 
			function onDocumentMouseDown( event ) {
 
				event.preventDefault();
 
				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
				document.addEventListener( 'mouseout', onDocumentMouseOut, false );
 
				mouseXOnMouseDown = event.clientX - windowHalfX;
				targetRotationOnMouseDown = targetRotation;
 
			}
 
			function onDocumentMouseMove( event ) {
 
				mouseX = event.clientX - windowHalfX;
 
				targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
 
			}
 
			function onDocumentMouseUp() {
 
				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
 
			}
 
			function onDocumentMouseOut() {
 
				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
 
			}
 
			function onDocumentTouchStart( event ) {
 
				if ( event.touches.length == 1 ) {
 
					event.preventDefault();
 
					mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
					targetRotationOnMouseDown = targetRotation;
 
				}
 
			}
 
			function onDocumentTouchMove( event ) {
 
				if ( event.touches.length == 1 ) {
 
					event.preventDefault();
 
					mouseX = event.touches[ 0 ].pageX - windowHalfX;
					targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
 
				}
 
			}
 
			//
 
			function animate() {
 
				requestAnimationFrame( animate );
 
				render();
 
			}
 
			function render() {
 
				//group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
				renderer.render( scene, camera );
 
			}
 
		</script>
 
	</body>
</html>

 

效果图

 

 

UV按Face分解

function QiangBufferGeometry(wdth, height, depth) {

	var squareShape = new THREE.Shape();
	squareShape.moveTo( 0, 0 );
	squareShape.lineTo( 0, height);
	squareShape.lineTo( wdth, height);
	squareShape.lineTo( wdth, 0 );
	squareShape.lineTo( 0, 0 );
	
	var squareShapePath = new THREE.Path();
	squareShapePath.moveTo( 10, 10 );
	squareShapePath.lineTo( 10, height -10);
	squareShapePath.lineTo( wdth - 10, height - 10);
	squareShapePath.lineTo( wdth -10 , 10 );
	squareShapePath.lineTo( 10, 10 );
	
	squareShape.holes.push( squareShapePath );// 挖洞

	
	var extrudeSettings = { depth: depth, bevelEnabled: true, bevelSegments: 0, steps: 1, bevelSize: 0, bevelThickness: 0 };
	
	// extruded shape

	var geometry = new THREE.ExtrudeBufferGeometry( squareShape, extrudeSettings );
	// ExtrudeBufferGeometry转换BufferGeometry
	var bGeometry = new THREE.BufferGeometry();
	// bGeometry.attributes.normal = geometry.attributes.normal;
	bGeometry.attributes.position = geometry.attributes.position;
	bGeometry.attributes.uv = geometry.attributes.uv;
	for (let idx2 in bGeometry.attributes.uv.array) {

		// UV: 每一个Face有三个顶点组成,每个顶点由两个数字
		// 所以每个Face由6个数字组成
		if (idx2 > 5) {
			geometry.attributes.uv.array[idx2] = 0;
		}
		/*
		// 大face 1, 
		if (idx2 > 47) {
			geometry.attributes.uv.array[idx2] = 0;
		}
		*/
		/*
		// 大face 2
		if (idx2 < 48 || idx2 > 96) {
			geometry.attributes.uv.array[idx2] = 0;
		}
		*/
	}
	return bGeometry;
}

 只贴图第一个Face和Face的一小块儿。

正面和后面为例,每一面由8个Face组成,如果侧面或顶面地面应该由两个Face组成,这个自己可以试一下。

看到现在突然想起之前的记录
ThreeJs的学习:贴图(三)BufferGeometry
                           https://blog.youkuaiyun.com/jinold/article/details/99243957
ThreeJs的学习:BufferGeometry的理解(3),Geometry和BufferGeometry的关系
                           https://blog.youkuaiyun.com/jinold/article/details/98250783

饶了一大圈又回来了。恍然大悟,原来ThreeJs中BufferGeometry才是无敌的存在。

后面练习的球形圆柱形,多边形应该也是这样

结束本章结束前最后一个实验:

var geometry = new THREE.ExtrudeBufferGeometry( squareShape, extrudeSettings );
// ExtrudeBufferGeometry转换BufferGeometry
var bGeometry1 = new THREE.BufferGeometry();
bGeometry1.attributes.position = new THREE.Float32BufferAttribute( geometry.attributes.position.array.slice(0,72), 3);
bGeometry1.attributes.uv = new THREE.Float32BufferAttribute( geometry.attributes.uv.array.slice(0,48), 2);
addExtrudeShape( bGeometry1, {x: -sqLength * 2, y: -sqLength * 2, z: 0}, {x: Math.PI / 2, y: 0, z: 0}, 1 ,texture );
				
var bGeometry2 = new THREE.BufferGeometry();
bGeometry2.attributes.position = new THREE.Float32BufferAttribute( geometry.attributes.position.array.slice(72,144), 3);
bGeometry2.attributes.uv = new THREE.Float32BufferAttribute( geometry.attributes.uv.array.slice(48,96), 2);
addExtrudeShape( bGeometry2, {x: -sqLength * 2, y: -sqLength * 2, z: 0}, {x: Math.PI / 2, y: 0, z: 0}, 1 ,texture2 );

效果图:每个墙面不同的贴图 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值