与此相关的四个基本类是 vertex buffer , index buffer , transformations 和 bounding volumes 。
一, VB 和 IB
VB 包含了 position , color , texture coordinates , normals , IB 包含了点的邻接信息。 VB 和 IB 共同组成 geometric primitive ,它可以是点集,多边形,和三角网格等。 Geometry 类便是其容器。
二, Transformation
1 .原理
R 代表旋转矩阵, T 代表平移向量, >0 代表 uniform scaling 。
变换方程为 Y=R( X)+T 逆变换为 X= ,1-σ.,.,R-T.(Y−T)
若为 nonuniform scaling, 则
Y=RSX+T , 逆变换为 X= , 其中, S=diag ( ,σ-0. , ,σ-1. , ,σ-2. )
若引入 nonuniform S ,则计算量很大,同时,将 M (齐次矩阵左上角的 3x3 部分)分解为 R 和 S 几乎是不可能的(详见 p236 )
若引入 nonunifrom scale, 在每一个 transformation 中分别存储 R,S,T 显然是不现实的,因此,应以 M,T 的形式存储,这样便无法分别获得 R,S ,不仅如此,计算量也会增加,一种解决办法(不完美)是写成( L,R,T )的形式,并计算
M=LDR ( singular value decomposition ),但计算量也不小。
作者的思路是为了运算方便,在 Node 中不支持 nonuniform scale, 只在 Geometry 中支持 nonuniform scale, 这样一来,除了最底层的节点,每节都可分别获得 R, ,T ,也避免了完全不支持 nonunifrom scale 的缺点。
2 . Transformation 类简介(详见 p237-244 )
现仅简述关键问题若干。
@H_619_404@(1) Set 系列函数有 side effect ,它不准确的设置了 3 个 bool 变量,但换来的是安全性。 @H_619_404@(2) GetNorm ()的作用是将 3 个 nonuniform scale 分量强制转换为一个 uniform scale 。专用于包围球的变换,因为球体经 nonuniform scale 会变为椭球,而 WM 不支持此种类型的包围体。
@H_619_404@(3) Transformation :: ApplyInverse ()与 Transformation :: InvertVector ()的区别。前者作用于点,后者作用于向量(如法线)。对于点 P , , 对于向量 V , ,V-..=RSV (向量不需要平移)。
@H_619_404@(4) Transformation :: Inverse ()的特殊性。该函数的功能仅仅是获取逆变换中的 , 和 −,S-−1.,R-T.T , 考虑到 X= ,因此参数中返回的 Transformation 只能作为一个容器,而不能用于对点进行逆变换,若要对点进行逆变换,用 ApplyInverse.
(5) 对平面 ,N-0.∙X=,C-0. 进行变换。
将 (Y-T) 代入上式,得
,N-1.=,N-0.-.-|R,N-0.|.
,C-1.=,C-0.-.-|R,N-0.|. +
(6) 除法对 cpu 的开销远大于乘法,因此对于 a/d,b/d,c/d, 应改为
P=1/d,ap,bp,cp.
三, bounding volumes
bounding volumes 的作用有二:剔除,碰撞检测。
1. 剔除
Inexact query 基本思路:当检测到包围体完全位于某一个截面之外时,即判定该包围体位于整个视景体之外。
Inexact 体现在存在边角物体,虽然其包围体不在任何截面之外,但该物体仍在视景体之外,不过值得庆幸的是,这样的情况并不多见。
Scene graph 的好处体现在一旦检测到某节点完全位于视景体之外或之内,那么节点下的每一层物体均位于视景体之外或之内, scene graph 便会通知下面的每一层没有必要再检测,可通过传递位数组实现。
一个问题是应该选尽量复杂的包围体还是尽量简单的包围体,这个没有一般规律可循,多加调试即可。
2. 碰撞检测, 3D 拾取 详见第八章。
3. 抽象类 BoundingVolume
这个类是抽象类,在开发阶段我们必须继承这个类而不能直接使用。这样做的目的是让该核心类可支持各种类型的包围体以及满足各种形式的几何操作。
但该类还是保留了一些基本信息:
(1) center :包围体的中心,如球的球心, oriented Box 的中心,凸多面体的重心等等。
(2) radius :包围体的大小。如球的半径, oriented Box 的顶点到中心的最大距离,凸多面体的顶点到重心的最大距离。
实际上,我们可以发现,该抽象类的默认形状是球体。
一些说明: static BoundingVolume * Create ()
该函数的功能是在不知道引擎中存在哪些类型的包围体时,批量生成大量对象,待研究!! !若要把抽象类换成其他类,须重载 Create ()
两个说明:( 1 )该抽象类只支持同种类型包围体之间的操作,为了简化引擎,若要支持,需派生类。
(2) 包围体的合并是很复杂的算法。
四, Geometry TYPES
如点集,线段集, linestrip, 多边形,三角网格等。
Points
点集存在于 VB 中,但实际绘制通常是激活的那部分。
m_iActiveQuantity 用于存储激活数目。
IBuffer->GetIndexQuantity 用于获得 ID 中所有索引数,但我们通过 SetIndexQuantity 将其设为激活 的索引数,但须自己记住原值,以便恢复。
LineSegments
构造函数会根据参数值调用 SetGeometryType ,设置类型。关于激活数目,原理同上。
TriangleMeshes (最常见的类型)
该类中实现了计算法向量的函数:
N=,V-1.−,V-0....X(,V-2.−,V-0.)
,N.=,i=1-M-,-,N-i....-|,N-i....|.
Particles
详见第五章。
五, Geometric State 的更新。
当以下任何一项改变时, geometry state 需要更新:
(1) vertex position ( 2 ) bounding volumes ( 3 ) transformation ( 4 )场景图的拓扑结构
三个核心类均参与了该过程,该过程是在自顶向下和自下及顶两步对场景图的遍历中完成的。
相关成员变量及成员函数简介如下:
先看成员变量
Class Spatial::public Object
{
Public:
Transformation Local;// 这里存储了每个 Geometry 或 Node 的 local transformation 。
Transformation World ; // 这里存储了每个 Node 或 Geometry 的 world transformation 。
bool WorldIsCurrent ; // 该变量决定 World 是否需要更新。若为 true ,则不更新。该变量为编程带来了极大的灵活性,例如, Controller 可能直接对 world 进行更新,这样就没有必要通过父节点计算 world 将该变量设为 true 即可实现这一点。
BoundingVolumePtr WorldBound ; // 存储了 world bounding volume 。
bool WorldBoundIsCurrent ; // 该变量决定 WorldBound 是否需要更新。设为 true ,则不需要更新。例如:( 1 ) Controller 可能直接对 worldbound 进行更新,此时便没必要通过 worldtransformation*local bound 或 growtocontain ()计算 WorldBound 。( 2 )某节点 worldbound 已经确定为固定不变,如一个房间,已知房间内的物体无论如何移动都不会移动出房间。
}
Class Geometry : public Spatial
{
Public :
BoundingVolumePtr ModelBound ; // 该变量存储了 model bounding volume 。
}
再看成员函数。
三大顶层函数: updateGS , updateBS , updateMS
Class Spatial : public Object
{
Public :
Void UpdateGS ( double dAppTime=-Mathd::MAX_REAL,bool bInitiator=true ) ;
// 该函数为三大顶层函数之一,公共接口由程序员调用,该函数负责 node 或 geometry 的 geometry state 的更新,既包括自顶向下的 world transformation 的计算,底层的 world bounding volume 的计算,又包括自底向上的 Node world bounding volume 的计算,该函数的定义见下文的函数调用图。
Void UpdateBS (); // 三大顶层函数之一,只负责 world bounding volume 的更新。例如,在某些情况下, local transformation 没有变化, world transformation 没变,只有一些几何操作导致了 world bounding volume 的变化,因此,只需要更新 wvb ,而不必向下遍历,此时调用 UpdateBS ()即可。
Protected :
Virtual void UpdateWorldData ( double dAppTime ) ;// 虚函数,具体的实现怎样要看调用者是 Node 还是 Geometry 。若是 Node ,先调用基类的 Spatial 的同名函数,在调用每个子类的 UpdateGS ,进行递归;若是 Geometry ,直接调用 Spatial 中的 UpdateGS 该函数调用 controller ,在更新 global state 和 lights ,再计算 world transformation 。
Virtual void UpdateWorldBound () =0 ; // 纯虚函数,因为 Node 和 Geometry 各有不同的实现,两者毫无关系,若为 Node ,则根据子节点的 world bound 计算 Node 的 world bound ,若为 Geometry ,则直接通过 world bound*world transformation 计算 world bound 。
Void propagateTOROOT();// 该函数是否执行取决于 bInitiator 是否为 true 。负责向上遍历以更新父节点的 world bound 。
}
Class Geometry : public spatial
{
Public :
Void UpdateMS ( bool bUpdateNormals=true ) ;// 三大顶层函数之一。该函数负责更新 model bound ,并根据参数是否为 true ,决定是否更新 normal 。
Protected :
Virtual void UpdateModelBound ();
Virtual void UpdateModelNormals ();
Virtual void Updateworldbound (); // 计算 geometry 的 world bound 。
}
Class Node : public spatial
{
Protected :
Virtual void UpdateWorldData ( double dAppTime );
Virtual void UpdateWorldBound (); // 此两个函数上文已说明。
}
Node Spatial::UpdateGS
UpdateWorldData Spatial::UpdateWorldData
调用每个子节点的 UpdateGS (递归开始)
UpdateWorldBound 调用 WorldBound--- à GrowToContain
PropagateBoundToRoot() m_pkParent->UpdateWorldBound()
m->pkParent->PropagateBoundToRoot()( 递归开始 )
Geometry Spatial :: UpdateGS
controller
Spatial :: UpdateWorldData 更新 global state 及 lights
计算 WorldTransformation
UpdateWorldBound( 递归返回 )
PropagateBoundToRoot
UpdateWorldBound();
Node/Geometry->Spatial :: UpdateBS
PropagateBoundToRoot();
UpdateModelBound()
Geometry->UpdateMS
UpdateModelNormals()// 调用与否由参数决定。
调用原则见表 p279