three.js 02-03 之自定义形状

59 篇文章 2 订阅
59 篇文章 6 订阅

    上一篇,我们已经学习了如何使用 Three.js 提供的几种常用的几何对象及网格。今天,我们来重点学习一下,如何通过自定义顶点和面来构建自己的几何体,并用它来创建网格。我们先来一个完整的例子,代码如下:

<!DOCTYPE html>
<html>
<head>
    <title>示例 02.03 - 自定义形状</title>
	<script src="../build/three.js"></script>
	<script src="../build/js/controls/OrbitControls.js"></script>
	<script src="../build/js/libs/stats.min.js"></script>
	<script src="../build/js/libs/dat.gui.min.js"></script>
	<script src="../jquery/jquery-3.2.1.min.js"></script>
    <style>
        body {
            /* 设置 margin 为 0,并且 overflow 为 hidden,来完成页面样式 */
            margin: 0;
            overflow: hidden;
        }
		/* 统计对象样式 */
		#Stats-output {
			position: absolute;
			left: 0px;
			top: 0px;
		}
    </style>
</head>
<body>

<!-- 用于 WebGL 输出的 Div -->
<div id="WebGL-output"></div>
<!-- 用于统计 FPS 输出的 Div -->
<div id="Stats-output"></div>

<!-- 运行 Three.js 示例的 Javascript 代码 -->
<script type="text/javascript">

	var scene;
	var camera;
	var render;
	var controls;
	var stats;
	
	var guiControls;
	var gui;
	
	var plane;
	var mesh;
	var controlPoints = [];

    // 当所有元素加载完毕后,就执行我们 Three.js 相关的东西
    $(function() {
		stats = initStats();
		
		scene = new THREE.Scene();
		camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // 2147483647
		camera.position.set(-20, 25, 20);
		camera.lookAt(new THREE.Vector3(5, 0, 0));
		render = new THREE.WebGLRenderer( {antialias: true} ); // antialias 抗锯齿
		render.setSize(window.innerWidth, window.innerHeight);
		render.setClearColor(0xEEEEEE);
		render.shadowMap.enabled = true; // 允许阴影投射
		$('#WebGL-output')[0].appendChild(render.domElement);
		window.addEventListener('resize', onWindowResize, false);
		controls = new THREE.OrbitControls(camera, render.domElement);
		
		scene.add(new THREE.AxisHelper(20));// 加入坐标轴
		
		// 加入一个平面
		var planeGeometry = new THREE.PlaneGeometry(60, 40, 1, 1);
		var planeMaterial = new THREE.MeshLambertMaterial( {color: 0xFFFFFF} );
		plane = new THREE.Mesh(planeGeometry, planeMaterial);
		plane.rotation.x = -0.5 * Math.PI; // 沿着 X轴旋转-90°
		plane.position.x = 0;
		plane.position.y = 0;
		plane.position.z = 0;
		plane.receiveShadow = true; // 几何平面接收阴影
		scene.add(plane);
		
		// 加入一个环境光源
		var ambientLight = new THREE.AmbientLight(0x090909);
		scene.add(ambientLight);
		
		// 加入一个聚光灯光源
		// 注:基础材质 MeshBasicMaterial 不会对光源产生反应,因此要改用 MeshLambertMaterial 或 MeshPhongMaterial 材质才有效果
		var spotLight = new THREE.SpotLight( 0xFFFFFF);
		spotLight.position.set(-40, 40, 50);
		spotLight.castShadow = true; // 光源产生阴影
		spotLight.shadow.mapSize.width = 1024; // 必须是 2的幂,默认值为 512
		spotLight.shadow.mapSize.height = 1024; // 必须是 2的幂,默认值为 512
		scene.add(spotLight);
		
		guiControls = new function() {
			// 克隆处理函数
			this.clone = function() {
				var cloneGeometry = mesh.children[0].geometry.clone();
				var materials = [
                    new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),
                    new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})

                ];
				var cloneMesh = THREE.SceneUtils.createMultiMaterialObject(cloneGeometry, materials);
				cloneMesh.children.forEach(function(e){
					e.castShadow = true;
				});
				
				cloneMesh.translateX(5);
				cloneMesh.translateZ(5);
				cloneMesh.name = "cloneMesh";
				scene.remove(scene.getObjectByName("cloneMesh"));
				scene.add(cloneMesh);
			}
		}
		
		function addControl(x, y, z) {
			var controlPoint = new function() {
				this.x = x;
				this.y = y;
				this.z = z;
			};
			return controlPoint;
		}
		controlPoints.push(addControl(3, 5, 3));
		controlPoints.push(addControl(3, 5, 0));
		controlPoints.push(addControl(3, 0, 3));
		controlPoints.push(addControl(3, 0, 0));
		controlPoints.push(addControl(0, 5, 0));
		controlPoints.push(addControl(0, 5, 3));
		controlPoints.push(addControl(0, 0, 0));
		controlPoints.push(addControl(0, 0, 3));
		
		gui = new dat.GUI();
		gui.add(guiControls, 'clone');
		for (var i = 0; i < 8; i++) {
			var folder = gui.addFolder('Vertices ' + (i+1));
			folder.add(controlPoints[i], 'x', -10, 10);
			folder.add(controlPoints[i], 'y', -10, 10);
			folder.add(controlPoints[i], 'z', -10, 10);
		}
		
		addGeomtries();
		
		renderScene();
    });
	
	/** 初始化 stats 统计对象 */
	function initStats() {
		stats = new Stats();
		stats.setMode(0); // 0 为监测 FPS;1 为监测渲染时间
		$('#Stats-output').append(stats.domElement);
		return stats;
	}
	
	/** 渲染场景 */
	function renderScene() {
		stats.update();
		updateMesh(); // 更新物体
		//rotateMesh(); // 旋转物体
		requestAnimationFrame(renderScene);
		render.render(scene, camera);
	}
	
	/** 当浏览器窗口大小变化时触发 */
	function onWindowResize() {
		camera.aspect = window.innerWidth / window.innerHeight;
		camera.updateProjectionMatrix();
		render.setSize(window.innerWidth, window.innerHeight);
	}
	
	/** 添加物体 */
	function addGeomtries() {
		// 通过一系列点创建物体
		var vertices = [
            new THREE.Vector3(1, 3, 1),
            new THREE.Vector3(1, 3, -1),
            new THREE.Vector3(1, -1, 1),
            new THREE.Vector3(1, -1, -1),
            new THREE.Vector3(-1, 3, -1),
            new THREE.Vector3(-1, 3, 1),
            new THREE.Vector3(-1, -1, -1),
            new THREE.Vector3(-1, -1, 1)
        ];
        var faces = [
            new THREE.Face3(0, 2, 1),
            new THREE.Face3(2, 3, 1),
            new THREE.Face3(4, 6, 5),
            new THREE.Face3(6, 7, 5),
            new THREE.Face3(4, 5, 1),
            new THREE.Face3(5, 0, 1),
            new THREE.Face3(7, 6, 2),
            new THREE.Face3(6, 3, 2),
            new THREE.Face3(5, 7, 0),
            new THREE.Face3(7, 2, 0),
            new THREE.Face3(1, 3, 4),
            new THREE.Face3(3, 6, 4),
        ];
        var geom = new THREE.Geometry();
        geom.vertices = vertices;
        geom.faces = faces;
        geom.computeFaceNormals();
		
		var materials = [
			new THREE.MeshLambertMaterial( {color: 0x44ff44, opacity: 0.6, transparent: true} ),
			new THREE.MeshBasicMaterial( {color: 0x000000, wireframe: true} )
		];
		// 创建多材质对象
		mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);
		mesh.traverse(function(e){
			e.castShadow = true;
		});
		mesh.name = geom.type + "-" + 4;
		scene.add(mesh);
	}
	
	/** 更新物体 */
	function updateMesh() {
		mesh.children.forEach(function(e) {
			for (var i = 0; i < 8; i++) {
				e.geometry.vertices[i].x = controlPoints[i].x;
				e.geometry.vertices[i].y = controlPoints[i].y;
				e.geometry.vertices[i].z = controlPoints[i].z;
			}
			e.geometry.verticesNeedUpdate = true;
			e.geometry.computeFaceNormals();
		});
	}
	
	/** 转动物体 */
	rotationSpeed = 0.02
	function rotateMesh() {
		// 遍历整个场景
		scene.traverse(function(e) {
			if (e instanceof THREE.Mesh && e != plane) {
				e.rotation.x += rotationSpeed;
				e.rotation.y += rotationSpeed;
				e.rotation.z += rotationSpeed;
			}
		});
	}

</script>
</body>
</html>
    这个示例的框架其实跟我们前面讲过的其他示例框架基本一样,都有一个 renderScene() 函数。在这个例子中,对于场景中立方体的任何一个顶点,无论何时,只要你修改了右上角这个属性面板中下拉列表里的值,场景中的立方体都会按照修改后的位置正确地渲染出来。为了实现这个目的,在我们的 updateMesh() 函数中,我们把场景里的这个立方体 mesh 的每一个 vertices 属性都更新一遍。更新后我们需要告诉 geometry 对象,这些顶点需要更新,也就是将 geometry 的 verticesNeedUpdate 属性设置为 true。最后我们需要通过调用 geometry 的 computeFaceNormals() 函数重新计算侧面,从而完成整个模型的更新。

    其中,我们还用到了一个 geometry 对象的 clone() 函数(克隆、复制函数)。通过它我们可以创建出一个 geometry 的副本,赋予不同的材质后,我们就可以用这个副本创建出一个不同的网格对象。在这个示例中,你可以通过点击右上角界面中的 clone 按钮来试验这个特性。

    另外,比较细心的读者可能会发现,我们的立方体同时使用了两种不同的材质。这是通过使用 SceneUtils.createMultiMaterialObject() 函数来达到这个目的。在这个函数里,Three.js 库创建的不是一个 THREE.Mesh 实例,而是为每个指定的材质创建一个实例(此处是两个实例),并把这些实例存放在一个名叫 Group 的组里。这样使得我们可以像使用 Scene 对象那样使用这个 Group 组对象,我们可以向其添加网格、通过名字查找网格等。例如,要为组中的每个子对象添加阴影,我们可以像下面这样做:

mesh.children.forEach(function(e){e.castShadow = true});
最后,关于此示例中的其他一些细节方面,请读者多研究几遍,我在代码中已经做了足够的注释。

未完待续···

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值