添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

首先从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()(更新父子元素的模型或世界空间变换矩阵)等
  • 这次就先到这里,下次分析下继承自Object3D的其他上层对象。

  • mrdoob/three.js: JavaScript 3D library
  •