Three.js OBJ模型的剖切

一、
由于项目需要,我进行了模型剖切功能方面的研究,如果单纯实现模型剖切效果,我相信你会从这篇文章中得到收获。

在这里插入图片描述
二、
我对模型剖切功能的研究主要基于three.js中的webgl_clipping_stencil.html进行,该示例中被剖切的网格模型是由Three.js自身创建的几何体和材质构成,当我把导入的obj模型添加进去之后,始终没有剖切成功,更别说绘制剖切面了,这花费了我很长时间,还查过着色器相关的知识,后来随着对Three.js学习的深入,我认识到了我的错误。。。

实际上,最终的代码十分简单,导入的obj模型 和Three.js自身创建的网格模型本质上是一样的,所以上只要用obj模型合理的替代原本的网格模型,便可以实现剖切及剖切面的绘制,不过这样并没有实际上学习到模型剖切的原理,可以在研究场景渲染效果时加深对着色器的认识,再回过头分析剖切的原理。

在这里插入图片描述
三、
首先对源代码中的一些内容进行介绍:

1.创建三个剖切平面,gui用于调节剖切平面的位置

	planes = [
					new THREE.Plane( new THREE.Vector3( - 1, 0, 0 ), 0 ),
					new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0 ),
					new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 )
				];

2.创建 组object,将网格模型 clippedColorFront 和 剖切面stencilGroup 添加到object中,实际上分为两部分内容:将模型位于剖切一侧的部分剪切掉,添加到 组object中,此时只是实现剖切,但是没有剖切面;调用creatPlaneStencilGroup函数,设置几何体、平面、渲染顺序,生成剖切面的网格模型,同样添加到 组object中。

var camera, scene, renderer, object, stats;
//初始化组
object = new THREE.Group();
scene.add( object );
//添加网格模型
var clippedColorFront = new THREE.Mesh( geometry, material );
object.add( clippedColorFront );
//绘制剖切面
var stencilGroup = createPlaneStencilGroup( geometry, plane, i + 1 );
object.add( stencilGroup );

四、
接下来对代码进行修改,实现obj模型的剖切
1.在加载obj模型的函数中修改模型的材质,设置属性clippingPlanes,其参数对应先前创建的平面,实现模型的剪切。

obj.traverse(function(child){
	if (child instanceof THREE.Mesh) {
		child.material = new THREE.MeshStandardMaterial({
		color: child.material.color,
		clippingPlanes: planes,
		clipShadows: true,
		shadowSide: THREE.DoubleSide,
	});
		child.castShadow = true;
		child.renderOrder = 6;
	}
})

2.由于我使用的模型尺寸很大,设置模型缩放比例为0.001,同时为确保模型位于场景中心,模型位置为(x,y,z)。剖切面的绘制如代码所示,object为创建的组,object.children[0]代表加载的obj模型,其子对象为网格模型,具有几何体和材质。这里还需要设置geometry.translate和stencilGroup.scale,从而确保剖切面的比例和位置正确。

	for (var w = 0; w < obj.children.length; w++) {
		var geometry0 = object.children[0].children[w].geometry.clone();
			geometry0.translate(x/0.001, y/0.001, z/0.001);
		var stencilGroup = createPlaneStencilGroup(geometry0, plane,  1);
			stencilGroup.scale.set(0.001,0.001,0.001)
			object.add(stencilGroup);
	}

3.还有一点需要注意,只有位于planeGeom范围内的模型才会被成功剖切,所以要设定合适的参数。

var planeGeom = new THREE.PlaneBufferGeometry( 4000, 4000 );

五、
下面为obj模型剖切的完整代码,为避免代码过长,仅定义了一个剖切平面。

<!DOCTYPE html>
<html lang="en">
<head>
	<title>three.js webgl - clipping stencil</title>
	<meta charset="utf-8">
</head>
<style>
	body{
		overflow:hidden;
		background-color:#263238;
	}
</style>
<body>
<script type="module">
	import * as THREE from '../build/three.module.js';
	import { OrbitControls } from './jsm/controls/OrbitControls.js';
	import { GUI } from './jsm/libs/dat.gui.module.js';
	import Stats from './jsm/libs/stats.module.js';
	import { OBJLoader } from './jsm/loaders/OBJLoader.js';
	import { MTLLoader } from './jsm/loaders/MTLLoader.js';

	var camera, scene, renderer, object, stats;
	var planes, planeObjects=[], planeHelpers;
	var params = {
		planeY: {
			constant: 0,
			negated: false,
			displayHelper: false
		},
	};
	init();
	animate();
	function createPlaneStencilGroup( geometry, plane, renderOrder ) {

		var group = new THREE.Group();
		var baseMat = new THREE.MeshBasicMaterial();
		baseMat.depthWrite = false;
		baseMat.depthTest = false;
		baseMat.colorWrite = false;
		baseMat.stencilWrite = true;
		baseMat.stencilFunc = THREE.AlwaysStencilFunc;
		// back faces
		var mat0 = baseMat.clone();
		mat0.side = THREE.BackSide;
		mat0.clippingPlanes = [ plane ];
		mat0.stencilFail = THREE.IncrementWrapStencilOp;
		mat0.stencilZFail = THREE.IncrementWrapStencilOp;
		mat0.stencilZPass = THREE.IncrementWrapStencilOp;
		var mesh0 = new THREE.Mesh( geometry, mat0 );
		mesh0.renderOrder = renderOrder;
		group.add( mesh0 );
		// front faces
		var mat1 = baseMat.clone();
		mat1.side = THREE.FrontSide;
		mat1.clippingPlanes = [ plane ];
		mat1.stencilFail = THREE.DecrementWrapStencilOp;
		mat1.stencilZFail = THREE.DecrementWrapStencilOp;
		mat1.stencilZPass = THREE.DecrementWrapStencilOp;
		var mesh1 = new THREE.Mesh( geometry, mat1 );
		mesh1.renderOrder = renderOrder;
		group.add( mesh1 );
		return group;
	}
	function init() {

		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera( 36, window.innerWidth / window.innerHeight, 1, 100 );
		camera.position.set( 0, 0, 20 );
		let ambientLight = new THREE.AmbientLight(0xcccccc,0.6);
		scene.add(ambientLight);
		var dirLight = new THREE.DirectionalLight( 0xffffff, 1 );
		scene.add( dirLight );
		planes = [
			new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0 ),
		];
		planeHelpers = planes.map( p => new THREE.PlaneHelper( p, 10, 0xffffff ) );
		planeHelpers.forEach( ph => {
			ph.visible = false;
			scene.add( ph );
		} );
		object = new THREE.Group();
		scene.add( object );
		var obj_loader = new OBJLoader();
		var mtl_loader=new MTLLoader();
		mtl_loader.load( 'models/obj/blue0.mtl', function (materials ) {
			materials.preload();
			obj_loader.setMaterials(materials);
			obj_loader.load( 'models/obj/blue0.obj', function (obj ) {

				obj.scale.set(0.001,0.001,0.001);

				let bbox = new THREE.Box3().setFromObject(obj);
				var x=-(bbox.max.x+bbox.min.x)/2;
				var y=-(bbox.max.y+bbox.min.y)/2;
				var z= -(bbox.max.z+bbox.min.z)/2;
				obj.position.set(-(bbox.max.x+bbox.min.x)/2,
						-(bbox.max.y+bbox.min.y)/2,
						-(bbox.max.z+bbox.min.z)/2);
				obj.traverse(function(child){
					if (child instanceof THREE.Mesh) {
						child.material = new THREE.MeshStandardMaterial({
							color: child.material.color,
							clippingPlanes: planes,
							clipShadows: true,
							shadowSide: THREE.DoubleSide,
						});
						child.castShadow = true;
						child.renderOrder = 6;
					}
				})
				console.log(obj)
				object.add(obj);
				var planeGeom = new THREE.PlaneBufferGeometry( 4000, 4000 );
					var poGroup = new THREE.Group();
					var plane = planes[ 0 ];
					for (var w = 0; w < obj.children.length; w++) {
						var geometry0 = object.children[0].children[w].geometry.clone();
						geometry0.translate(x/0.001, y/0.001, z/0.001);
						var stencilGroup = createPlaneStencilGroup(geometry0, plane,  1);
						stencilGroup.scale.set(0.001,0.001,0.001)
						object.add(stencilGroup);
					}
					var planeMat =
							new THREE.MeshBasicMaterial( {
								color: 0xffff00,
								clippingPlanes: planes.filter( p => p !== plane ),
								stencilWrite: true,
								stencilRef: 0,
								stencilFunc: THREE.NotEqualStencilFunc,
								stencilFail: THREE.ReplaceStencilOp,
								stencilZFail: THREE.ReplaceStencilOp,
								stencilZPass: THREE.ReplaceStencilOp,
							} );
					var po = new THREE.Mesh( planeGeom, planeMat );
					po.onAfterRender = function ( renderer ) {
						renderer.clearStencil();
					};
					po.renderOrder = 1.1;
					poGroup.add( po );
					planeObjects.push( po );
					scene.add( poGroup );

			} );
		})
		var ground = new THREE.Mesh(
				new THREE.PlaneBufferGeometry( 9, 9, 1, 1 ),
				new THREE.ShadowMaterial( { color: 0, opacity: 0.25, side: THREE.DoubleSide } )
		);
		ground.rotation.x = - Math.PI / 2; // rotates X/Y to X/Z
		ground.position.y = - 1;
		ground.receiveShadow = true;
		scene.add( ground );

		stats = new Stats();
		document.body.appendChild( stats.dom );

		renderer = new THREE.WebGLRenderer( { antialias: true } );
		renderer.shadowMap.enabled = true;
		renderer.setPixelRatio( window.devicePixelRatio );
		renderer.setSize( window.innerWidth, window.innerHeight );
		renderer.setClearColor( 0x263238 );
		window.addEventListener( 'resize', onWindowResize, false );
		document.body.appendChild( renderer.domElement );
		renderer.localClippingEnabled = true;

		var controls = new OrbitControls( camera, renderer.domElement );
		controls.update();

		var gui = new GUI();
		var planeY = gui.addFolder( 'planeY' );
		planeY.add( params.planeY, 'displayHelper' ).onChange( v => planeHelpers[ 1 ].visible = v );
		planeY.add( params.planeY, 'constant' ).min( - 6 ).max( 6 ).onChange( d => planes[ 0 ].constant = d );
		planeY.add( params.planeY, 'negated' ).onChange( () => {
			planes[ 0 ].negate();
			params.planeY.constant = planes[ 0 ].constant;

		} );
		planeY.open();
	}
	function onWindowResize() {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		renderer.setSize( window.innerWidth, window.innerHeight );
	}
	function animate() {

		requestAnimationFrame( animate );
		for ( var i = 0; i < planeObjects.length; i ++ ) {
			var plane = planes[ i ];
			var po = planeObjects[ i ];
			plane.coplanarPoint( po.position );
			po.lookAt(
					po.position.x - plane.normal.x,
					po.position.y - plane.normal.y,
					po.position.z - plane.normal.z,
			);
		}
		stats.begin();
		renderer.render( scene, camera );
		stats.end();
	}

</script>
</body>
</html>

  • 8
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 16
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 16
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值