构建一个"向场景中添加方块"的功能
这主要要在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;
// 新建一个平面plane
var 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 = x
this.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 -- 10
f1.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对象cube
var 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对象中的分量scaleX
guiScale.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>