Three.js源码阅读笔记(Object3D类)

这是Three.js源码阅读笔记的第二篇,直接开始。
Core::Object3D
Object3D似乎是Three.js框架中最重要的类,相当一部分其他的类都是继承自Object3D类,比如场景类、几何形体类、相机类、光照类等等:他们都是3D空间中的对象,所以称为Object3D类。Object3D构造函数如下:


代码如下:

THREE.Object3D = function () {
THREE.Object3DLibrary.push( this );
this.id = THREE.Object3DIdCount ++;
this.name = '';
this.properties = {};
this.parent = undefined;
this.children = [];
this.up = new THREE.Vector3( 0, 1, 0 );
this.position = new THREE.Vector3();
this.rotation = new THREE.Vector3();
this.eulerOrder = THREE.Object3D.defaultEulerOrder;
this.scale = new THREE.Vector3( 1, 1, 1 );
this.renderDepth = null;
this.rotationAutoUpdate = true;
this.matrix = new THREE.Matrix4();
this.matrixWorld = new THREE.Matrix4();
this.matrixRotationWorld = new THREE.Matrix4();
this.matrixAutoUpdate = true;
this.matrixWorldNeedsUpdate = true;
this.quaternion = new THREE.Quaternion();
this.useQuaternion = false;
this.boundRadius = 0.0;
this.boundRadiusScale = 1.0;
this.visible = true;
this.castShadow = false;
this.receiveShadow = false;
this.frustumCulled = true;
this._vector = new THREE.Vector3();
};

在介绍函数之前,需要先介绍一下这个类的几个重要属性。
属性parent和children说明,通常需要使用树来管理众多Object3D对象。比如一辆行驶的汽车是一个Object3D对象,控制汽车行驶路线的逻辑在该对象内部实现,汽车的每个顶点经过模型矩阵的处理后,都位于正确的位置;但是汽车摆动的雨刮器,其不但随着汽车行驶方向运动,而且自身相对汽车也在左右摆动,这个摆动的逻辑无法在汽车这个对象内部的实现。解决的方法是,将雨刮器设定为汽车的chidren,雨刮器内部的逻辑只负责其相对于汽车的摆动。在这种树状结构下,一个场景Scene实际上就是最顶端的Object3D,它的模型矩阵就是视图矩阵(取决于相机)的逆矩阵。

属性matrix和matrixWorld就很好理解了,matrix表示本地的模型矩阵,仅仅表示该对象的运动,而matrixWorld则需要依次向父亲节点迭代,每一次迭代都左乘父亲对象的本地模型矩阵,直到Scene对象——当然,实际上是左乘父亲对象的全局模型矩阵。

属性position、rotation、scale表示模型矩阵的三种变换部分,在Matrix4类中有相关说明。rotation和eulerOrder共同描述了一个旋转状态,quaternion也可以描述一个旋转状态,具体使用哪种方法要看useQuation的布尔值。

可以看到,关于该Object3D对象最重要的“变换状态”信息实际上是存储在两个“备份”中的,一个是matrix对象,还有一个是position等属性,两部分应当保持一致,如果通过某种方法改变了一个备份,则另一个备份也应该在适当的时候更新。还有一些其他属性从字面和类型上就能看出其含义,不再单独列出了。下面说函数:
函数applyMatrix(matrix)将参数matrix左乘到this.matrix上,实际上就是对该Object3D对象实行某个变换(该变换可能要经过好几步基本变换,但是已经存储在参数matrix里面了)。注意,在对this.matrix执行完左乘之后,;立刻更新了position等参数的值。比起下面几个变换函数,该函数更“高级”,允许开发者自由指定变换矩阵,而不是说“朝着x轴前进5单位距离”。


代码如下:

applyMatrix: function ( matrix ) {
this.matrix.multiply( matrix, this.matrix );
this.scale.getScaleFromMatrix( this.matrix );
var mat = new THREE.Matrix4().extractRotation( this.matrix );
this.rotation.setEulerFromRotationMatrix( mat, this.eulerOrder );
this.position.getPositionFromMatrix( this.matrix );
},

函数translate(distance, axis)令该对象向axis轴指定的方向前进distance距离。函数translateX(distance),translateY(distance),translateZ(distance)令其向X,Y,Z轴前进distance距离。注意这些函数仅仅改变了position对象的值,而不曾改变matrix的值。


代码如下:

translate: function ( distance, axis ) {
this.matrix.rotateAxis( axis );
this.position.addSelf( axis.multiplyScalar( distance ) );
},
translateX: function ( distance ) {
this.translate( distance, this._vector.set( 1, 0, 0 ) );
},

函数localToWorld(vector)将本地坐标转化为世界坐标中,函数worldToLocal则正好相反。注意这里的vector本地坐标指的是未变换之前的坐标,也就是说雨刮器的默认位置的顶点坐标。

函数lookAt(eye,center,up)执行其matrix属性对象的lookAt函数(之前介绍过,matrix4对象也有一个lookAt函数),一般用于相机对象。该函数仅仅改变了旋转状态,所以当matrix属性对象执行完之后,如果属性rotationAutoUpdate为真,则会更新rotation或quaternion的值,更新哪一个取决于属性useQuation。
函数add(object)和函数remove(object)从当前Object3D对象中添加一个子对象,或删除一个子对象,了解到场景中的众多Object3D对象是用树来管理的,这就很容易理解了。

函数traverse(callback)遍历调用者和调用者的所有后代,callback参数是一个函数,被调用者和每一个后代对象调用callback(this)。


代码如下:

traverse: function ( callback ) {
callback( this );
for ( var i = 0, l = this.children.length; i < l; i ++ ) {
this.children[ i ].traverse( callback );
}
},

函数getChildByName(name,recursive)通过字符串在调用者的子元素(recursive为false)或后代元素(recursive为true)中查询属性name符合的对象返回。

函数getDescendants(array)将调用者的所有后代对象全部push到数组array中。
函数updateMatrix()和updateMatrixWorld(force)将根据position,rotation或quaternion,scale参数更新matrix和matrixWorld。updateMatrixWorld还会更新所有后代元素的matrixWorld,如果force值为真或者调用者本身的matrixWorldNeedsUpdate值为真。在函数applyMatrix(matrix)中,改变了matrix值后立刻就更新了position,rotation等属性,但在函数translate(distance,axis)中改变了position等变量(或者直接改变position等属性)后并没有立刻更新matrix值,这时应该手动调用updateMatrix()。这些细节值得注意,你也许会认为应该加入事件监听,一旦一个值发生变化,其他所有的都会立刻更新,但我想在,可能是出于这方面的考虑:适当的时候更新会带来更高的效率——比如可能会频繁地改变rotation值,但是仅仅在使用matrix属性之前,才对其进行更新。


代码如下:

updateMatrix: function () {
this.matrix.setPosition( this.position );
if ( this.useQuaternion === false ) {
this.matrix.setRotationFromEuler( this.rotation, this.eulerOrder );
} else {
this.matrix.setRotationFromQuaternion( this.quaternion );
}
if ( this.scale.x !== 1 || this.scale.y !== 1 || this.scale.z !== 1 ) {
this.matrix.scale( this.scale );
this.boundRadiusScale = Math.max( this.scale.x, Math.max( this.scale.y, this.scale.z ) );
}
this.matrixWorldNeedsUpdate = true;
},
updateMatrixWorld: function ( force ) {
if ( this.matrixAutoUpdate === true ) this.updateMatrix();
if ( this.matrixWorldNeedsUpdate === true || force === true ) {
if ( this.parent === undefined ) {
this.matrixWorld.copy( this.matrix );
} else {
this.matrixWorld.multiply( this.parent.matrixWorld, this.matrix );
}
this.matrixWorldNeedsUpdate = false;
force = true;
}
for ( var i = 0, l = this.children.length; i < l; i ++ ) {
this.children[ i ].updateMatrixWorld( force );
}
},

函数deallocate手动将调用者占用的空间释放掉,当不再需要该对象时这样做。
Core::Projectors
管理投影矩阵的类,代码太复杂了,我猜会涉及到render类里的操作,等到适当的时候再看吧。
Core::UV
该构造函数产生一个材质坐标类——就是材质上的坐标,往往与顶点对应起来,光栅化后每个像素都有一个材质坐标,再从材质上“取色”以实现纹理。


代码如下:

THREE.UV = function ( u, v ) {
this.u = u || 0;
this.v = v || 0;
};

材质坐标类就是一个简化的vector2类,除了属性名称不同而已。
Core::Ray Core::Rectangle Core:Spline
射线类,有原点、方向、远近截断点。在点光源中应该有应用。矩形类、曲线类,相对都比较简单,也不那么“核心”,以后再看吧。
Core::Geometry
Geometry类也是非常重要的一类,表示一个由顶点和表面构成的几何形体。


代码如下:

THREE.Geometry = function () {
THREE.GeometryLibrary.push( this );
this.id = THREE.GeometryIdCount ++;
this.name = '';
this.vertices = [];
this.colors = [];
this.normals = [];
this.faces = [];
this.faceUvs = [[]];
this.faceVertexUvs = [[]];
this.morphTargets = [];
this.morphColors = [];
this.morphNormals = [];
this.skinWeights = [];
this.skinIndices = [];
this.lineDistances = [];
this.boundingBox = null;
this.boundingSphere = null;
this.hasTangents = false;
this.dynamic = true;
this.verticesNeedUpdate = false;
this.elementsNeedUpdate = false;
this.uvsNeedUpdate = false;
this.normalsNeedUpdate = false;
this.tangentsNeedUpdate = false;
this.colorsNeedUpdate = false;
this.lineDistancesNeedUpdate = false;
this.buffersNeedUpdate = false;
};

以下两组属性最重要
属性vertics是一个数组,每个元素是vector3类型的对象,表示一个顶点坐标。属性colors和normals表示和顶点对应的颜色值和发现向量,只有在很少的情况下才使用,大部分情况下,顶点的颜色和发现时在“表面”中定义的——如果立方体的6面颜色各不相同,则每个顶点实在不同的面上是不同的颜色。

属性faces是一个数组,每个元素是face4或face3类型的对象,之前介绍face3的时候说到,face中存储的仅仅是顶点的索引值,通过索引值就可以在数组vertices中取到顶点的坐标值。

下面说函数
applyMatrix(matrix)函数更新geometry中的所有顶点坐标和表面的法线向量,所做的实际上是用变换矩阵matrix对geometry形体进行空间变换。normalMatrix是参数matrix左上角3×3矩阵的逆转置矩阵,该矩阵用来旋转矢量(法线,而不是顶点坐标)。


代码如下:

applyMatrix: function ( matrix ) {
var normalMatrix = new THREE.Matrix3();
normalMatrix.getInverse( matrix ).transpose();
for ( var i = 0, il = this.vertices.length; i < il; i ++ ) {
var vertex = this.vertices[ i ];
matrix.multiplyVector3( vertex );
}
for ( var i = 0, il = this.faces.length; i < il; i ++ ) {
var face = this.faces[ i ];
normalMatrix.multiplyVector3( face.normal ).normalize();
for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
normalMatrix.multiplyVector3( face.vertexNormals[ j ] ).normalize();
}
matrix.multiplyVector3( face.centroid );
}
},

函数ComputeCentroid()计算几何形体中每个表面的重心(不是几何形体自己的重心)。这个函数似乎应当放到face类的原型上会更好,但是由于face类内部无法获取点的坐标(除非再将点坐标数组的引用作为参数传入构造函数,这样代价就大了)而仅仅是索引值,所以只好在geometry类的原型上定义了。下面几个函数都是类似的情况(事实上,face类几乎没有什么成员函数)。

函数computeFaceNormals()和computeVertexNormals(areaWeight)计算法线向量,前者影响的是face数组中每个元素的normal属性,一个face只有1个;后者face数组中每个元素的vertexNormal属性,一个face3型对象有3个,一个face4型对象有4个,但是需要注意的是,被多个表面共享的顶点,其法线向量只有一个,同时受到多个表面的影响。比如中心在原点,三组表面都垂直于轴的立方体,其第一象限中的顶点,法线向量是(1,1,1)的归一化。虽然看上去不可思议,平面的顶点的法线居然不是垂直于平面的,但这种指定法线的方法在利用平面模拟曲面的时候有很好的效果。

函数createMorphNormal为每一个morph创建法线。morph应该是用作显示固定连续动画的变形效果。
函数mergeVertics将坐标值相同的点剔除,同时更新face对象中的点索引值。
Core::Quaternian
四维数旋转类用另一种方式表达一个旋转变换,相比用rotation,可以避免万向节死锁问题。


代码如下:

THREE.Quaternion = function( x, y, z, w ) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.w = ( w !== undefined ) ? w : 1;
};

如果不谈函数,Quaternian就是一个简单的vector4类型对象。
函数setFromEuler(v,order)通过一次欧拉旋转设置四维数旋转。
函数setFromAxis(axis,angle)通过绕任意轴旋转设定四维数旋转。
函数setFromRotationMatrix(matrix)通过旋转矩阵设置四维数旋转。
还有一些和vector4类相同的函数这里就不列了。

(0)

相关推荐

  • Three.js学习之文字形状及自定义形状

    1.文字形状 说起3d文字想起了早年word里的一些艺术字: 那么TextGeometry可以用来创建三维的文字形状. 使用文字形状需要下载和引用额外的字体库.这里,我们以 helvetiker字体为例. 引用: <script type="text/javascript" src="你的路径/helvetiker_regular.typeface.json"></script> TextGeometry的构造函数是: THREE.TextG

  • THREE.JS入门教程(4)创建粒子系统

    译序 Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 0.简介 嗨,又见面了.这么说我们已经开始学习Three.js了,如果你还没有看过之前三篇教程,建议你先读完.如果你已经读完前面的教程了,你可能会想做一些关于粒子的东西.让我们直面这个话题吧,每个人都爱粒子效果.不管你是否知道,你可以很轻易地创建它们. 1

  • THREE.JS入门教程(1)THREE.JS使用前了解

    Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 国外网站 aerotwist.com 有六篇较为简单的入门教程,我尝试着将其翻译过来,与大家分享. 我在一些实验项目中使用了Three.js,我发现它对快速上手浏览器3D编程确实很有帮助.通过Three.js,你不仅可以创建相机.物体.光线.材质等等,还可以选择

  • THREE.JS入门教程(6)创建自己的全景图实现步骤

    译序 Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 0.简介 全景图非常酷.使用Three.js做一个属于自己的全景图并不是那么困难. 要做一个全景图,你需要一个软件用来做一张全景图片(译者注:如果你没有那些特殊的设备).我使用了iPhone上的Microsoft Photosynth软件来制作. 1.环境

  • Three.js学习之几何形状

    1.立方体 虽然这一形状的名字叫立方体(CubeGeometry),但它其实是长方体,也就是长宽高可以设置为不同的值.其构造函数是: THREE.CubeGeometry(width,height,depth,widthSegments,heightSegments, depthSegments) width:x方向上的长度 height:y方向上的长度 depth:z方向上的长度 widthSegments:x方向上的分段数(可选,缺省值1) heightSegments:y方向上的分段数(同

  • three.js实现围绕某物体旋转

    话不多说,请看代码: 可以拖动右上角观察变化 <!DOCTYPE html> <html lang="en" style="width: 100%; height:100%;"> <head> <meta charset="UTF-8"> <title>Document</title> <script src="http://cdn.bootcss.com/t

  • three.js快速入门【推荐】

    Three.js的核心五步就是: 1.设置three.js渲染器 2.设置摄像机camera 3.设置场景scene 4.设置光源light 5.设置物体object 1.设置three.js渲染器 三维空间里的物体映射到二维平面的过程被称为三维渲染. 一般来说我们都把进行渲染操作的软件叫做渲染器. 具体来说要进行下面这些处理. var renderer; function initThree(){ width = document.getElementById("box").clie

  • THREE.JS入门教程(2)着色器-上

    译序 Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 0.简介 之前我已经给出了一篇<开始使用Three.js>.如果你还没有读过,你可能需要去读一下,本文的基础是在那一篇教程的基础上完成的. 我想讨论一下着色器.在Three.js帮助你免去了很多麻烦之前,原生WebGL就很优秀了.有的时候,你也许会想要完

  • THREE.JS入门教程(5)你应当知道的十件事

    Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. 0.简介 嗨,这是我的第一篇关于如何写出好的代码的文章.和很多开发者一样,我通过实践学习,但同时我也向其他更有经验的开发者们学习.在过去的几个月中,我在canvas标签上花了很多时间,我想如果把这段时间学到的关于WebGL和JavaScript的小技巧都写下来,

  • THREE.JS入门教程(3)着色器-下

    译序 Three.js是一个伟大的开源WebGL库,WebGL允许JavaScript操作GPU,在浏览器端实现真正意义的3D.但是目前这项技术还处在发展阶段,资料极为匮乏,爱好者学习基本要通过Demo源码和Three.js本身的源码来学习. .简介 这是WebGL着色器教程的后半部分,如果你没看过前一篇,阅读这一篇教程可能会使你感到困惑,建议你翻阅前面的教程. 上一篇结束的时候,我们在屏幕中央画了一个好看的粉红色的球体.现在我要开始创建一些更加有意思的东西了. 在这一篇教程中,我们会先花点时间

随机推荐