5.4、变换

      从数学上说,变换就是在两个坐标系之间转换顶点坐标。每个坐标系都是相对于其他的参照坐标系定义的。对于OpenGL ES来说,最终的参照坐标系是在一个像素颜色渲染缓存中的像素位置的2D数组。默认3D坐标系是相对于最终参考坐标系定义的,能够用来将-1.0到1.0范围内的X坐标值转换为像素颜色渲染缓存对应宽度处的像素位置,将-1.0到1.0范围内的Y坐标值转换为像素颜色渲染缓存对应高度处的像素位置。除了Z坐标值在-1.0到1.0范围外的顶点不会绘制以外,Z坐标值不会影响默认3D坐标系跟像素坐标之间的转换。


注意 在渲染的过程中, GPU会转换点、线和三角形的顶点数据为着色的片元。当前(实际上是在GPU处理顶点数据时)坐标系决定了片元在像素颜色渲染缓存和深度缓存中的位置。当前坐标系因此决定了每个片元会显示在屏幕的哪里,并使从 任意视点渲染场景成为可能。


      程序能够相对于默认3D坐标系(作为一个参考)定义一个新的坐标系。可以相对于其他坐标系定义任意数量的坐标系。在任何情况下,在一个新坐标系中的顶点位置可以被转换回参考的坐标系。因此,可以转换在任意坐标系中的任意顶点位置为像素颜色渲染缓存中的一个位置。

      变换看起来需要大量的数学计算才能在坐标系之间进行转换,但是线性代数再次提供了解决方案。任意数量任意顺序的基本变换都能够被捕获并保存在一个简单的4乘4的浮点值矩阵中。一个矩阵定义一个坐标系。第11章会介绍GPU在坐标系之间进行顶点转换时要用到的实际方程式。

      矩阵计算几乎完全使用加法和乘法,并且在现代GPU上执行得非常快。这是个好事,因为每次一个场景被渲染时,GPU都会变换所有的顶点,并且在一个场景中常常含有几十万个顶点。

5.4.1 基本变换

只存在四个基本变换:平移(translation)、 旋转(rotation)、 缩放(scale) 和透视(perspective)。基本变换联合决定了在一个新坐标系中的每一个 顶点位置是怎么转换为参考坐标系中的一个位置的。四个基本变换足以产生变化无穷的坐标系。

每个基本变换对应于矩阵的一个简单变化。定义一个与参考坐标系相同的坐标系的矩阵叫做单位矩阵(identity matrix)。任意两个矩阵可以在一个级联(concatenation) 操作中结合起来以产生一个新矩阵,这个新矩阵包含了两个矩阵的所有变换。实际上,每个基本变换会产生一个简单的矩阵,然后把这个简单的矩阵与当前变换矩阵连接起来 以产生一个新的当前矩阵。


注意

“矩阵级联” 有时又被称为“矩阵乘法”(matrix multiplication)。术语“矩阵乘法”还适用于使用一个矩阵把一个矢量或者顶点从一个坐标系变换到另一个的操作。“级联”是一个更精确的术语,它描绘了两个矩阵的结合,这个结合产生了包含这两个源矩阵的所有信息的第三个矩阵。


1. 平移

通过相对于参考坐标系的原点移动新坐标系的原点,平移定义了一个新的坐标系。平移不会影响坐标轴的单位长度,平移不会改变坐标轴相对于参考坐标系的方向。图5-2描述了一个新坐标系,这个坐标系是对参考坐标系的平移。

图 5-2

GLKit提供了GLKMatrix4MakeTranslation(float x, float y, float z)函数,这个函数通过平移一个单位矩阵来返回一个定义了坐标系的新矩阵。x、y和z参数指定了新坐标系的原点沿着当前参考坐标系的每个轴移动的单位 数。函数GLKMatrix4Translate(GLKMatrix4matrix, float x, float y, float z)通过平移作为参数传人的矩阵来返回定义了一个坐标系的新矩阵。从概念上看,GLKMatrix4Translate()会返回参数矩阵与GLKMatrix4MakeTranslation()产生的新矩阵的级联。

GLKMatrix4 GLKMatrix4Translate (GLKMatrix4 matrix,
    float x, float y, float z) {
    return GLKMatrix4Multiply(matrix,
            GLKMatrix4MakeTranslation(x, y, z));
}

2. 旋转

旋转是通过相对于参考坐标系坐标轴的方向旋转新坐标系的坐标轴来定义一个新坐标系。旋转的坐标系会与参考坐标系使用同一个原点。旋转不会影响坐标轴的单位长度,只有坐标 轴的方向会发生变化。图5-3描绘了一个新坐标系,这个坐标系是由旋转一个参考坐标系来定义的。

图 5-3

GLKit提供了GLKMatrix4MakeRotation(float angleRadians, float x, float y, float z)函数,这个函数通过旋转一个单位矩阵来返回定义了一个坐标系的新矩阵。angleRadians参数指定了要旋转的弧度数。使用GLKMathDegreesToRadians()函数可以把角度转换成弧度。x、y和z参数用于指定当前坐标系的哪一个轴作为旋转的轮毂。例如,代码GLKMatrix4MakeRotation(GLKMathDegreesTo-Radians(30.0), 1.0, 0.0, 0.0);会沿着一个特定的坐标系的X轴旋转30度来产生一个新的坐标系。GLKMatrix4Rotate(GLKMatrix4 matrix, float angleRadians, float x, float y, float z)函数会通过旋转一个作为参数传人的矩阵来返回定义了一个坐标系的新矩阵。从概念上看,GLKMatrix4Rotate()会返回参数矩阵与GLKMatrix4MakeRotation()产生的新矩阵的级联。

GLKMatrix4 GLKMatrix4Rotate (GLKMatrix4 matrix,
                            float angleRadians, 
                            float x, 
                            float y, 
                            float z) {
        return GLKMatrix4Multiply (matrix, GLKMatrix4MakeRotation( angleRadians, x, y, z));
}

3. 缩放

缩放是通过相对于参考坐标系的坐标轴的单位长度改变新坐标系的坐标轴的单位长度来定义一个新坐标系。缩放的坐标系与参考坐标系使用同一个原点,坐标轴的方向通常不会改变。不过,通过一个负值所做的缩放会翻转坐标轴的方向。例如,如果增加一个坐标轴的值通常代表方向向上,那么使用一个负值缩放那个坐标轴后,再增加这个坐标轴的值就代表方向向下。图5-4描述了一个通过缩放参考坐标系来定义的新坐标系。

图 5-4

GLKit提供了GLKMatrix4MakeScale(float x, float y, floatz) 函数,这个函数会通过扩大或者缩小一个单位矩阵的任意坐标轴的单位长度来返回一个定义了坐标系的矩 阵。x、y和z参数指定了用来扩大或者缩小每个轴的单位长度的因数。GLKMatrix4-Scale(GLKMatrix4 matrix, float x, float y, float z)函数通过按指定的因数缩放作为参数传人的矩阵来返回一个定义了坐标系的新矩阵。从概念上看,GLKMatrix4Scale( 会返回参数矩阵与GLKMatrix4MakeScale()产生的新矩阵的级联。


GLKMatrix4 GLKMatrix4Scale(GLKMatrix4 matrix,
                            float x, 
                            float y, 
                            float z)
{
        return GLKMatrix4Multiply(matrix,GLKMatrix4MakeScale(x, y, z));
}

例如,函数GLKMatrix4Scale(matrix, 2.0, 1.0,2.0)会返回一个新矩阵,这个矩阵拉伸了由参数矩阵定义的坐标系的X和Z轴的单位长度。

4. 透视

透视是通过相对于参考坐标系的坐标轴的单位长度多样化新坐标系的坐标轴的单位长度来定义一个新的坐标系。透视不会改变坐标轴的方向或者原点,但是坐标轴的每个单位离原点越远长度越短。这个效果会让在远处的物体比离原点近的物体显得更小。图5-5描述了通过相对于参考坐标系的透视定义的一个新坐标系。

GLKit提供了GLKMatrix4MakeFrustum( float left, float right, float bottom, float top, float nearVal, float farVal) 函数,这个函数会透视一个单位矩阵来返回一个定义了坐标系的新矩阵。透 视坐标系的形状是一个类似角锥体的平截头体。平截头体会在5.6节做详细解释。

图 5-5

5.4.2 顺序很重要

结合四大基本变换可以定义新坐标系,不过重要的是变换的顺序。平移在旋转的前面还是后面会产生不同的坐标系。缩放在旋转的前面还是后面会产生不同的坐标系。先旋转再平移最后缩放与先平移再旋转最后缩放是不同的,诸如此类。例子OpenGLES_Ch5_4为测试变换提供了用户界面控件,参见图5-6。

图 5-6

5.4.3 projectionMatrix和modelviewMatrix

GLKBaseEffect的transform属性是一个GLKEffectPropertyTransform类型的实例并为支持常见的操作保存了三个不同的矩阵。其中的两个矩阵,projectionMatrix和modelviewMatrix分别定义了一个用于整个场景的坐标系和一个用于控制对象(又叫做场景内模型)显示位置的坐标系。GLKBaseEffect 会级联modelviewMatrix和projectionMatrix以产生一个modelviewProjectionMatrix矩阵,这个矩阵会把对象顶点完全地变换到OpenGLES默认坐标系中。默认坐标系直接映射到像素颜色渲染缓存中的片元位置。图5-7 显示了理论上的一系列变换,以及在渲染过程中为变换顶点所产生的坐标系。

图 5-7

OpenGLES_Ch5_4 应用会设置baseEffectprojectionMatrix来定义一个正射投影,正射投影会在5.6节中进行讲解。

const GLfloat aspectRatio = (GLfloat view.drawableWidth / (GLfloat )view.drawableHeight;
self.baseEffect.transform.projectionMatrix = GLIOMatrix4MakeOrtho( 
                                                -0.5 * aspectRatio,
                                                 0.5 * aspectRatio,
                                                  -0.5,
                                                  0.5,
                                                  5.0);

在OpenGLES_Ch5_4ViewController 的“-viewDidLoad” 方法中初始化的 modelview-Matrix矩阵会使用--个明显的右上视角来渲染对象,参见图5-6。

GLKMatrix4 modelviewMatrix = GLKMatrix4MakeRotation(
                                    GLKMathDegreesToRadians(30.0f),
                                    1.0// Rotate about x axis
                                    0.0,
                                    0.0);
modelviewMatrix = GLKMatrix4Rotate(
                            modelviewMatrix,
                            GLKMathDegreesToRadians(-30.0f),
                            0.0,
                            1.0, // Rotate about Y axis
                            0.0);
modelviewMatrix = GLKMatrix4Translate(
                        modelviewMatrix,
                        -0.25,// Translate apparent position left and into the screen
                        0.0,
                        -0.20);

在用户移动图5-6中显示的滑动条的同时,会更新每个用户用来控制变换的值。在例子OpenGLES_Ch5_4重绘时,下面的代码会重新计算modelviewMatrix。首先,保存baseEffect的modelviewMatrix的当前值,以便可以在后面加载它。通过按特定的顺序连接当前矩阵与用户控制的三个变换矩阵,这个例子创建了一个新的矩阵。修改顺序就会修改变换的最终结果。接着,把新矩阵设置给baseEffect的modelviewMatrix。



// Save the current Modelview matrix
GLKMatrix4 sevedModelviewMatrix = self.baseEffect.transform.modelviewMatrix;

//Combine all of the user chosen transforms in order
GLKMatrix4 newModelviewMatrix = GLKMatrix4Multiply( 
                    sevedModelviewMatrix,
                    SceneMatrixForTransform(
                                transform1Type,
                                transformlAxis,
                                transformlValue) );

newModelviewMatrix =
        GLKMatrix4Multiply(
            newModelviewMatrix,
            SceneMatrixForTransform(
                    transform2Type,
                    transform2Axis,
                    transform2Value));

newModelviewMatrix =
        GLKMatrix4Multiply(
            newModelviewMatrix,
            SceneMatrixForTransform(
                    transform3Type,
                    transform3Axis,
                    transform3Value)) ;
// Set the Modelview matrix for drawing
self.baseEffect.transform.modelviewMatrix = newModelviewMatrix;

在用户控制的变换结合到modelviewMatrix后,为绘制准备好baseEffect,最后用一个白色的漫反射光绘制场景中的对象。


// Make the light white
self.baseEffect.light0.diffuseColor = GLKVector4Make (
                                                    1.0f, // Red 
                                                    1.0f, // Green
                                                    1.0f, // Blue
                                                    1.0f);// Alpha

[self.baseEffect prepareToDraw];


// Draw triangles using vertices in the prepared vertex
// buffers
[AGLKVertexAttribArrayBuffer
        drawPreparedArraysWithMode:GL_TRIANGLES
                    startVertexIndex:0
                    numberOfVertices:lowPo1yAxesAndModels2NumVerts];

重新设置modelviewMatrix为保存的值,灯光颜色改成黄色,再次绘制相同的对象。场景中的黄色对象提供了一个参考,以便可以清楚地观察用户控制的变换会怎么影响在最终的像索颜色渲染缓存中的白色对象的哪些片元。


// Restore the saved Modelview matrix
self.baseEffect.transform.modelviewMatrix = sevedModelviewMatrix;
// Change the light color
self.baseEffect.light0.diffuseColor = GLKVector4Make(
            1.0f, // Red
            1.0f, // Green
            0.0f, // Blue
            0.3f);// Alpha

[self.baseEffect prepareToDraw] ;

// Draw triangles using vertices in the prepared vertex
/// buffers
[AGLKVertexAttribArrayBuffer
            drawPreparedArraysWithMode:GL_TRIANGLES
                        startVertexIndex: 0
                        numberOfVertices: lowPo1yAxesAndModels2NumVerts ];

例子OpenGLES_Ch5_4通过复制矩阵到一个临时的变量来保存并恢复modelview-Matrix。GLKit提供了一个方便的数据类型GLKMatrixStack,还提供了一个用来向栈数据结构保存矩阵的函数集合。堆栈是一个后进先出的数据结构,它可以方便地存储某个程序可能需要恢复的矩阵。GLKMatrixStack会实现一个4X4矩阵的堆栈。GLKMatrixStackPush()丽数会复制最顶部的矩阵到堆栈的顶部。GLKit 为修改矩阵堆栈 顶部的矩阵提供了一个综合的函数集合,包括GLKMatrixStackMultiplyMatrix4()函数,这个函数是其他函数的基础。GLKMatrixStackGetMatrix4(函数会返回最顶部的矩阵。GLKMatrixStackPop(函数会移除堆栈最顶部的项,并把前一个顶部矩阵恢复到最顶部位置。

应用会推入一个新矩阵到堆栈顶部,操作并使用它在OpenGL ES中渲染几何图形,然后把它弹出堆栈并把上一个矩阵恢复到堆栈的顶部。从例子OpenGLES_Ch5_5开始,本书的很多例子都使用了GLKMatrixStack。

5.4.4 textureMatrix

桌面OpenGL和OpenGL ES 1.x 包含三个内建的矩阵堆栈。前两个保存投影矩阵和model-view矩阵。第三个是纹理矩阵,S和T坐标系的纹理与顶点的U和V坐标之间有一个映射,纹理矩阵会向这个映射施加变换。纹理映射是在第3章讲解的。在写作本书时,GLKBaseEffect的transform 属性还没有使用或者提供-个textureMatrix。因此,例子OpenGLES_Ch5_5用一个新的AGLKTextureTransfromBaseEffect类扩展了GLKit的GLKBaseEffect类,用来显示使用纹理矩阵可能会产生的一些效果。正如本书中的其他AGLK类,如果某天GLKit添加了一个textureMatrix属性,那么应该优先使用苹果的实现。


注意

在例子OpenGLES_Ch5_5中的AGLKTextureTransformBaseEffect类使用了自定 义的OpenGL ES 2.0的Shading Language程序来实现与OpenGL ES 1.x的内置支持相似的纹理矩阵操作。看看AGLKTextureTransformBaseEffect的实现,观察一 下ShadingLanguage程序实际是怎么使用的,找出实现GLKBaseEffec的线索。GLKit的存在是为了让你可以使用常用的OpenGL ES功能实现3D应用,而不需要知道Shading Language的所有细节。


下面来自OpenGLES_Ch5_5的摘录显示了OpenGLES_Ch5_5ViewContollr 的“glkView: drawInRect:”方法,以此来演示利用AGLKTextureTransformBaseEffect的texture-Matrix2d1属性和一个GLKMatrixStack所做的纹理坐标变换。当使用多重纹理时,textureMatrix2d1属性会保存用来变换第二个纹理的纹理坐标的矩阵。多重纹理效果是在3.5节介绍的。AGLKTexture TransformBaseEffect还提供了一个textureMatrix2d0属性,用于使用多重纹理时第一个纹理的纹理坐标的变换。

GLKMatrixStackPush(self.textureMatrixStack);
// Scale and rotate about the center of the texture
GLKMatrixStackTranslate(
        self,textureMatrixStack,
        0.50.5, 0.0);

GLKMatrixStackScale(
        self.textureMatrixStack,
        textureScaleFactor, textureScaleFactor, 1.0);


GLKMatrixStackRotate( // Rotate about z axis
        self.textureMatrixStack,
        GLKMathDegreesToRadians(textureAngle),
        0.0, 0.0, 1.0);

GLKMatrixStackTranslate(
        self.textureMatrixStack,
        -0.5, -0.5, 0.0);


self.baseEffect.textureMatrix2d1 =
                    GLKMatrixStackGetMatrix4 (self.textureMatrixStack);

[self .baseEffect  prepareToDrawMultitextures ];


// Draw triangles using currently bound vertex buffer
[self.vertexBuffer drawArrayWithMode:GL_TRIANGLES
                    startVertexIndex:0
                    numberofVertices:sizeof (vertices) / sizeof (SceneVertex)];


GLKMatrixStackPop(self.textureMatrixStack);
self.baseEffect.textureMatrix2d1 = GLKMatrixStackGetMatrix4( self . textureMatrixStack);

上述代码中的textureScaleFactor和textureAngle变量是由用户界面中的滑动条所控制的。运行例子并通过设置滑动条来测试以观察最终的纹理效果。把对于self.baseEffect.textureMatrix2d1的引用换成self.baseEffect.textureMatrix2d0,以此来改变变 换的纹理映射。

results matching ""

    No results matching ""