11.4、常用的图形数学

本章是3D图形数学的一个速成课。如果本章让你头晕,不要太担心。本节列出了应用会执行的最常见的3D数学运算。使用这些诀窍和技术可以解决你所碰到的大部分问题,甚至不必深入理解其实现。

11.4.1 简单矢量运算

下面是常见的矢量运算。

  • 翻转方向。GLKVector3 GLKVector3Negate(GLKVector3 vector)函数会返回一个跟vector参数长度相同但是方向相反的新矢量。你可以在第6章的SceneCar类中看到这个函数。
  • 长度缩放。GLKVector3 GLKVector3MultiplyScalar(GLKVector3 vector, float value)函数会使用value参数的值缩放vector参数的长度,但是不会改变vector参数的方向,除非值为负。函数调用GLKVector3MultiplyScalar(vect-or, -1.0)和GLKVector3Negate(vector)返回的是相同的矢量。在第4章使用GLKVector3-MultiplyScalar()平衡表面法向量以获得平滑的灯光效果。SceneCar例子类、AGLKit例子类和Utility例子类也使用了这个函数。
  • 两点间矢量。GLKVector3 GLK Vector3Subtract(GLK Vector3 positionl, GLKVector3position2)函数会返回从position2到position1之间的矢量。第4章在计算用于灯光模拟的表面法向量过程的--个步骤中计算了顶点间的矢量。多个AGLKit和Utility例子类使用了这个函数。
  • 矢量相加。GLKVector3 GLKVector3Add(GLKVector3 vectorA, GLKVector3 vectorB)函数会返回一个相当于在vectorA方向上移动vectorA长度,并在vectorB方向上移动vectorB长度的一个新矢量,参见第1章的图1.11。第4章在为平滑灯光模拟而平衡表面法向量的过程中应用了矢量相加。矢量标准化。GLKVector3 GLK Vector3Normalize(GLKVector3 vector) 函数会返回一个跟vector参数方向相同,但是长度为1.0 的新矢量。长度为1.0 的矢量被称为单位矢量。第4章讲解了法向量。
  • 两点间距离。float GLKVector3Distance(GLKVector3 positionA, GLKVector3 positionB) 函数会返回参数positionA和positionB之间的距离。它相当于float GLKVector3-Distance(GLK Vector3 positionA, GLKVector3 positionB)。 第6章的SceneCar类调用了GLKVector3Distance() 函数。

11.4.2 矢量标量积

本章的第11.2.3节展示了标量积函数float GLKVector3DotProduct(GLK Vector3 vectorA, GLKVector3 vectorB)的实现。标量积有以下常见用途。

  • 失量间角度。标量积是两个矢量间夹角的余弦值。因此acosf(GLK Vector3Dot-Product(vectorA, vectorB))得出的是两个矢量间的角度。第6章的SceneCar类调用了GLKVector3DotProduct()函数,用以决定把碰碰车模型掉转到它正移动的方向需要旋转多少度。
  • 计算长度的平方。一个矢量与它自身的标量积等于这个矢量长度的平方。很多情况下要用到长度的平方,因为如果lengthA的平方大于lengthB的平方,那么lengthA也大于lengthB。常常只需要知道哪-个更长而不需要知道精确的长度值。可参看在第10章的例子TerrainEditor中的UtilityVector3LengthSquared(函数的实现。
  • 长度计算。表达式sqrtf(GLK Vector3DotProduct(vectorA, vectorA)) 会计算vectorA的长度,这个表达式相当于float GLK Vector3Length(GLK Vector3 vectorA)函数。sqrtf()函数是标准C库中计算代价最高的函数之一。任何时刻都应该尽量避免调用它。在很多情况下都可以使用一个矢量长度的平方来代替实际长度。第6章中的SceneCar类的实现代码中调用了GLKVector3Length() 函数,第10章的例子使用这个函数来控制摄像机的移动。
  • 确定视点背后的点。如果表达式GLKVector3DotProduct(GLK Vector 3Subtract(position, eyePosition), lookAtVector))的值为负,那么position 位于eyePosition的后面,因此无法被看到。这个技术是在第8章介绍的,用以避免绘制那些看不到的公告牌特效。
  • 计算漫反射光。射向一个三角形的漫反射光的强度等于表达式GLKVector3Dot-Product(diffuseLightDirection, surfaceNormalvector))的值。第4章讲解了标量积在灯光模拟中的用途。
  • 分离矢量分量。表达式GLK Vector3DotProduct(vectorA,vectorB)会返回vectorA在vectorB方向,上的分量部分。表达式GLKVector3DotProduct (vectorA, zAxisVector)会返回vectorA在Z轴方向上的分量。GLKVector3DotProduct(vectorA, yAxisVector) 会返回vectorA在Y轴方向上的分量。表达式GLKVector3DotProduct(vectorA, xAxisVector)会返回vectorA在X轴方向上的分量。请参看在第9章中介绍的AGLKFrustum例子类以及11.2.3节。
图 11-14

11.4.3 矢量的矢量积

两个非平行单位矢量的矢量积是垂直于这两个非平行单位 矢量的第三个单位矢量,参见图11-14。 v 矢量积的计算只需要乘法和加法运算,这两个计算是GPU执行最快的运算。

 GLKVector3 GLKVector3CrossProduct(GLKVector3 vectorLeft, GLKVector3 vectorRight)
{
    GLKVector3 v = { vectorLeft.v[1] * vectorRight.v[2] - vectorLeft.v[2] * vectorRight.v[1],
                     vectorLeft.v[2] * vectorRight.v[0] - vectorLeft.v[0] * vectorRight.v[2],
                     vectorLeft.v[0] * vectorRight.v[1] - vectorLeft.v[1] * vectorRight.v[0] };
    return v;
}
  • 表面法向量。表面法向量是垂直于由两个矢量所定义的面的一个单位矢量。GLKVector3CrossProduct(GLKVector3Normalize (vectorA), GLK Vector3-Normalize(vectorB))函数会返回由vectorA和vectorB所定义的面的表面法向量。第4章讲解了用于灯光模拟的表面法向量的计算。第12章会使用相同的计算以确保装载了火箭发动机的碰碰车模型在驶过山谷和丘陵时会与地形保持平行。
  • 平截体几何图形。一个平截体的视线方向矢量和上方向矢量之间的矢量积产生了这个平截体的X轴矢量。第8章和第9章讲解了一个3D场景中的可视区域的几何定义。11.2.1 节介绍了一个平截体的几何定义与对应的矩阵表示之间的关系。
  • 公告牌方向。公告牌是与平截体的近面平行的。一个平截体的视线方向矢量和上方向矢量的矢量积乘以公告牌宽度的一半确定了公告牌左下角的位置。如第8章介绍的,其余的公告牌角是相对于左下角计算出来的。

11.4.4 model-view矩阵

model-view矩阵定义了用来描述一个场景内的物体的位置和方向的坐标系。model-view矩阵定义了视平截体的位置和方向。

  • 初始化一个model-view矩阵。初始化一个model-view矩阵的最常见方式是调用GLKMatrix4 GLKMatrix4MakeLookAt(float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)函数。model-view矩阵还可以由一个视平截体或者一个四元数计算而来。
  • 与默认坐标系之间的关系。可以使用model-view矩阵把一个点从model-view坐标系变换到投影坐标系。model-view矩阵和投影矩阵的级联产生了一个单独的矩阵,这个矩阵可以把一个点从model-view坐标系一直变换到OpenGL默认坐标系。
  • 级联。两个矩阵的级联会产生第三个包含了这两个矩阵的所有坐标系变换的矩阵。函数GLKMatrix4 GLKMatrix4Multiply(GLKMatrix4 matrixA, GLKMatrix4matrixB)会返回matrixA和matrixB的级联。
  • 累积变换。model-view矩阵通常包含与透视图无关的所有坐标系变换。函数GLKMatrix4 GLKMatrix4Scale(GLKMatrix4 matrix, float sx, float sy, float sz)会返回,通过使用指定的因数缩放编码在矩阵中的坐标轴所产生的一个新矩阵。函数GLKMatrix4 GLKMatrix4Translate(GLKMatrix4 matrix, float tx, float ty, float tz)会返回,通过使用指定的轴对齐距离来平移编码在矩阵中的坐标轴原点所产生的一个新矩阵。函数GLKMatrix4 GLKMatrix4RotateWithV ector3(GLKMatrix4 matrix, float radians, GLKVector3 axisVector) 会返回,通过使用指定的围绕参数axisVector的弧度值来旋转编码在矩阵中的坐标轴所产生的一个新矩阵。

11.4.5 投影矩阵

投影矩阵要么会定义一个正交投影,要么会定义一个透视投影。正交投影不会基于物体与坐标系原点的距离而改变物体的显示大小。透视投影会让离坐标系原点较远的物体显得更小。投影矩阵定义了视平截体的形状,投影矩阵通常是通过调用GLKMatrix4 GLKMatrix4MakePerspective(float fovyRadians, float aspect, float nearZ, float farZ)或者 GLKMatrix4 GLKMatrix4MakeOrtho(float left, float right, float bottom, float top, float nearZ,float farZ) 函数来初始化的。投影矩阵还可以基于一个现存的平截体形状计算出来。

11.5 小结

本章探讨了3D图形的数学基础。限于篇幅很难涵盖线性代数的

深度和广度。矩阵是通过存储用于定义三个坐标轴方向的单位矢量,以及坐标系原点在参考坐标系中的位置来编码坐标系的。所有的坐标系都是相对于其他坐标系的。默认OpenGL坐 标系提供了一个基础参考,其他坐标系是基于这个基础参考来定义的。

矩阵还定义了视平截体的形状、位置和方向。可以在矩阵和平截体数据结构间来回转换。四元法可以编码关于任意矢量的旋转,四元法可以用于避免旋转一个矩阵出现错误时的未定义行为。矩阵和四元数之间也是可以来回转换的。

第12章会介绍一个结合了地形渲染、天空盒、粒子系统、动画、变化视点、灯光、模型和碰撞检测技术的例子。要描述出在一个应用中应该在何时,以及为什么而使用前面章节中分隔介绍的专题可能有些困难。第12章会把所有的知识都结合在一起。

results matching ""

    No results matching ""