前一篇我们介绍了 Three.js 中 Mesh 网格对象的一些常用函数及属性。本篇我们将介绍 Three.js 中有关相机的概念,照例我们先上一个完整的示例,代码如下:
<!DOCTYPE html>
<html>
<head>
<title>示例 02.05 - 相机</title>
<script src="../build/three.js"></script>
<script src="../build/js/controls/OrbitControls.js"></script>
<script src="../build/js/libs/stats.min.js"></script>
<script src="../build/js/libs/dat.gui.min.js"></script>
<script src="../jquery/jquery-3.2.1.min.js"></script>
<style>
body {
/* 设置 margin 为 0,并且 overflow 为 hidden,来完成页面样式 */
margin: 0;
overflow: hidden;
}
/* 统计对象样式 */
#Stats-output {
position: absolute;
left: 0px;
top: 0px;
}
</style>
</head>
<body>
<!-- 用于 WebGL 输出的 Div -->
<div id="WebGL-output"></div>
<!-- 用于统计 FPS 输出的 Div -->
<div id="Stats-output"></div>
<!-- 运行 Three.js 示例的 Javascript 代码 -->
<script type="text/javascript">
var scene;
var camera;
var render;
var controls;
var stats;
var guiParams;
var gui;
var plane;
var mesh;
var centerSphere;
// 当所有元素加载完毕后,就执行我们 Three.js 相关的东西
$(function() {
stats = initStats();
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); // 2147483647
camera.position.set(120, 60, 180);
render = new THREE.WebGLRenderer( {antialias: true} ); // antialias 抗锯齿
render.setSize(window.innerWidth, window.innerHeight);
render.setClearColor(0xEEEEEE, 1.0);
render.shadowMap.enabled = true; // 允许阴影投射
$('#WebGL-output')[0].appendChild(render.domElement);
window.addEventListener('resize', onWindowResize, false);
var target = new THREE.Vector3(scene.position.x, scene.position.y + 10, scene.position.z);
controls = new THREE.OrbitControls(camera, render.domElement);
controls.target = target; // 解决 camera.lookAt(x, y, z) 失效问题
camera.lookAt(target);
scene.add(new THREE.AxisHelper(20));// 加入坐标轴
// 加入一个平面
var planeGeometry = new THREE.PlaneGeometry(180, 180);
var planeMaterial = new THREE.MeshLambertMaterial( {color: 0xFFFFFF} );
plane = new THREE.Mesh(planeGeometry, planeMaterial);
plane.rotation.x = -0.5 * Math.PI; // 沿着 X轴旋转-90°
plane.position.x = 0;
plane.position.y = 0;
plane.position.z = 0;
plane.receiveShadow = true; // 几何平面接收阴影
scene.add(plane);
// 为相机目标设置一个球
var centerGeometry = new THREE.SphereGeometry(2);
var centerMaterial = planeMaterial.clone();
centerSphere = new THREE.Mesh(centerGeometry, centerMaterial);
centerSphere.position.set(target.x, controls.target.y, target.z);
scene.add(centerSphere);
// 加入立方体
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);
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);
}
}
// 加入一个环境光源
var ambientLight = new THREE.AmbientLight(0x292929);
scene.add(ambientLight);
// 加入一个方向灯灯光源
// 注:基础材质 MeshBasicMaterial 不会对光源产生反应,因此要改用 MeshLambertMaterial 或 MeshPhongMaterial 材质才有效果
var directionalLight = new THREE.DirectionalLight(0xffffff, 0.7);
directionalLight.position.set(-20, 40, 60);
scene.add(directionalLight);
// 定义 gui 控制参数
guiParams = new function() {
this.switchCamera = function () {
if (camera instanceof THREE.PerspectiveCamera) {
camera = new THREE.OrthographicCamera(window.innerWidth / -16, window.innerHeight / 16,
window.innerHeight / 16, window.innerWidth / -16,
-200, 500);
guiParams.perspective = "Orthographic";
} else {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
guiParams.perspective = "Perspective";
}
var target = scene.position.clone();
controls = new THREE.OrbitControls(camera, render.domElement);
target = target;
camera.position.set(120, 60, 180);
camera.lookAt(target);
}
this.perspective = "Perspective";
}
// 定义 gui 并绑定参数
gui = new dat.GUI();
gui.add(guiParams, 'switchCamera');
gui.add(guiParams, 'perspective').listen();
renderScene();
});
/** 初始化 stats 统计对象 */
function initStats() {
stats = new Stats();
stats.setMode(0); // 0 为监测 FPS;1 为监测渲染时间
$('#Stats-output').append(stats.domElement);
return stats;
}
/** 渲染场景 */
function renderScene() {
stats.update();
centerSphere.position.set(controls.target.x, controls.target.y, controls.target.z);
requestAnimationFrame(renderScene);
render.render(scene, camera);
}
/** 当浏览器窗口大小变化时触发 */
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
render.setSize(window.innerWidth, window.innerHeight);
}
</script>
</body>
</html>
通过浏览器观察时,默认使用的相机叫透视相机,这是一种最自然的相机。正如你所看到的那样,离相机越远的方块被渲染得越小。通过右上角的 switchCamera 工具,我们可以来在 Three.js 支持的两种相机中回切换。Three.js 中的另一种相机叫正投影相机,在这种相机下,场景中所有的方块渲染出来的尺寸看起来都一样大,对象和相机之间的距离不会影响物体的渲染结果。这种相机通常用在二维游戏或 2.5D 地图中。
我们先来看看透视相机 PerspectiveCamera,它的常用参数如下表所示:
参数 | 描述 |
---|---|
fov (视野) | fov 表示视野(field of view)。这是从相机位置能够看到的部分场景。例如,人类有差不多180°的视野;而一些鸟类拥有差不多完整的360°的视野; 但是由于计算机显示器不能完全显示我们所看到的景象,所以一般会选择较小的区域。多数游戏会选择60°到90°左右的视野。推荐默认值:45° |
aspect (宽高比) | 这是渲染结果输出区的横向宽度与纵向高度的比值。我们的例子中使用了整个窗口作为输出界面。推荐默认值:window.innerWidth / window.innerHeight |
near (近面) | near 属性定义的是从距离相机多近的地方开始渲染场景。一般情况下我们会为这个属性设置一个很小的值,从而可以渲染从相机位置可以 看到的几乎所有的物体。推荐默认值:0.1 |
far (远面) | far 属性定义的是相机可以从它所处的位置看多远。如果我们把这个属性设置得太低,那么场景中的一部分可能不会被渲染;如果太高,在某些情况 下又会影响渲染的效率。推荐默认值:1000 |
接下来我们看看正投影相机 OrthographicCamera,它的常用参数如下表所示:
参数 | 描述 |
---|---|
left (左边界) | 这个属性是可视范围的左平面。可以把它当做是可渲染部分的左侧面边界。如果把它设为 -100,那么就不会看到任何比这个左侧面更远的对象 |
right (右边界) | 跟 left 属性同理 |
top (上边界) | 跟 left 属性同理 |
bottom (下边界) | 跟 left 属性同理 |
near (近面) | 基于相机所在的位置,从这一点开始渲染场景 |
far (远面) | 基于相机所在的位置,场景一直渲染到这一点 |
一般情况下,在没有明确指出相机的目标时,相机会指向场景的中心,用坐标表示就是 position(0, 0, 0)。但我们也可以轻松改变相机所看到的目标位置,即通过执行 camera.lookAt(x, y, z); 代码来达到目的。我们的示例中则使用了另一种写法,即:camera.lookAt(new THREE.Vector3(x, y, z)); 同样可以实现相同的目的。另外,我们还在场景中央添加了一个球体,来始终指向相机的目标。
未完待续···