Three.js 正投影和透视投影

发布于 2021-12-18 19:32:49 字数 6539 浏览 1544 评论 0

针对不同应用的三维场景需要使用不同的投影方式,比如机械、工业设计领域常常采用正投影(平行投影),游戏场景往往采用透视投影(中心投影)。为了完成三维场景不同的投影方式,three.js封装WebGL API和相关算法,提供了OrthographicCamera、PerspectiveCamera等相机对象。

正投影与透视投影

生活中的物体都是三维的,但是人的眼睛只能看到正面,不能看到被遮挡的背面,三维几何体在人眼睛中的效果就像一张相机拍摄的二维照片,你看到的是一个2D的投影图。

空间几何体转化为一个二维图的过程就是投影,不同的投影方式意味着不同的算法。

对于正投影而言,一条直线放置的角度不同,投影在投影面上面的长短不同;对于透视投影而言,投影的结果除了与几何体的角度有关,还和距离相关,人的眼睛观察世界就是透视投影,比如你观察一条铁路距离越远你会感到两条轨道之间的宽度越小。

无论正投影还是透视投影,three.js都对相关的投影算法进行了封装,大家只需要根据不同的应用场景自行选择不同的投影方式。使用OrthographicCamera相机对象的时候,three.js会按照正投影算法自动计算几何体的投影结果;

使用PerspectiveCamera相机对象的时候,three.js会按照透视投影算法自动计算几何体的投影结果。

正投影相机对象 OrthographicCamera

构造函数格式:OrthographicCamera( left, right, top, bottom, near, far )

OrthographicCamera 构造函数参数列表,参数的数据类型都是 number。

参数含义默认值
left渲染空间的左边界
right渲染空间的右边界
top渲染空间的上边界
bottom渲染空间的下边界
nearnear属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。0.1
farfar属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到1000

三维场景中坐标值不在三维空间中的网格模型不会被渲染出来,会被剪裁掉,比如你把上面代码中far参数的值从1000更改为420,你会发现长方体的一部分无法显示。

注意

左右边界的距离与上下边界的距离比值与画布的渲染窗口的宽高比例要一致,否则三维模型的显示效果会被单方向不等比例拉伸

OrthographicCamera 构造函数本质上是对WebGL投影矩阵的封装,宽度width、高度height越大,三维模型顶点的位置坐标就会越大,超出可视区域的网格模型就会被剪裁掉,不会再显示在屏幕上,大家还可以看到参数left与right、参数式top与bottom互为相反数,这样做的目的是能够是lookAt指向的对象能够显示在canvas画布的中间位置。

three.js正投影

/**
 * 相机设置(正投影)
 */
var width = window.innerWidth;//窗口宽度
var height = window.innerHeight;//窗口高度
var k = width/height;//窗口宽高比
var s = 100;//三维场景缩放系数
/**正投影相机对象*/
var camera=new THREE.OrthographicCamera(-s*k,s*k, s,-s,1,1000);
camera.position.set(200,300,200);//设置相机位置
camera.lookAt(scene.position);//设置相机方向(指向的场景对象)

透视投影相机对象 PerspectiveCamera

构造函数格式:PerspectiveCamera( fov, aspect, near, far )

PerspectiveCamera 构造函数参数列表,参数的数据类型都是 number。

参数含义默认值
fovfov表示视场,所谓视场就是能够看到的角度范围,人的眼睛大约能够看到180度的视场,视角大小设置要根据具体应用,一般游戏会设置60~90度45
aspectaspect表示渲染窗口的长宽比,如果一个网页上只有一个全屏的canvas画布且画布上只有一个窗口,那么aspect的值就是网页窗口客户区的宽高比window.innerWidth/window.innerHeight
nearnear属性表示的是从距离相机多远的位置开始渲染,一般情况会设置一个很小的值。0.1
farfar属性表示的是距离相机多远的位置截止渲染,如果设置的值偏小小,会有部分场景看不到1000

three.js透视投影

  /**
 * 相机设置(透视投影)
 */
var width = window.innerWidth;//窗口宽度
var height = window.innerHeight;//窗口高度
/**透视投影相机对象*/
var camera=new THREE.PerspectiveCamera(45, width/height, 1, 1000);
camera.position.set(100,200,200);//设置相机位置
camera.lookAt(scene.position);//设置相机方向(指向的场景对象)

camera 对象的基类是 Object3D,具有 posiiotn 属性,通过 position 属性设置相机的位置。
lookAt 方法用来指定相机拍摄对象的坐标位置,lookAt 方法的参数是 Vector3 对象,可以手动定义 new THREE.Vector3(x,y,z),实际开发的时候,你希望相机对准那个对象,就返回那个对象的位置属性值,比如上面代码中的 scene.position,就表示返回scene的位置坐标,如果把scene换成网格模型对象就是mesh.position,上面的网格模型是一个立方体,具体的position属性值就是立方体的几何中心。通过观察点的位置和lookAt方法指向的位置就可以计算出相机的拍摄角度。

对于的透视投影,相机位置与 lookAt 指向的观察目标位置越小,场景中的三维模型放大倍数越大,同时超出的部分会被剪裁掉,比如更改上面代码 camera.position.set(100,200,200);为(20,20,20),测试结果你会发现立方体几何体放大显示,超出区域被剪裁。

three.js相机对象

如果是观察一个产品外观效果,相机就位于几何体的外面,如果是室内漫游预览,就把相机放在房间三维模型的内部。

第一人称相机控件

第一章讲解了一个利用轨道相机控件 OrbitControls.js 实现了三维模型操作的案例,通过鼠标或键盘可以实时缩放、旋转三维模型。这里三维模型的旋转本质上是相机对象的旋转,OrbitControls.js 控件的作用就是控制浏览器实时监控鼠标或键盘触发事件,比如鼠标左键发生拖动,拖动的距离就会改变相机的位置和拍摄角度,相机的数据会随着鼠标或键盘事件实时更新,相机的位置、角度、渲染空间数据更新,three.js渲染器render根据新的相机对象参数渲染出来的图像就会变化。

three.js 提供了多种相机控件,不同的相机控件满足不同的应用场景,当然你也可以在three.js相机对象的基础上结合HTML的鼠标事件自定义一个相机控件。

下面的代码是借助 FirstPersonControls.js 控件实现三维空间中的漫游,通过鼠标移动可以调整视角方向,通过方向键可以实现前后左右移动,WASD键和方向键功能一样。

操作功能
鼠标移动调整视角
上、下、左、右、方向键前、后、左、右移动
W、S、A、D键前、后、左、右移动
R、F键上、下移动
Q键停止

引入控件

可以在下载的 three.js-master 文件中找到 FirstPersonControls.js 控件,路径是 three.js-master\examples\js\controls。

  <!--引入第一人称相机控件FirstPersonControls.js-->
<script src="FirstPersonControls.js"></script>

创建控件对象

参考第一章中相机控件 OrbitControls.js 的使用方法。每一次调用 render 函数执行 controls.update(T); 语句的时候,下面代码中lookSpeed、movementSpeed 的属性值会与事件T相乘得到具体的角度或位置参数,得到的数据用来更新相机对象carmera的位置、角度参数。

  /**第一人称相机控件对象*/
var controls = new THREE.FirstPersonControls(camera);
controls.lookSpeed = 0.1;//视角变化速度
controls.movementSpeed = 100;//前后左右平移速度

设置时钟对象

three.js 引擎提供的时钟对象Clock实际上是对 JavaScript 语言 Date 对象的封装,然后与 requestAnimationFrame() 方法配合可以实现动画。

Clock 主要功能就是我为了计算时间,getDelta()是时钟对象的一个方法,执行该方法可以返回一个时间差值。

/**
 * 创建时钟对象(注意是全局变量)
 * 获取上次调用render()函数的时间
 */
var clock = new THREE.Clock();

render 函数更新数据

执行语句 clock.getDelta(); 返回的结果是两次调用render函数的时间差值,语句 controls.update(T); 的作用就是利用得到的时间差与相机控件对象的速度参数相乘来更新相机对象的数据。

  function render() {
  .....
  .....
  var T = clock.getDelta();//返回两次调用render函数的时间间隔
  controls.update(T);//更新相机对象位置角度参数
}
render();

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

灵芸

每个人心里都住着一个人,或眷念,或暗恋,或想念。

0 文章
0 评论
23714 人气
更多

推荐作者

qq_aHcEbj

文章 0 评论 0

寄与心

文章 0 评论 0

13545243122

文章 0 评论 0

流星番茄

文章 0 评论 0

春庭雪

文章 0 评论 0

潮男不是我

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文