构建一个"向场景中添加方块"的功能

这主要要在dat.GUI中添加按钮以控制场景中各物体对象的属性。

<!DOCTYPE html>
<html>
<head>
<title>第2话</title>
<script type="text/javascript" src="../libs/three.js"></script>
<script type="text/javascript" src="../libs/stats.js"></script>
<script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="threejs-example"></div>
<div id="Stats-output"></div><script type="text/javascript">function init() {var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);scene.add(camera);var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMapEnabled = true;// 新建一个平面planevar planeGeometry = new THREE.PlaneGeometry(60, 20, 1, 1);var planeMaterial = new THREE.MeshLambertMaterial({color: 0xcccccc});var plane = new THREE.Mesh(planeGeometry, planeMaterial);plane.receiveShadow = true;plane.rotation.x = -0.5 * Math.PI;plane.position.x = 15;plane.position.y = 0;plane.position.z = 0;scene.add(plane);camera.position.x = -30;camera.position.y = 40;camera.position.z = 30;camera.lookAt(scene.position);// 添加自然光(平行光)var ambientLight = new THREE.AmbientLight(0x0c0c0c);scene.add(ambientLight);// 添加聚光灯光源var spotLight = new THREE.SpotLight( 0xffffff );spotLight.position.set( -40, 60, -10 );spotLight.castShadow = true;scene.add(spotLight);document.getElementById("threejs-example").appendChild(renderer.domElement);var controls = new function () {this.rotationSpeed = 0.02;this.numberOfObjects = scene.children.length;       // scene.children[]数组中存放的是所有在场景中的cube对象,scene.children.length可以得到场景中的物体数量this.addCube = function() {var cbeSize = Math.ceil(Math.random() * 3);var cubeGeometry = new THREE.cubeGeometry(cubeSize, cubeSize, cubeSize);            // 物体形状的大小随机var cubeMaterial = new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff}) // 物体材质的颜色随机var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.castShadow = true;cube.name = "cube-" + scene.children.length;cube.position.x = -30 + Math.round(Math.random() * planeGeometry.width);cube.position.y = Math.round(Math.random() * 5);cube.position.z = -20 + Math.round(Math.random() * planeGeometry.height);scene.add(cube);this.numberOfObjects = scene.children.length;}this.removeCube = function() {var allChildren = scene.children;var lastObject = allChildren[allChildren.length-1];if(lastObject instanceof THREE.Mesh) {  // 场景中最后一个cube对象scene.remove(lastObject);           // scene.remove()可以从场景中移除物体this.numberOfObjects = scene.children.length;}}};var gui = new dat.GUI();gui.add(controls, 'rotationSpeed', 0, 0.5); // GUI中的滑动条gui.add(controls, 'addCube');               // GUI中的"addCube"按钮gui.add(controls, 'removeCube');gui.add(controls, 'outputObjects');gui.add(controls, 'numberOfObjects').listen();  // GUI中的监听器(监测场景中的物体数量变化)var stats = initStats();var step = 0;renderScene();function renderScene() {stats.update();scene.traverse(function (ele) {       // scene.traverse(funcA())会对所有scene内的子对象调用funcA()if (ele instanceof THREE.Mesh && ele != plane) {    // 忽略plane平面ele.rotation.x += controls.rotationSpeed;     // 通过traverse()来更新cube对象的旋转速度ele.rotation.y += controls.rotationSpeed;ele.rotation.z += controls.rotationSpeed;}});renderer.render(scene, camera);requestAnimationFrame(renderScene);}function initStats() {var stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init;
</script>
</body>
</html>

点击右边的outputObjects按钮,控制台输出:

这是场景中各元素的属性构成。

使用场景雾化效果

在init()函数的开头加上:

var scene = new THREE.Scene();
/* 开启场景雾化效果 */
scene.fog = new THREE.Fog(0xffffff, 0.015, 100);        // 0xffffff白色雾化效果,near近处的值0.015,far远处的值100
// scene.fog = new THREE.FogExp2( 0xffffff, 0.015 );    // 0xffffff白色雾化效果,雾的浓度0.015

使用材质覆盖效果

在新建场景之后,往init()代码中添加一句:

/* 开启材质覆盖效果 */scene.overrideMaterial = new THREE.MeshLambertMaterial({color: 0xffff00});
// 场景内所有物体的材质都是MeshLambertMaterial({color: 0xffffff})

threejs中内置的几何对象

Three.js库中封装了很多现成的几何体,可以直接调用相应的几何对象构造函数在三维场景中使用它们。确定好几何对象后,只要再加上材质,就创建出了一个完整的mesh对象。

<!DOCTYPE html>
<html>
<head><title>第2话</title><script type="text/javascript" src="../libs/three.js"></script><script type="text/javascript" src="../libs/ParametricGeometries.js"></script><script type="text/javascript" src="../libs/ConvexGeometry.js"></script><script type="text/javascript" src="../libs/stats.js"></script><script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="Stats-output"></div>
<div id="threejs-example"></div><script type="text/javascript">function init() {var stats = initStats();var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMapEnabled = true;var planeGeometry = new THREE.PlaneGeometry(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);camera.position.x = -50;camera.position.y = 30;camera.position.z = 20;camera.lookAt(new THREE.Vector3(-10, 0, 0));var ambientLight = new THREE.AmbientLight(0x090909);scene.add(ambientLight);var spotLight = new THREE.SpotLight(0xffffff);spotLight.position.set(-40, 40, 50);spotLight.castShadow = true;scene.add(spotLight);addGeometries(scene);document.getElementById("threejs-example").appendChild(renderer.domElement);var step = 0;renderScene();function addGeometries(scene) {var geoms = [];geoms.push(new THREE.CylinderGeometry(1, 4, 4));geoms.push(new THREE.BoxGeometry(2, 2, 2));geoms.push(new THREE.SphereGeometry(2));geoms.push(new THREE.IcosahedronGeometry(4));var points = [new THREE.Vector3(2, 2, 2),new THREE.Vector3(2, 2, -2),new THREE.Vector3(-2, 2, -2),new THREE.Vector3(-2, 2, 2),new THREE.Vector3(2, -2, 2),new THREE.Vector3(2, -2, -2),new THREE.Vector3(-2, -2, -2),new THREE.Vector3(-2, -2, 2)];geoms.push(new THREE.ConvexGeometry(points));var pts = [];var detail = .1, radius = 3;for (var angle = 0.0; angle < Math.PI; angle += detail)pts.push(new THREE.Vector3(Math.cos(angle) * radius, 0, Math.sin(angle) * radius));geoms.push(new THREE.LatheGeometry(pts, 12));geoms.push(new THREE.OctahedronGeometry(3));geoms.push(new THREE.ParametricGeometry(THREE.ParametricGeometries.mobius3d, 20, 10));geoms.push(new THREE.TetrahedronGeometry(3));geoms.push(new THREE.TorusGeometry(3, 1, 10, 10));geoms.push(new THREE.TorusKnotGeometry(3, 0.5, 50, 20));var j = 0;for (var i = 0; i < geoms.length; i++) {var cubeMaterial = new THREE.MeshLambertMaterial({wireframe: true, color: Math.random() * 0xffffff});var materials = [new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff, shading: THREE.FlatShading}),new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})];var mesh = THREE.SceneUtils.createMultiMaterialObject(geoms[i], materials);mesh.traverse(function (e) {e.castShadow = true});mesh.position.x = -24 + ((i % 4) * 12);mesh.position.y = 4;mesh.position.z = -8 + (j * 12);if ((i + 1) % 4 == 0) j++;scene.add(mesh);}}function  renderScene() {stats.update();requestAnimationFrame(renderScene);renderer.render(scene, camera);}function initStats() {var stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init
</script>
</body>
</html>

自定义几何对象

threejs库中的geometry和其他大多数三维库中的一样,基本上是三维空间中的点集,以及一些将这些点连接起来的面。

举个栗子,一个方块有8个角,每个角都可以定义为x,y和z坐标的一个组合,所以每个方块都是三维空间中的8个点。在threejs库中,这些点称为顶点(vertice)。

另外,一个方块有6个侧面,每个角有一个顶点。在threejs库里,每个侧面称为面(face)。

当使用Three.js库提供的这些几何体时,你不必亲自定义所有的这些顶点和面。对于一个方块而言,你只需给出它的长宽高即可,Threejs会利用这些信息在正确的位置自动创建一个拥有8个顶点的几何体,并用正确的面连接起来。

虽然threejs库提供了很多内置的几何体对象,但是你仍然可以通过定义顶点和面,手工创建几何体。例如:

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.computeCentroids();
geom.mergeVertices();

可以写一个程序通过GUI上的滑动条来动态控制自定义几何对象的形状。

<!DOCTYPE html>
<html>
<head><title>第2话</title><script type="text/javascript" src="../libs/three.js"></script><script type="text/javascript" src="../libs/stats.js"></script><script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="Stats-output"></div>
<div id="threejs-example"></div><script type="text/javascript">function init() {var stats = initStats();var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMapEnabled = true;var planeGeometry = new THREE.PlaneGeometry(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);camera.position.x = -20;camera.position.y = 25;camera.position.z = 20;camera.lookAt(new THREE.Vector3(5, 0, 0));var spotLight = new THREE.SpotLight(0xffffff);spotLight.position.set(-40, 60, 10);spotLight.castShadow = true;scene.add(spotLight);document.getElementById("threejs-example").appendChild(renderer.domElement);var step = 0;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提供的面坐标新生成场景中第一个mesh物体var geom = new THREE.Geometry();geom.vertices = vertices;geom.faces = faces;geom.computeFaceNormals();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 (ele) {  // 为mesh实例组(由多重材质生成的多个mesh实例被打包到了一个组上)下的所有子mesh对象开启"发射阴影"效果ele.castShadow = true});scene.add(mesh);function addControl(x, y, z) {var controls = new function () {this.x = x;     // controls.x = xthis.y = y;this.z = z;};return controls;    // controls是一个对象,controls{x: x, y: y, z: z}}// 初始化controlPoints数组中的坐标内容,这是原物体的初始形状,后面可通过GUI上的滑动条进行更改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 () {  // 向GUI中添加一个clone按钮var clonedGeometry = mesh.children[0].geometry.clone(); // 调用clone()方法复制几何对象var materials = [new THREE.MeshLambertMaterial({opacity: 0.6, color: 0xff44ff, transparent: true}),new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})];// 通过复制来的几何对象再加上前面定义好的材质重新生成一个原mesh对象的克隆体// 这里是使用多重材质创建mesh物体对象(使用多个材质创建mesh对象时,其实根据材质的数量产生了多个mesh对象,只不过threejs将它们拼合成了一个组,再作为整体把它们显示出来)var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);mesh2.children.forEach(function (ele) {ele.castShadow = true});mesh2.translateX(5);    // 让克隆出来的物体相比原物体偏移一定距离mesh2.translateZ(5);mesh2.name = "clone";scene.remove(scene.getChildByName("clone"));    // 删除原来画布上有的"clone"几何对象scene.add(mesh2);}}, 'clone');for (var i = 0; i < 8; i++) {f1 = gui.addFolder('Vertices ' + (i + 1));  // 新建8个文件夹,每个文件夹各存放x,y,z三个滑动条f1.add(controlPoints[i], 'x', -10, 10);     // 每个滑动条都控制着controlPoints[i]中分量x|y|z的值,范围都是-10 -- 10f1.add(controlPoints[i], 'y', -10, 10);f1.add(controlPoints[i], 'z', -10, 10);}renderScene();function renderScene() {stats.update();var vertices = [];for (var i = 0; i < 8; i++) {               // 更新节点数组,更新节点在x,y,z方向上的坐标值vertices.push(new THREE.Vector3(controlPoints[i].x, controlPoints[i].y, controlPoints[i].z));}mesh.children.forEach(function (ele) {ele.geometry.vertices = vertices;ele.geometry.verticesNeedUpdate = true; // 允许几何对象的节点数组更新ele.geometry.computeFaceNormals();      // 重新根据更新后的节点数组计算几何对象的侧面});requestAnimationFrame(renderScene);renderer.render(scene, camera);}function initStats() {var stats = new Stats();stats.setMode(0); stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init
</script>
</body>
</html>

使用gui控制mesh对象的属性

mesh对象的属性大致有position,rotation,scale,translateX,translateY和translateZ等。

<!DOCTYPE html>
<html>
<head><title>第2话</title><script type="text/javascript" src="../libs/three.js"></script><script type="text/javascript" src="../libs/stats.js"></script><script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="Stats-output"></div>
<div id="threejs-example"></div><script type="text/javascript">function init() {var stats = initStats();var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);renderer.shadowMapEnabled = true;var planeGeometry = new THREE.PlaneGeometry(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);camera.position.x = -30;camera.position.y = 40;camera.position.z = 30;camera.lookAt(scene.position);// 添加微弱的自然光var ambientLight = new THREE.AmbientLight(0x0c0c0c);scene.add(ambientLight);// 添加聚光灯光源来生成阴影var spotLight = new THREE.SpotLight(0xffffff);spotLight.position.set(-40, 60, 20);spotLight.castShadow = true;scene.add(spotLight);document.getElementById("threejs-example").appendChild(renderer.domElement);var step = 0;// 构建controls对象的初始状态,为之后要实现的gui功能做准备var controls = new function () {this.scaleX = 1;this.scaleY = 1;this.scaleZ = 1;this.positionX = 0;this.positionY = 4;this.positionZ = 0;this.rotationX = 0;this.rotationY = 0;this.rotationZ = 0;this.scale = 1;this.translateX = 0;this.translateY = 0;this.translateZ = 0;this.visible = true;this.translate = function () {cube.translateX(controls.translateX);cube.translateY(controls.translateY);cube.translateZ(controls.translateZ);controls.positionX = cube.position.x;controls.positionY = cube.position.y;controls.positionZ = cube.position.z;}};// 向场景中添加一个初始mesh对象cubevar material = new THREE.MeshLambertMaterial({color: 0x44ff44});var geom = new THREE.BoxGeometry(5, 8, 3);var cube = new THREE.Mesh(geom, material);cube.position.y = 4;cube.castShadow = true;scene.add(cube);// 构建gui,以通过滑动条来变换物体的形状,大小,位置var gui = new dat.GUI();guiScale = gui.addFolder('scale');guiScale.add(controls, 'scaleX', 0, 5);     // 控制controls对象中的分量scaleXguiScale.add(controls, 'scaleY', 0, 5);guiScale.add(controls, 'scaleZ', 0, 5);guiPosition = gui.addFolder('position');var contorl_X = guiPosition.add(controls, 'positionX', -10, 10);var contorl_Y = guiPosition.add(controls, 'positionY', -4, 20);var contorl_Z = guiPosition.add(controls, 'positionZ', -10, 10);// 当contorl_X, contorl_Y和contorl_Z发生变化时,触发onChange()函数,将controls对象中的坐标更新为场景中物体cube的坐标位置contorl_X.listen();contorl_X.onChange(function (value) {cube.position.x = controls.positionX;});contorl_Y.listen();contorl_Y.onChange(function (value) {cube.position.y = controls.positionY;});contorl_Z.listen();contorl_Z.onChange(function (value) {cube.position.z = controls.positionZ;});guiRotation = gui.addFolder('rotation');guiRotation.add(controls, 'rotationX', -4, 4);guiRotation.add(controls, 'rotationY', -4, 4);guiRotation.add(controls, 'rotationZ', -4, 4);guiTranslate = gui.addFolder('translate');guiTranslate.add(controls, 'translateX', -10, 10);guiTranslate.add(controls, 'translateY', -10, 10);guiTranslate.add(controls, 'translateZ', -10, 10);guiTranslate.add(controls, 'translate');    // 点击'translate'按钮之后,调用translate()函数(这个函数在controls对象的内部被定义)来更新translateX, translateY, translateZ,即物体的位移值gui.add(controls, 'visible');   // gui中添加visible可选框renderScene();function renderScene() {stats.update();// 更新物体的可见状态,旋转状态和大小尺寸cube.visible = controls.visible;cube.rotation.x = controls.rotationX;cube.rotation.y = controls.rotationY;cube.rotation.z = controls.rotationZ;cube.scale.set(controls.scaleX, controls.scaleY, controls.scaleZ);requestAnimationFrame(renderScene);renderer.render(scene, camera);}function initStats() {var stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init
</script>
</body>
</html>

position属性

首先是position属性。

事实上,一个对象的位置是相对于其父对象而言的,而父对象通常是盛放mesh对象的那个外层场景scene。通过position属性,你可以设置对象的x, y, z轴坐标。
为一个对象设置位置,其实有三种方法。
1.
首先是最直观的三个分量分开设置的方法:

cube.position.x = 10;
cube.position.y = 3;
cube.position.z = 1;

也可以使用set()函数一次性设置这三个分量:

cube.position.set(10, 3, 1);

由于position属性实质上是一个THREE.Vector3对象,所以也可以使用三维向量的方法设置对象坐标:

cube.position = new THREE.Vector3(10, 3, 1)

前面说过,使用THREE.SceneUtilscreateMultiMaterialObject()函数可以利用多个材质创建一个mesh实例对象组,这个组中的所有子mesh实例的几何结构都是一样的,但材质不同。

此时,如果我们改变其中一个网格的位置,可以清晰的看到两个独立的对象,而如果我们移动这个对象组,那么他们的偏移量就是一样的。

rotation属性

通过rotation属性,可以设置对象绕坐标轴旋转的角度。

rotation属性设置的三种方法与position属性设置的三种方法相似。

cube.rotation.x = 0.5 * Math.PI;    // 设置对象绕x轴旋转的角度
cube.rotation.set(0.5 * Math.PI, 0, 0); // 使用set()函数设置对象分别绕三个坐标轴旋转的角度
cube.rotation = new THREE.Vector3(0.5 * Math.PI, 0, 0); // 使用Vector3(x, y, z)同时设置对象分别绕三个坐标轴旋转的角度

translate属性

使用translate函数也可以改变对象的位置,但和position不同,translate属性并不定义对象将要放在哪里的绝对位置,而是定义相对于当前位置对象移动的量。

假设你在场景中添加了一个球体,位置是(1,2,3),现在我们想让这个对象沿着x轴平移,使用translateX(4),那么球体现在的位置就是(5,2,3)。
threejs中的两种相机

threejs中有两种需要特别注意的相机——透视相机(PerspectiveCamera)和正交相机(OrthographicCamer),这两种相机也是3D渲染技术如OpenGL等中使用最多的相机。

<!DOCTYPE html>
<html>
<head><title>第2话</title><script type="text/javascript" src="../libs/three.js"></script><script type="text/javascript" src="../libs/stats.js"></script><script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="Stats-output"></div>
<div id="threejs-example"></div><script type="text/javascript">function init() {var stats = initStats();var scene = new THREE.Scene();// 定义场景相机的初始状态(之后camera将会通过gui控制面板被更改)var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);// 创建一个白色的平面var planeGeometry = new THREE.PlaneGeometry(180, 180);var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});var plane = new THREE.Mesh(planeGeometry, planeMaterial);plane.rotation.x = -0.5 * Math.PI;plane.position.x = 0;plane.position.y = 0;plane.position.z = 0;scene.add(plane);var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);for (var j = 0; j < (planeGeometry.parameters.height / 5); j++) {for (var i = 0; i < planeGeometry.parameters.width / 5; i++) {var rnd = Math.random() * 0.75 + 0.25;var cubeMaterial = new THREE.MeshLambertMaterial();cubeMaterial.color = new THREE.Color(rnd, 0, 0);    // cube物体的材质颜色随机(深红或浅红)var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.position.z = -((planeGeometry.parameters.height) / 2) + 2 + (j * 5);   // 将平面plane用cube方块填充满cube.position.x = -((planeGeometry.parameters.width) / 2) + 2 + (i * 5);cube.position.y = 2;scene.add(cube);}}var directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);directionalLight.position.set(-20, 40, 60);scene.add(directionalLight);var ambientLight = new THREE.AmbientLight(0x292929);scene.add(ambientLight);document.getElementById("threejs-example").appendChild(renderer.domElement);var controls = new function () {this.perspective = "Perspective";this.switchCamera = function () {if (camera instanceof THREE.PerspectiveCamera) {    // 如果此时的相机为透视相机,那么将当前场景的相机设置为正交相机camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;camera.lookAt(scene.position);this.perspective = "Orthographic";      // controls.perspective = "Orthographic"} else {camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;camera.lookAt(scene.position);this.perspective = "Perspective";}};};var gui = new dat.GUI();gui.add(controls, 'switchCamera');gui.add(controls, 'perspective').listen();  // 监听controls.perspective值的变化camera.lookAt(scene.position);renderScene();function renderScene() {stats.update();requestAnimationFrame(renderScene);renderer.render(scene, camera);}function initStats() {var stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init
</script>
</body>
</html>

可见,使用OrthographicCamera相机,渲染出来的所有方块的尺寸都一样大,没有“近大远小”的特点,物体对象与相机之间的距离不会影响渲染结果。我们在平时的使用过程中,还是要尽量使用PerspectiveCamera相机,因为它的渲染效果更接近真实世界的感受。

再来分析一下创建PerspectiveCamera和OrthographicCamera相机的构造方法,这两个相机的构造方法有些不一样:

透视相机

先来看一看THREE.PerspectiveCamera()所需要的参数:

透视相机的视椎体(场景中将被计算机渲染出来的部分):

正投影相机
再来看一看THREE.OrthographicCamera()所需要的参数:

正交相机的视方体:

改变相机的聚焦点

一般来讲,相机默认会指向场景的中心,即坐标position(0, 0, 0),但我们也可以很方便的改变相机所看的位置。如:

camera.lookAt(new THREE.Vector3(x, y, z));

如果将上一小节中的代码改动一下,可以使相机的聚焦点动态发生改变,从而使场景可视区域也发生变化。

<!DOCTYPE html>
<html>
<head><title>第2话</title><script type="text/javascript" src="../libs/three.js"></script><script type="text/javascript" src="../libs/stats.js"></script><script type="text/javascript" src="../libs/dat.gui.js"></script>
</head>
<body><div id="Stats-output"></div>
<div id="threejs-example">
</div><script type="text/javascript">function init() {var stats = initStats();var scene = new THREE.Scene();var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;var renderer = new THREE.WebGLRenderer();renderer.setClearColor(new THREE.Color(0xEEEEEE, 1.0));renderer.setSize(window.innerWidth, window.innerHeight);var planeGeometry = new THREE.PlaneGeometry(180, 180);var planeMaterial = new THREE.MeshLambertMaterial({color: 0xffffff});var plane = new THREE.Mesh(planeGeometry, planeMaterial);plane.rotation.x = -0.5 * Math.PI;plane.position.x = 0;plane.position.y = 0;plane.position.z = 0;scene.add(plane);var cubeGeometry = new THREE.BoxGeometry(4, 4, 4);var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x00ee22});for (var j = 0; j < (planeGeometry.parameters.height / 5); j++) {for (var i = 0; i < planeGeometry.parameters.width / 5; i++) {var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);cube.position.z = -((planeGeometry.parameters.height) / 2) + 2 + (j * 5);cube.position.x = -((planeGeometry.parameters.width) / 2) + 2 + (i * 5);cube.position.y = 2;scene.add(cube);}}// 添加一个loookAtMesh物体以指示相机当前的聚焦位置var lookAtGeom = new THREE.SphereGeometry(2);var lookAtMesh = new THREE.Mesh(lookAtGeom, new THREE.MeshLambertMaterial({color: 0xff0000}));scene.add(lookAtMesh);var directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);directionalLight.position.set(-20, 40, 60);scene.add(directionalLight);var ambientLight = new THREE.AmbientLight(0x292929);scene.add(ambientLight);document.getElementById("threejs-example").appendChild(renderer.domElement);var controls = new function () {this.perspective = "Perspective";this.switchCamera = function () {if (camera instanceof THREE.PerspectiveCamera) {camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerWidth / 16, window.innerHeight / 16, window.innerHeight / -16, -200, 500);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;camera.lookAt(scene.position);this.perspective = "Orthographic";} else {camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);camera.position.x = 120;camera.position.y = 60;camera.position.z = 180;camera.lookAt(scene.position);this.perspective = "Perspective";}};};var gui = new dat.GUI();gui.add(controls, 'switchCamera');gui.add(controls, 'perspective').listen();renderScene();var step = 0;function renderScene() {stats.update();step += 0.02;   // 相机移动的速度if (camera instanceof THREE.Camera) {var x = 10 + ( 100 * (Math.sin(step)));camera.lookAt(new THREE.Vector3(x, 10, 0)); // 动态改变相机的聚焦点lookAtMesh.position.copy(new THREE.Vector3(x, 10, 0));  // 改变lookAtMesh物体的坐标为此时相机的聚焦点}requestAnimationFrame(renderScene);renderer.render(scene, camera);}function initStats() {var stats = new Stats();stats.setMode(0);stats.domElement.style.position = 'absolute';stats.domElement.style.left = '0px';stats.domElement.style.top = '0px';document.getElementById("Stats-output").appendChild(stats.domElement);return stats;}}window.onload = init
</script>
</body>
</html>

第2话 Mesh对象的属性和threejs中的两种相机相关推荐

  1. Spring 让 LOB 数据操作变得简单易行,LOB 代表大对象数据,包括 BLOB 和 CLOB 两种类型

    http://www.ibm.com/developerworks/cn/java/j-lo-spring-lob/index.html 本文讲解了在 Spring 中处理 LOB 数据的原理和方法, ...

  2. java 对象拷贝属性_使用Java对两个对象的属性进行拷贝

    最近和Java的反射打交道比较多一点,可能是因为自己以后的方向是架构师的缘故吧,他们主要搞业务.我能也就搞架构,整天画一些流程图. 虽然对于只有一年实习经验的我,不知道这样是否好,但是我还是那句话,不 ...

  3. 重装系统重启后计算机属性无法打开,Win7系统计算机属性窗口无法打开的两种参考方法...

    正常情况下,直接右键计算机属性选项就可以查看有关该程序或者文件的基本信息.不过,有些Win7系统右击计算机属性时发现打不开而且还提示"此项目的属性未知",如下图所示,这该如何来处理 ...

  4. java获取jsp对象的属性_java-从jsp el中的对象获取布尔属性

    好.我真笨.否决这个问题,嘲笑我,等等.问题出在isAdmin()委托给的方法中.该方法中存在一个空指针异常.但是,在我的辩护中,我会说我所得到的堆栈跟踪有点不清楚,并使其看起来像是EL问题,而不是代 ...

  5. java 拷贝属性值_Java 反射拷贝相同的属性值到指定对象中(两种实现方式)

    范例: public class ReflectUtils { private ReflectUtils() {}; /** * 对象反射赋值 * * @param source 目标对象 * @pa ...

  6. python中类似对象吗_在Python中,两个对象什么时候相同? - python

    似乎2 is 2和3 is 3在python中始终为true,通常,对整数的任何引用都与对相同整数的任何其他引用相同. None(即None is None)也是如此.我知道用户定义类型或可变类型不会 ...

  7. 商品属性与商品之间的两种不同表设计方案

    1.先设计一个商品表: 表结构如下 商品表数据如下: 现在商品表已经构建完了,下面我们开始设计商品属性表 方案一:一个商品表关联一个商品属性表,这种设计比较简单,但可以实现商品属性的基本功能 1.设计 ...

  8. [置顶] 深入浅出Javascript(三)创建自定义对象以及属性、方法

    怎么样创建一个对象? 利用Object创建自定义对象 JavaScript能够自定义对象来扩展程序的功能,不仅如此,它还能扩展JavaScript提供的内置对象,新增内置对象的属性或方法 例如下面代码 ...

  9. JavaScript 中对象的属性类型

    对象的属性类型 JavaScript 中的对象的属性包括数据属性和访问器属性,在 JavaScript 引擎的内部实现中定义了用于描述属性(property)的特性(attribute).规范中将特性 ...

最新文章

  1. python怎么读取excel某一行某一列-python3读取excel文件只提取某些行某些列的值方法...
  2. Linux系统管理命令:date、free、ps、du、kill、uname
  3. MySQL排序优化(两次排序和单次排序)
  4. 20131127-正则表达式
  5. 【图像超分辨率论文】BasicVSR++: Improving Video Super-Resolution with Enhanced Propagation and Alignment
  6. Android 亮屏速度分析
  7. bash脚本编程入门_Bash编程入门
  8. Linux 多线程应用中如何编写安全的信号处理函数
  9. 天津消协警示“58同城” 请珍视消费者的信任和选择
  10. 文字垂直居中,水平居中 a标签水平居中只要给他的父级设置text-align=center
  11. HDU 5634 Rikka with Phi
  12. 遥感水文前景_【充电】学遥感必读的十本专业书
  13. 【计算机网络实验】DHCP报文捕获和分析
  14. 什么是ETL?ETL是什么技术?
  15. 手机关闭浏览器html,如何解除手机浏览器网页限制?
  16. python写的一个王者荣耀刷金币脚本
  17. 如何优雅高效地使用Python——这些Python技巧你必须学会!
  18. c语言字母表输出大写字母,c语言输入一个大写字母,输出字母表中它前面的字母和后面的字母.如果...
  19. 毫米波技术入局智能家居,是大材小用还是技术革命?
  20. 【微信红包封面】最新!最全!

热门文章

  1. 当你孤单你会想起谁,是不是翻遍了所有的通信录,微信和QQ好友,却找不到陪你的人?...
  2. 我的笔记本电脑瞬间扩大一个T的容量
  3. 港联证券|“牛市旗手”频遭股东减持 机构仍看好板块后市表现
  4. 信息资源管理--初见成效篇
  5. CentOS 6.5+Oracle 11.2.0.4的ADG环境搭建
  6. 目标检测——R2CNN与SCRDet
  7. (六)从零开始学人工智能-搜索:对抗搜索
  8. 图像处理(4)--基于内容的图像检索
  9. linux中的execlp函数的作用,我不明白execlp()在Linux中的工作原理
  10. 合理设置 HashMap 初始值大小