本节将在上一节的基础上进一步介绍一下摄像机功能。
three.js的摄像机主要包括两类:正交投影摄像机和透视投影摄像机。
- 透视投影摄像机:THREE.PerspectiveCamera,最自然的视图,距离摄像机越远,它们就会被渲染得越小。
- 正交投影摄像机:THREE.OrthographicCamera,所有的立方体被渲染出来的尺寸都是一样的。
- VR摄像机:THREE.StereoCamera将左右眼画面并排渲染,或者通过WebVR等。
效果图
源码
引入的插件js【本人的csdn也有下载资源,如果打不开git可以在csdn下载】:
- three.js
- dat.gui.js
- Stats.js
- TrackballControls.js
- SceneUtils.js
- util.js
准备工作(先克隆几个不同颜色的网格)
添加一个gui方法,作用是能够克隆几个不同颜色和相对位置的网格。现在我们可以点击页面右上角的clone方法添加不同的立方体了。
var gui = new dat.GUI();
var cloneIndex = 0
gui.add(new function () {
this.clone = function () {
cloneIndex += 3
var clonedGeometry = mesh.children[0].geometry.clone();
var materials = [
new THREE.MeshLambertMaterial({opacity: 0.8, color: Math.random() * 0xffffff, transparent: true}),
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})
];
var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
mesh2.children.forEach(function (e) {
e.castShadow = true
});
mesh2.translateX(cloneIndex);
mesh2.translateZ(-cloneIndex);
mesh2.name = "clone" + cloneIndex;
scene.remove(scene.getChildByName("clone"));
scene.add(mesh2);
}
}, 'clone');
注意点:
- 使用translate()方法你可以改变对象的位置,但是该方法设置的不是物体的绝对位置,而是物体相对于当前位置的平移距离。
- Math.random() * 0xffffff:方法是实现颜色随机数而已。
透视投影摄像机
这种摄像机的效果更贴近真实世界,摄像机的fov属性决定了横向视场。基于aspect属性,纵向视场也就相应地确定了。near属性决定了近面距离,far属性决定了远面距离。近面距离和远面距离之间的区域将会被渲染。
正交投影摄像机
由于正交投影摄像机渲染出的物体大小都是一样的,所以它并不关心使用什么长宽比,或者以什么样的视角来观察场景。当使用正交投影摄像机时,你要定义的是一个需要被渲染的方块区域。
摄像机聚焦
- 在之前我们通常通过方法
camera.lookAt(scene.position)
方法让摄像机聚焦到场景的中心 - 现在我们可以使用
camera.lookAt(new THREE.Vector3(x, y, z));
来支持指定一个三维坐标点,让摄像机聚焦。 - 当然你也可以指定某个特殊的网格,让摄像机去聚焦在这一网格上:
camera.lookAt(mesh.position)
,这样在网格位置变化过程中,摄像机会跟随网格的坐标变化而去追踪它。
源码
4.js
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(0x000000));
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = 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 = -80;
camera.position.y = 100;
camera.position.z = 240;
camera.lookAt(scene.position);
// 设置8个顶点
var vertices = [
new THREE.Vector3(1,1,1),
new THREE.Vector3(1,5,1),
new THREE.Vector3(1,1,5),
new THREE.Vector3(1,5,5),
new THREE.Vector3(5,1,1),
new THREE.Vector3(5,5,1),
new THREE.Vector3(5,1,5),
new THREE.Vector3(5,5,5)
]
// 构建立方体所需要的十二个三角形平面
var faces = [
new THREE.Face3(0,2,1),
new THREE.Face3(2,3,1),
new THREE.Face3(7,3,2),
new THREE.Face3(7,2,6),
new THREE.Face3(5,7,6),
new THREE.Face3(4,5,6),
new THREE.Face3(1,5,0),
new THREE.Face3(0,5,4),
new THREE.Face3(5,1,3),
new THREE.Face3(5,3,7),
new THREE.Face3(2,0,4),
new THREE.Face3(2,4,6)
]
var geom = new THREE.Geometry()
geom.vertices = vertices
geom.faces = faces
geom.computeFaceNormals()
// 设置两种材质,这样方便同时看颜色和骨架
var materials = [
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true}),
new THREE.MeshLambertMaterial({opacity: 0.6, color: 0x44ff44, transparent: true})
];
// materials这样做的原因是,除了显示绿色透明的立方体外,我还想显示一个线框。因为使用线框可以很容易地找出顶点和面的位置。
// SceneUtils是SceneUtils里的方法
var mesh = THREE.SceneUtils.createMultiMaterialObject(geom, materials);
mesh.castShadow = true;
mesh.children.forEach(function (e) {
e.castShadow = true
});
scene.add(mesh);
var ambientLight = new THREE.AmbientLight(0x3c3c3c);
scene.add(ambientLight);
var spotLight = new THREE.SpotLight(0xffffff, 1.2, 150, 120);
spotLight.position.set(-40, 60, -10);
spotLight.castShadow = true;
scene.add(spotLight);
document.getElementById("webgl-output").appendChild(renderer.domElement);
var trackballControls = initTrackballControls(camera, renderer);
var clock = new THREE.Clock();
var gui = new dat.GUI();
var cloneIndex = 0
gui.add(new function () {
this.clone = function () {
cloneIndex += 3
var clonedGeometry = mesh.children[0].geometry.clone();
var materials = [
new THREE.MeshLambertMaterial({opacity: 0.8, color: Math.random() * 0xffffff, transparent: true}),
new THREE.MeshBasicMaterial({color: 0x000000, wireframe: true})
];
var mesh2 = THREE.SceneUtils.createMultiMaterialObject(clonedGeometry, materials);
mesh2.children.forEach(function (e) {
e.castShadow = true
});
mesh2.translateX(cloneIndex);
mesh2.translateZ(-cloneIndex);
mesh2.name = "clone" + cloneIndex;
scene.remove(scene.getChildByName("clone"));
scene.add(mesh2);
}
}, 'clone');
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 = -80;
camera.position.y = 100;
camera.position.z = 240;
camera.lookAt(scene.position);
trackballControls = initTrackballControls(camera, renderer);
this.perspective = "Orthographic";
} else {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.x = -80;
camera.position.y = 100;
camera.position.z = 240;
camera.lookAt(scene.position);
trackballControls = initTrackballControls(camera, renderer);
this.perspective = "Perspective";
}
};
};
gui.add(controls, 'switchCamera');
gui.add(controls, 'perspective').listen();
render();
var step = 0
function render() {
trackballControls.update(clock.getDelta());
stats.update();
if (camera instanceof THREE.Camera) {
step += 0.02;
var x = 10 + ( 100 * (Math.sin(step)));
camera.lookAt(new THREE.Vector3(x, 10, 0));
}
requestAnimationFrame(render);
renderer.render(scene, camera);
}