深入学习Three.js核心对象之(一)Object3D详解[转载]
从底层对象开始,看看Threejs如何利用图形学知识,通过各种数据对象构建场景,最终通过渲染器绘制出来。
先来看看最基础的Object3D对象,内容包含:
- 官方demo引入: 主要对象分析
- Object3D的属性: 位置、欧拉角、四元数、变换矩阵等
- Object3D的变换: 以世界空间或模型空间为参考系的基础变换
本文所参考的Three.js版本为0.116.1
系列文章
- 深入学习Three.js核心对象之(一)Object3D
- 深入学习Three.js核心对象之(二)Geometry
- 深入学习Three.js核心对象之(三)Material
一个例子
首先从Three.js官方README的示例入手,看下它所使用了哪些对象。
import * as THREE from 'js/three.module.js';
var camera, scene, renderer;
var geometry, material, mesh;
init();
animate();
function init() {
// 创建一个透视相机,定义视口比例、近裁面与远裁面
camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 0.01, 10 );
camera.position.z = 1;
// 创建一个场景对象,存储需要渲染的模型、灯光等物体
scene = new THREE.Scene();
// 创建模型所需的几何体与材质属性
geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
material = new THREE.MeshNormalMaterial();
mesh = new THREE.Mesh( geometry, material );
scene.add( mesh );
// 初始化渲染器,导出dom元素,为渲染做准备
renderer = new THREE.WebGLRenderer( { antialias: true } );
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );
}
function animate() {
requestAnimationFrame( animate );
// 修改欧拉角的值来实现旋转
mesh.rotation.x += 0.01;2
mesh.rotation.y += 0.02;
// 传入场景(世界空间)与相机(视口空间)执行渲染
renderer.render( scene, camera );
}
看看上面所用的这些对象的内部继承关系
- PerspectiveCamera => Camera => Object3D
- Scene => Object3D
- BoxGeometry => Geometry
- MeshNormalMaterial => Meterial
- Mesh => Object3D
可以看出,不管是交给render函数用来渲染的对象(scene, camera),还是在scene中添加的对象(mesh),本质上都是Object3D对象,下面来简单看下Object3D中都设置了哪些属性与方法。
Object3D
Object3D为框架中最核心的类,相机、模型等多个上层对象都继承自该类。它的属性与方法均具有一定的通用性,如空间变换、特性处理、矩阵计算等等。
主要属性
- 数据相关: type(类型字符串)、uuid(唯一ID)、parent、children
- 位置与变换相关: position(位置向量)、scale(缩放向量)、rotation(欧拉角)、quaternion(四元数)、up(上方向向量,用于lookAt时确定旋转姿态朝向的唯一性)、
- 特性开关与自定义数据相关: visible(可视性)、castShadow&receiveShadow(产生与接收阴影)、frustumCulled(视锥剔除)、renderOrder(渲染优先级)、userData(用户自定义数据)
- 矩阵相关: matrix(模型空间)、matrixWorld(世界空间)、modelViewMatrix(模型视图矩阵,用于shader中的位置计算)、normalMatrix(法向矩阵,用于shader中的光照计算,可通过模型视图矩阵计算得出)
变换
旋转
Object3D对象提供旋转方法与修改旋转属性值两种方式来执行旋转操作:
- 旋转方法:使用如
mesh.rotateX(Math.PI/2)
这样的旋转方法进行旋转,其内部的操作是构建新的四元数与当前姿态的四元数做乘法运算,物体当前姿态对应的四元数属性对象为quaternion
。 - 修改旋转属性值:上面例子中的
mesh.rotation.x += 0.01
即为这种方式,即通过改变欧拉角的值来实现旋转,对应操作的属性对象为rotation
,其中默认旋转顺规为XYZ(泰特布莱恩角),参考坐标系为世界坐标系(外旋)。
关于四元数与欧拉角可以看下之前的简单总结: 欧拉角、万向节死锁与四元数。
Object3D的rotate相关方法,本质上是利用轴角+四元数的方式处理旋转,它提供了预置常规旋转轴单位向量的rotateX、rotateY等方便调用的方法,如下所示:
// src/core/Object3D.js
var _xAxis = new Vector3( 1, 0, 0 );
function Object3D() {
rotateX: function ( angle ) {
// 将内置的X旋转轴(模型空间)与指定角度传入按轴旋转方法
return this.rotateOnAxis( _xAxis, angle );
},
// 通用轴角(axis-angle)旋转方法
rotateOnAxis: function ( axis, angle ) {
// axis为模型空间的旋转轴(应为单位向量),angle为旋转角度
_q1.setFromAxisAngle( axis, angle );
// 将构建的四元数与之前的四元数相乘
this.quaternion.multiply( _q1 );
return this;
},
quaternion._onChange( onQuaternionChange );
function onQuaternionChange() {
// 模型四元数变化的同时更新欧拉角,同样当欧拉角变化时也会更新四元数
rotation.setFromQuaternion( quaternion, undefined, false );
}
}
还有一点众所周知的是,使用四元数处理旋转可以避免欧拉角的万向节死锁问题。
为了直观的体现这两种方式的不同,看看下面这个例子:如果有两个方块分别执行了下面两种旋转,想象一下渲染出来会是什么结果?
- 绿色方块使用内置方法执行旋转:
cube.rotateY((45 * Math.PI) / 180); cube.rotateX((90 * Math.PI) / 180);
- 蓝色方块修改rotation属性执行旋转:
cube.rotation.y += (45 * Math.PI) / 180; cube.rotation.x += (90 * Math.PI) / 180;
结果:
图中坐标轴及颜色:x(红),y(绿), z(蓝)。背景的辅助线为世界坐标系,方块上的为各自的物体坐标系。
看看跟你想的是否一致,产生这样效果的原因是参考系不同:使用旋转方法时,内部会根据物体空间中的轴进行旋转,而rotation欧拉角的值则会在世界空间中进行处理。
使用xyz表示世界空间,XYZ表示物体空间。实际上两种操作是:
- 绿色方块:先绕Y轴旋转45度(Y轴初始方向与y轴重合,旋转后XZ的方向发生了变化),再绕X轴旋转90度(绕自身的X轴旋转90度看起来没有变化)
- 蓝色方块:先绕y轴旋转45度,再绕x轴旋转90度
下面来看看剩下两种变换:平移和缩放
平移
平移与旋转类似,既可以使用内置了参考轴的方法来执行平移操作,也可以通过修改操position
位置向量的值来实现。
其中在物体空间平移的方法中利用四元数还原旋转姿态再进行平移:
// src/core/Object3D.js
var _xAxis = new Vector3( 1, 0, 0 );
function Object3D() {
translateX: function ( distance ) {
// 将内置的X旋转轴(模型空间)与平移距离传入按轴平移方法
return this.translateOnAxis( _xAxis, distance );
},
// 通用按轴平移方法
translateOnAxis: function ( axis, distance ) {
// 与轴角旋转方法同理,axis为模型空间的旋转轴(应为单位向量)
// 根据旋转轴与旋转姿态计算平移朝向的方向向量
_v1.copy( axis ).applyQuaternion( this.quaternion );
// 将方向向量乘以位移值并与position向量相加完成平移
this.position.add( _v1.multiplyScalar( distance ) );
return this;
}
}
在平移的实现中会①先按照参考轴与当前姿态四元数计算物体朝向的方向向量,②再乘以距离并与原始位置相加得到最终平移的位置。
这里也给出一个例子:有两个方块先执行了一次旋转,接着通过两种方式进行了平移:
- 绿色方块使用内置方法执行旋转:
cube.rotateX((-45 * Math.PI) / 180); cube.translateZ(3); cube.translateX(3);
- 蓝色方块修改rotation属性执行旋转:
cube.rotateX((-45 * Math.PI) / 180); cube.position.z += 3; cube.position.x += 3;
结果:
缩放
Object3D中没有提供缩放操作的方法,仅能通过修改scale
向量的属性值来实现。
cube.scale.copy(new Vector3(2,1,1));
// 或 cube.scale.x = 2;
其他
除了主要属性与变换相关方法之外,还包含一些其他方法:
- 数据操作方法: add()、remove()、attach()、getObjectById()、getWorldPosition()等
- 通用方法: copy()、clone()、toJSON()等
- 变换矩阵更新方法: updateMatrix()(更新模型空间变换矩阵)、updateMatrixWorld()(更新世界空间变换矩阵)、updateWorldMatrix()(更新父子元素的模型或世界空间变换矩阵)等
这次就先到这里,下一步分析下构成模型的Geometry与Material对象。
参考
- 原文作者:yrq110
- 原文链接:http://yrq110.me/post/front-end/deep-in-threejs-core-objects-i-object3d/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。
WEBGL学习网 » 深入学习Three.js核心对象之(一)Object3D详解[转载]