照相机又分为正交投影照相机与透视投影照相机
举个简单的例子来说明正交投影与透视投影照相机的区别。使用透视投影照相机获得的结果是类似人眼在真实世界中看到的有“近大远小”的效果(如下图中的(a));
而使用正交投影照相机获得的结果就像我们在数学几何学课上老师教我们画的效果,对于在三维空间内平行的线,投影到二维空间中也一定是平行的(如下图中的(b))。
(a)透视投影,(b)正交投影
那么,你的程序需要正交投影还是透视投影的照相机呢?
一般说来,对于制图、建模软件通常使用正交投影,这样不会因为投影而改变物体比例;而对于其他大多数应用,通常使用透视投影,因为这更接近人眼的观察效果。当然,照相机的选择并没有对错之分,你可以更具应用的特性,选择一个效果更佳的照相机。
正交投影照相机(Orthographic Camera)设置起来较为直观,它的构造函数是:
THREE.OrthographicCamera(left, right, top, bottom, near, far)
这六个参数分别代表正交投影照相机拍摄到的空间的六个面的位置,这六个面围成一个长方体,我们称其为视景体(Frustum)。只有在视景体内部(下图中的灰色部分)的物体才可能显示在屏幕上,而视景体外的物体会在显示之前被裁减掉。
为了保持照相机的横竖比例,需要保证(right - left)
与(top - bottom)
的比例与Canvas宽度与高度的比例一致。
near
与far
都是指到照相机位置在深度平面的位置,而照相机不应该拍摄到其后方的物体,因此这两个值应该均为正值。为了保证场景中的物体不会因为太近或太远而被照相机忽略,一般near
的值设置得较小,far
的值设置得较大,具体值视场景中物体的位置等决定。
实例说明
下面,我们通过一个具体的例子来解释正交投影照相机的设置。
基本设置
设置照相机:
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10); camera.position.set(0, 0, 5); scene.add(camera);
在原点处创建一个边长为1
的正方体,为了和透视效果做对比,这里我们使用wireframe
而不是实心的材质,以便看到正方体后方的边:
var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }) ); scene.add(cube);
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html> <head> <script type="text/javascript" src="libs/three.js"></script> <script type="text/javascript"> function init() { var renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('mainCanvas') }); renderer.setClearColor(0x000000);
var scene = new THREE.Scene(); // camera // canvas size is 400x300 var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10); camera.position.set(0, 0, 5); //camera.lookAt(new THREE.Vector3(0, 0, 0))通过lookAt函数指定它看着原点方向,这样我们就能过仰望正方体了;
//注意,lookAt函数接受的是一个THREE.Vector3的实例,因此千万别写成camera.lookAt(0, 0, 0),否则非但不能得到理想的效果,而且不会报错,使你很难找到问题所在。
scene.add(camera); // a cube in the scene var cube = new THREE.Mesh(new THREE.CubeGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true }) ); scene.add(cube); // render renderer.render(scene, camera); } </script> </head> <body onload="init()"> <canvas id="mainCanvas" width="400px" height="300px" ></canvas> </body> </html>
得到的效果是:
我们看到正交投影的结果是一个正方形,后面的边与前面完全重合了,这也就是正交投影与透视投影的区别所在。
长宽比例
这里,我们的Canvas宽度是400px
,高度是300px
,照相机水平方向距离4
,垂直方向距离3
,因此长宽比例保持不变。为了试验长宽比例变化时的效果,我们将照相机水平方向的距离减小为2
:
var camera = new THREE.OrthographicCamera(-1, 1, 1.5, -1.5, 1, 10);
得到的结果是水平方向被拉长了:
照相机位置
接下来,我们来看看照相机位置对渲染结果的影响。在之前的例子中,我们将照相机设置在(0, 0, 5)
位置,而由于照相机默认是面向z轴负方向放置的,所以能看到在原点处的正方体。现在,如果我们将照相机向右移动1
个单位:
var camera = new THREE.OrthographicCamera(-2, 2, 1.5, -1.5, 1, 10); camera.position.set(1, 0, 5);
得到的效果是物体看上去向左移动了:
仔细想一下的话,这也不难理解。就好比你人往右站了,看起来物体就相对往左移动了。
那么,正交投影照相机在设置时,是否需要保证left
和right
是相反数呢?如果不是,那么会产生什么效果呢?下面,我们将原本的参数(-2, 2, 1.5, -1.5, 1, 10)
改为(-1, 3, 1.5, -1.5, 1, 10)
,即,将视景体设置得更靠右:
var camera = new THREE.OrthographicCamera(-1, 3, 1.5, -1.5, 1, 10); camera.position.set(0, 0, 5);
得到的结果是:
细心的读者已经发现,这与之前向右移动照相机得到的效果是等价的。
换个角度看世界
到现在为止,我们使用照相机都是沿z轴负方向观察的,因此看到的都是一个正方形。现在,我们想尝试一下仰望这个正方体。我们已经学会设置照相机的位置,不妨将其设置在(4, -3, 5)
处:
camera.position.set(4, -3, 5);
但是现在照相机沿z轴负方向观察的,因此观察不到正方体,只看到一片黑。我们可以通过lookAt
函数指定它看着原点方向:
camera.lookAt(new THREE.Vector3(0, 0, 0));
这样我们就能过仰望正方体啦:
不过一定要注意,lookAt
函数接受的是一个THREE.Vector3
的实例,因此千万别写成camera.lookAt(0, 0, 0)
,否则非但不能得到理想的效果,而且不会报错,使你很难找到问题所在。
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Three框架</title> <script src="libs/Three.js"></script> <style type="text/css"> div#canvas-frame { border: none; cursor: pointer; width: 100%; height: 600px; background-color: #EEEEEE; } </style> <script> var renderer; function initThree() { width = document.getElementById('canvas-frame').clientWidth; height = document.getElementById('canvas-frame').clientHeight; renderer = new THREE.WebGLRenderer({ antialias : true }); renderer.setSize(width, height); document.getElementById('canvas-frame').appendChild(renderer.domElement); renderer.setClearColor(0xFFFFFF, 1.0); } var camera; function initCamera() { camera = new THREE.PerspectiveCamera(45, width / height, 1, 10000); //camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, 10, 1000 ); camera.position.x = 0; camera.position.y = 0; camera.position.z = 600; camera.up.x = 0; camera.up.y = 1; camera.up.z = 0; camera.lookAt({ x : 0, y : 0, z : 0 }); } var scene; function initScene() { scene = new THREE.Scene(); } var light; function initLight() { light = new THREE.AmbientLight(0xFF0000); light.position.set(100, 100, 200); scene.add(light); light = new THREE.PointLight(0x00FF00); light.position.set(0, 0,300); scene.add(light); } var cube; function initObject() { var geometry = new THREE.CylinderGeometry( 70,100,200); var material = new THREE.MeshLambertMaterial( { color:0xFFFFFF} ); var mesh = new THREE.Mesh( geometry,material); mesh.position = new THREE.Vector3(0,0,0); scene.add(mesh); } function threeStart() { initThree(); initCamera(); initScene(); initLight(); initObject(); animation(); } function animation() { changeFov(); renderer.render(scene, camera); requestAnimationFrame(animation); } function setCameraFov(fov) { camera.fov = fov; camera.updateProjectionMatrix(); } function changeFov() { var txtFov = document.getElementById("txtFov").value; var val = parseFloat(txtFov); setCameraFov(val); } </script> </head> <body onload="threeStart();"> <div id="canvas-frame"></div> <div> Fov:<input type="text" value="45" id="txtFov"/>(0到180的值) </div> </body> </html>