一、任意剪裁平面
通过任意剪裁平面,可以展示物体x剖面效果,下面的示例为展示一个球体剖面,核心代码如下所示:
1 ...//此处省略了定义变量的代码,读者可以查看源代码进行学习
2 var clipPlanes = [ //定义剪裁平面
3 new THREE.Plane( new THREE.Vector3( 1, 0, 0 ), 0 ), //设置 yOz面
4 new THREE.Plane( new THREE.Vector3( 0, - 1, 0 ), 0 ), //设置 xOz面
5 new THREE.Plane( new THREE.Vector3( 0, 0, - 1 ), 0 ), //设置 xOy面
6 ];
7 ...//此处省略了初始化场景,添加灯光,设置标签等部分的代码,读者可自行查看随书源代码
8 function addGeometry(){//添加几何对象的方法
9 var texture=["", "img/1.png", "img/1.png", "img/1.png", "img/1.png", "img/1.png
10 ", "img/1.png", "img/1.png", "img/1.png", "img/earth.png"]; //定义纹理数组
11 var myColor=["", "#e8cd66", "#e4c133", "#ce9b32", "#cd9a2f", "#c98f24", "#c3760a
12 ", "#c34900", "#ae1601", "#ffffff"]; //定义颜色数组
13 group=new THREE.Group(); //定义对象组合
14 for(var i=1; i<10; i++){ //创建10个球体
15 var geometry = new THREE.SphereGeometry( i / 2, 48, 24 ); //创建球体
16 var material = new THREE.MeshLambertMaterial({ //定义材质
17 color: new THREE.Color( myColor[i] ), //设置颜色
18 side: THREE.DoubleSide, //设置球体内外双面纹理贴图
19 clippingPlanes: clipPlanes, //设置剪裁面
20 clipIntersection: true, //开启任意剪裁平面
21 map: THREE.ImageUtils.loadTexture(texture[i]) //设置纹理
22 });
23 group.add( new THREE.Mesh( geometry, material ) ); //将球体添加进对象组合
24 }
25 scene.add(group); //将对象组合添加进场景
26 }
❑ 第2~6行代码中的new THREE.Plane(normal : Vector3, constant : Float)方法为创建平面的方法,其中参数normal是一个三维向量,表示平面的法向量,默认值为(1,0,0);参数constant是一个浮点型数,表示原点到平面的距离,默认值为0。
❑ 第8~26行的代码创建了一个被剪裁掉1/8的地球模型,并且能够看到地球模型的内部结构。该地球模型绘制了10个半径依次增大的球,让处于里面的每一个球都贴上不同颜色的纹理,最外层的球贴上地球表面的纹理。最后就能模拟出地球和地心的效果。
说明:在上述代码中首先定义了平面数组,然后在创建球体的材质中开启任意剪裁平面。在绘制球体时,剪裁面法向量的负方向区域为剪裁区域,3个剪裁面的法向量负方向组成了空间剪裁区域,最后绘制球体时位于空间剪裁区域内的部分将不可见。
二、 单个物体的多个实例
在一些场景,某个物体可能会多次重复出现。如果场景中多次重复的物体是obj等格式的模型文件,为了提高场景的渲染速度,可以使用单个物体多次绘制的方式来提高场景的渲染速度和流畅性。
示例:场景由500个茶壶按照一定的组合搭建而成。具体代码如下:
function getColor(){ //随机创建十六进制颜色方法
2 var colorElements = "0,1,2,3,4,5,6,7,8,9, a, b, c, d, e, f"; //定义十六进制颜色字符串
3 var colorArray = colorElements.split(", "); //切分颜色字符串
4 var color ="#"; //定义十六进制颜色的第一个字符
5 for(var i =0; i<6; i++){
6 color+=colorArray[Math.floor(Math.random()*16)]; //得到随机颜色值
7 }
8 return color; //返回颜色值
9 }
10 function getPostion() { //生成随机位置
11 var postion=new THREE.Vector3(); //定义空间点坐标
12 postion.x=Math.random()*1000; //随机生成点的 x值
13 postion.y=Math.random()*1000; //随机生成点的 y值
14 postion.z=Math.random()*1000; //随机生成点的 z值
15 return postion; //返回点坐标
16 }
17 function addMesh(){ //创建物体的方法
18 var jsLoader=new THREE.JSONLoader(); //定义js文件加载器
19 jsLoader.load('obj/1.js', function (object) { //加载茶壶模型的js文件
20 object.computeBoundingBox(); //设置茶壶模型的边界框
21 geometrySize = object.boundingBox.getSize(); //获取茶壶模型尺寸值
22 var objTemp=object.clone(); //复制茶壶模型
23 for ( var i = 0; i < meshCount; i ++ ) { //将茶壶绘制499次
24 material=new THREE.MeshLambertMaterial({color:getColor()});
//材质随机颜色
25 var object = new THREE.Mesh( objTemp, material ); //创建茶壶物体对象
26 object.rotation.set(Math.random()*Math.PI, Math.random()*//设置旋转角度
27 Math.PI, Math.random()*Math.PI);
28 object.position.set(getPostion().x, getPostion().y, getPostion().z);
//设置位置
29 object.material = material.clone(); //复制茶壶物体对象的材质
30 scene.add( object ); //将茶壶物体对象添加进场景
31 }
32 mesh=new THREE.Mesh(objTemp, material); //创建原点处的茶壶对象
33 mesh.rotation.x=-Math.PI*0.5; //将茶壶绕 x轴旋转90°
34 scene.add(mesh); //将茶壶添加进场景
35 })
36 }
❑ 第1~9行为随机生成十六进制颜色的方法。通过随机数方式随机地从表示颜色的字符数组中取值,然后组合成一个完整的颜色字符串,最后将颜色字符串返回。
❑ 第10~16行为随机生成茶壶初始位置点的方法。由于是用随机数乘以一个常数给出的x、y、z轴坐标,所以每一个点的位置都在边长等于该常数的正方体区域内。
❑ 第17~36行为创建茶壶物体的方法。首先定义js文件的加载器并加载茶壶模型文件,并将茶壶几何体进行复制,再用for循环方式来创建多个茶壶网格对象并添加到场景中,同时,在创建每个茶壶网格对象时设置了不同的旋转角度和初始位置点。
说明: 在模型加载完成之后,返回一个对象,它属于几何体对象,包含几何体的顶点数据、顶点索引数据、UV坐标数据等信息。这时便可以不用反复加载模型文件以获取对象,直接一次加载后重复使用几何体对象去创建网格对象,即可实现单个物体的多次绘制。
三、 高真实感的水面
经常需要高真实感的水面,包括真实的水面波纹和真实的水面倒影等。在Three.js引擎中要想实现这种高真实的水面是非常简单的。Three.js引擎提供了Water类,下面通过示例说明。
(1)场景搭建,包括二十面体的创建、天空盒的加载和水面的搭建等。首先介绍的是场景的初始化,包括初始化场景基本组件、向场景中添加物体、向场景中添加天空盒、添加鼠标控制,以及添加窗口变化监听等,具体代码如下:
1 var water; //水面
2 var sphere; //球体
3 var sphereAngle=0; /球运动的角度
4 ......//此处省略了声明其他变量的代码,读者可自行查看随书源代码
5 function init() { //初始化场景
6 initScene(); //初始化场景的基本组建
7 addMesh(); //添加物体
8 setSkybox(); //添加天空盒
9 addLight(); //添加光源
10 addControls(); //添加鼠标控制
11 addSupport(); //添加FPS状态监测
12 renderScene(); //渲染场景
13 document.getElementById("WebGL-output").appendChild(renderer.domElement);
14 window.addEventListener( 'resize', onWindowResize, false ); //窗口变化监听
15 }
16 function addSupport() { //添加FPS状态监测
17 stats=new Stats(); //创建状态监测对象
18 container.appendChild( stats.dom );
19 }
20 function addControls() { //添加鼠标控制
21 var controls = new THREE.OrbitControls( camera, renderer.domElement ); //添加鼠标控制
22 controls.enablePan = true; //是否可以平移
23 controls.enableZoom =true; //是否可以缩放
24 controls.maxPolarAngle = Math.PI*4/9; /控制角度
25 }
26 function initScene() { //初始化场景的基本组件
27 scene = new THREE.Scene(); //创建场景对象
28 container = document.getElementById( "container" );
29 renderer = new THREE.WebGLRenderer({ antialias: true } ); //创建渲染器对象
30 renderer.setClearColor(new THREE.Color(0x979797)); //设置背景颜色
31 renderer.setSize(window.innerWidth, window.innerHeight); //设置渲染窗口的大小
32 camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.
innerHeight, 0.1, 5000);
33 camera.position.set(8,3,0); //设置摄像机位置
34 camera.lookAt( scene.position ); //设置摄像机观察目标的位置
35 }
❑ 第1~14行为声明变量和初始化场景。初始化场景包括初始化场景的基本组件和向场景添加物体、天空盒、光照、鼠标控制和FPS状态监测以及渲染场景。其中,添加光照的代码与前面案例的相似,在此不在赘述。
❑ 第16~19行为添加鼠标控制以及FPS状态监测的代码。在添加鼠标控制方法中,开启对场景平移和旋转的控制,并将旋转角度控制在一定范围内。
❑ 第26~35行为初始化场景的基本组件,包括创建场景对象、渲染器对象,设置背景颜色和渲染窗口的大小,创建摄像机并指定摄像机的位置和观察位置。
(2)介绍添加天空盒的方法,包括加载立方体纹理,创建立方体着色器对象,创建着色器材质,创建立方体几何对象和网格对象并将网格对象添加进场景,具体代码如下:
1 function setSkybox() { //添加天空盒
2 var cubeTextureLoader = new THREE.CubeTextureLoader(); //创建立方体纹理加载对象
3 cubeTextureLoader.setPath( 'img/' ); //设置加载路径
4 var cubeMap = cubeTextureLoader.load( [ 5 'skycubemap_left.jpg', 'skycubemap_right.jpg', //立方体左面和右面的图片
6 'skycubemap_up.jpg', 'skycubemap_down.jpg', //立方体上面和下面的图片
7 'skycubemap_back.jpg', 'skycubemap_front.jpg', //立方体后面和前面的图片
8 ] );
9 var cubeShader = THREE.ShaderLib[ 'cube' ]; //创建立方体着色器对象
10 cubeShader.uniforms[ 'tCube' ].value = cubeMap; //传入立方体纹理
11 var skyBoxMaterial = new THREE.ShaderMaterial( { //创建着色器材质
12 fragmentShader: cubeShader.fragmentShader, //立方体片元着色器
13 vertexShader: cubeShader.vertexShader, //立方体顶点着色器
14 uniforms: cubeShader.uniforms, //立方体着色器的一致变量
15 side: THREE.BackSide //背面绘制
16 } );
17 var skyBoxGeometry = new THREE.BoxBufferGeometry(500, 500, 500); //创建立方体几何体
18 var skyBox = new THREE.Mesh( skyBoxGeometry, skyBoxMaterial ); //创建网格对象
19 scene.add( skyBox ); //将天空盒添加进场景
20 }
说明:上面是向场景中添加天空盒的代码。首先需要创建立方体纹理加载对象,并加载天空盒的6张图片。接下来创建材质对象并将其传入立方体着色器、立方体纹理以及开启背面绘制。最后创建立方体几何体对象,创建天空盒网格对象以及将其添加进场景。
(3)介绍添加物体和渲染场景的方法。添加物体的方法包括向场景中添加二十面体,向场景中添加地板,向场景中添加水面以及设置水面的颜色、流动方向和水面反射程度等。渲染场景的方法主要是控制二十面体的运动并请求绘制下一帧画面,具体代码如下:
1 function addMesh(){
2 var geometry = new THREE.IcosahedronGeometry(2, 1 ); //创建一个二十面体
3 for ( var i = 0, j = geometry.faces.length; i < j; i ++ ) {
4 geometry.faces[ i ].color.setHex( Math.random() * 0xffffff );
//对每个面随机指定颜色
5 }
6 var material = new THREE.MeshStandardMaterial( { //创建材质
7 vertexColors: THREE.FaceColors, //顶点颜色
8 roughness: 0.0, //粗糙度
9 flatShading: true, //平滑着色
10 } );
11 sphere = new THREE.Mesh( geometry, material ); //创建二十面体网格对象
12 scene.add( sphere ); //向场景添加网格对象
13 var groundGeometry = new THREE.PlaneBufferGeometry( 20, 20); //地板几何体
14 var groundMaterial = new THREE.MeshStandardMaterial( { roughness: 0.8,
metalness: 0.4 } );
15 var ground = new THREE.Mesh( groundGeometry, groundMaterial ); //创建地板网格对象
16 ground.rotation.x = Math.PI * - 0.5; //绕 x轴逆向旋转90°
17 var textureLoader = new THREE.TextureLoader(); //创建纹理加载对象
18 textureLoader.load( 'img/teture.png', function( map ) {
19 map.wrapS = THREE.RepeatWrapping; //S轴的纹理拉伸方式
20 map.wrapT = THREE.RepeatWrapping; //T轴的纹理拉伸方式
21 map.anisotropy =16; //最大各异向程度
22 map.repeat.set( 5, 5 ); //S轴和 y轴的重复次数
23 groundMaterial.map = map; //传入纹理
24 scene.add( ground ); //将地板网格对象添加进场景
25 } );
26 var waterGeometry = new THREE.PlaneBufferGeometry(20, 20); //创建水面几何体
27 water = new THREE.Water( waterGeometry, { //创建水面网格对象
28 color: '#ffffff', //水的颜色
29 scale: 0, //纹理坐标放大倍数
30 flowDirection: new THREE.Vector2( 4, 4 ), //流动方向
31 textureWidth: 1024, //纹理宽度
32 textureHeight: 1024, //纹理高度
33 reflectivity:0.5 //水面反射影像的程度
34 } );
35 water.position.y =1; //水面的 y坐标
36 water.rotation.x = Math.PI * - 0.5; //绕 x轴逆向旋转90°
37 scene.add( water ); //向场景中添加水面
38 }
39 function renderScene() { //渲染场景的方法
40 requestAnimationFrame(renderScene); //请求绘制下一帧
41 render();
42 }
43 function render() { //实际渲染的方法
44 sphere.position.y=2+3*Math.cos(sphereAngle); //二十面体的运动,改变 y轴坐标
45 sphereAngle+=0.02; //增加运动角度
46 stats.update(); //FPS状态更新
47 renderer.render( scene, camera ); //渲染场景
48 }
❑ 第2~12行为向场景添加二十面体的方法。在创建了二十面几何体后,遍历二十面体的各个面并分别赋予不同的颜色。接着创建材质并设置顶点颜色和粗糙程度以及是否平滑着色,然后创建二十面体网格对象并将其添加进场景。
❑ 第13~25行为向场景添加地板网格对象的代码。创建地板几何体后,创建纹理加载对象并加载纹理和设置纹理、S轴和T轴的拉伸方式以及最大各异向程度等。在纹理加载完毕后,将地板网格对象添加进场景。
❑ 第26~37行代码为创建水面网格对象并将其添加进场景。在创建水面网格对象时,设置了水的颜色、纹理坐标放大倍数、流动方向,以及水面反射影像的程度等属性。
❑ 第39~48行为渲染场景的方法。在实际渲染场景的方法中,通过改变运动角度和改变二十面体的y坐标,可不断更新FPS的状态。