3.1 使用几何和网格对象
Three.js库附带了很多现成的几何体,你可以在三维场景中使用它们。只要加上材质、创建一个mesh变量,基本上就算完成了。
本文主要介绍如何用这些几何体的基类Geometry手工创建几何体。
Three.js库中的geometry和其他大多数三维库中的一样,基本上是三维空间中的点集,以及一些将这些点连接起来的面。举例来说,一个方块:
- 一个方块有8个角。每个角都可以定义为x、y和z坐标的一个组合。所以每个方块都是三维空间中的8个点。在Three.js库中,这些点称为顶点(vertice)。
- 一个方块有6个侧面,每个角有一个顶点。在Three.js库里,每个侧面称为面(face)。
当你使用Three.js库提供的这些几何体时,你不必亲自定义所有的这些顶点和面。对于一个方块来讲,你只要定义长宽高即可。Three.js库会利用这些信息,在正确的位置创建一个拥有8个顶点的几何体,并用正确的面连接起来。
尽管你可以使用Three.js库提供的几何体,或者自动生成,但是你仍然可以通过定义顶点和面,手工创建几何体。创建方法可以参考下面的代码片段:
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), ];
这段代码展示的是如何创建一个简单的方块。在vertices数组里定义了构成这些方块的点。将这些点连接起来,创建三角面片,并保存在faces数组里。例如,元素new THREE.Face3(0,2,1)就是用vertices数组里的点0、2和1创建的一个三角面片。
本示例的截图如下。你可以修改这个方块的所有顶点。
对于一个顶点,无论何时修改了该顶点下拉列表框里的属性,这个方块都会按照修改后的位置正确地渲染出来,但这需要一些额外的设置。出于效率方面的考虑,Three.js库假设一个网格的几何体在其生命周期内不会改变。为了使我们的示例能够工作,我们需要将下面的代码添加到render循环里:
mesh.geometry.vertices=vertices; mesh.geometry.verticesNeedUpdate=true; mesh.geometry.computeFaceNormals();
在上述这段代码的第一行里,我们将mesh的vertices属性指向一个更新后的顶点数组。我们不需要重新配置侧面,因为它们跟以前一样仍然连接到那些点。设置好这些更新的顶点之后,我们需要告诉geometry对象,这些顶点需要更新。为此我们可以将geometry的verticesNeedUpdate属性设为true。最后我们需要通过调用computeFaceNormals()函数重新计算侧面法线,从而完成整个模型的更新。
组合材质
本示例的材质定义比前面的复杂,代码如下:
var materials = [ new THREE.MeshLambertMaterial( { opacity:0.6, color: 0x44ff44, transparent:true } ), new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true } ) ];
我们使用的不是一个单一的材质,而是有两个元素的材质数组。原因是除了显示一个绿色透明的方块之外,我还想显示一个线框。因为使用线框的话,更容易找出顶点和面的位置。在创建网格的时候,Three.js库也支持使用多种材质。你可以使用SceneUtils.createMultiMaterialObject()
函数来达到这个目的,如下所示:
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom,materials);
在这个函数里,Three.js库创建的不是一个THREE.Mesh实例,而是为每个你指定的材质创建一个实例,并把这些实例存放在一个组里。你可以像使用Scene对象那样使用这个组。你可以添加网格、通过名字查找网格等。例如,要为组中所有子对象添加阴影,我们可以这么做:
mesh.children.forEach(function(e) {e.castShadow=true});
Geometry对象的clone方法
Geometry对象定义的是物体的形式、形状,赋予一定材质之后,我们就可以创建出一可以添加到场景中并由Three.js库渲染的物体。通过clone()函数我们可以创建出一个geometry对象的副本。赋予不同的材质后,我们就可以用这个副本创建出一个不同的网格对象。你可以在控制界面的顶部找到一个clone按钮,如果你点击这个按钮,你就可以按照geometry对象当前的状态创建出一个副本,而且这个新对象被赋予了新的材质,并被添加到了场景中。clone()函数代码如下:
var cloned = 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 mesh2 = THREE.SceneUtils.createMultiMaterialObject(cloned,materials); mesh2.children.forEach(function(e) {e.castShadow=true}); mesh2.translateX(5); mesh2.translateZ(5); mesh2.name="clone"; scene.remove(scene.getChildByName("clone")); scene.add(mesh2);
这段JavaScript代码会在点击clone按钮时调用。我们复制的是方块的第一个子对象的geometry。mesh变量有两个子对象:一个mesh用的是MeshLambertMaterial材质,另一个mesh用的是MeshBasicMaterial材质。基于这个复制出来的几何体,我们将创建一个新的网格,并命名为mesh2。我们可以使用translate()函数移动这个新建的网格,删除之前的副本(如果可见的话),并把这个副本添加到场景中。
完整代码
<!DOCTYPE html> <html> <head> <title>示例03.01 - 自定义几何体</title> <script src="../../../Scripts/jquery-2.1.3.min.js"></script> <script src="../../../Scripts/Threejs/three.min.js"></script> <script src="../../../Scripts/Threejs/stats.js"></script> <script src="../../../Scripts/Threejs/dat.gui.min.js"></script> <style> /* 将margin设置为0,overflow设置为hidden,可让浏览器显示全屏 */ body { margin: 0; overflow: hidden; } </style> </head> <body> <div id="Stats-output"> </div> <!-- 作为canvas容器的div --> <div id="WebGL-output"> </div> <script type="text/javascript"> $(function () { var stats = initStats(); // 创建渲染器,并设置视口大小和清除色 var renderer = new THREE.WebGLRenderer(); renderer.setClearColor(0xEEEEEE, 1.0); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMapEnabled = true; // 将WebGL的输出canvas放置到div中 $("#WebGL-output").append(renderer.domElement); // 创建scene对象,用来容纳网格、相机、光源等对象 var scene = new THREE.Scene(); // 创建相机 var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.x = -20; camera.position.y = 25; camera.position.z = 20; camera.lookAt(new THREE.Vector3(5, 0, 0)); // 创建一个平面作为地面 var planeGeometry = new THREE.PlaneBufferGeometry(60,40,1,1); var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff}); var plane = new THREE.Mesh(planeGeometry,planeMaterial); plane.receiveShadow = true; plane.rotation.x=-0.5*Math.PI; plane.position.x=0 plane.position.y=0 plane.position.z = 0 scene.add(plane); // 添加一个聚光灯光源 var spotLight = new THREE.SpotLight( 0xffffff ); spotLight.position.set( -40, 60, 10 ); spotLight.castShadow = true; scene.add( spotLight ); // 自定义几何体 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.mergeVertices(); // 组合材质 var materials = [ new THREE.MeshLambertMaterial( { opacity:0.6, color: 0x44ff44, transparent:true } ), new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true } ) ]; var mesh = THREE.SceneUtils.createMultiMaterialObject(geom,materials); mesh.children.forEach(function(e) {e.castShadow=true}); scene.add(mesh); function addControl(x,y,z) { var controls = new function() { this.x = x; this.y = y; this.z = z; } return controls; } var controlPoints = []; 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)); var gui = new dat.GUI(); gui.add(new function() { this.clone = function() { var cloned = 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 mesh2 = THREE.SceneUtils.createMultiMaterialObject(cloned,materials); mesh2.children.forEach(function(e) {e.castShadow=true}); mesh2.translateX(5); mesh2.translateZ(5); mesh2.name="clone"; scene.remove(scene.getObjectByName("clone")); scene.add(mesh2); //console.log(scene.children); } },'clone'); for (var i = 0 ; i < 8 ; i++) { f1 = gui.addFolder('顶点' + (i+1)); f1.add(controlPoints[i], 'x',-10,10); f1.add(controlPoints[i], 'y',-10,10); f1.add(controlPoints[i], 'z',-10,10); } render(); function render() { stats.update(); var vertices = []; for (var i = 0 ; i < 8 ; i++) { vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y,controlPoints[i].z)); } mesh.children.forEach(function(e) { e.geometry.vertices=vertices; e.geometry.verticesNeedUpdate=true; e.geometry.computeFaceNormals(); }); requestAnimationFrame(render); renderer.render(scene, camera); } function initStats() { var stats = new Stats(); stats.setMode(0); // 0: fps, 1: ms stats.domElement.style.position = 'absolute'; stats.domElement.style.left = '0px'; stats.domElement.style.top = '0px'; $("#Stats-output").append( stats.domElement ); return stats; } }); </script> </body> </html>文件下载(已下载 1864 次)
发布时间:2015/8/1 下午9:40:27 阅读次数:5582