五、OpenGL ES 三维图形的初探

想象一下你站在一个桌子前面,从你的角度观察桌子,你会看到你那端的桌子会显得较大,因为你是从一个角度向下观察的,而不是从空中俯视的。接下来我会继续改造上一篇文章的图形,让他看起来有三维的效果。

一、三维的艺术

艺术家在作画的时候可以把一个平面的二维图形使用一些技巧(线性投影)绘制成三维的图形,它的原理就是在一个想象中的消失点处把并行的线段聚合在一起,从而创建出立体的幻想。例如当我们站在铁轨上向铁轨的远处看时,会发现远处的铁轨消失在地平线上的一个点上了,铁轨上的枕木离我们越远,它看起来就越小,如下图所示:

二、裁剪空间

当顶点着色器把一个值写到gl_position的时候,OpenGL期望这个位置是裁剪空间中的,裁剪空间背后的逻辑非常简单:
对于给定的位置,它的x、y以及z分量都需要在哪个位置的-w和w之间,假设这个位置的w是1,那么其x,y和z分量都需要在
[-1,1]的范围内,超出该范围的事物在屏幕上都是不可见的。

三、透视除法

在一个顶点位置成为一个归一化设备坐标之前,OpenGL实际上执行了一个额外的步骤,它被称为透视除法。
透视除法之后,那个位置就在归一化设备坐标中了,不管渲染区域的大小和形状,对于其中的每个可视坐标x、y和z分量的取值都位于[-1,1]的范围内。
为了在屏幕上创建三维幻象,OpenGL 会把每个gl_Position的x、y和z分量都除以它的w分量。当w分量用来表示距离的时候,它使得较远处的物体被移动到距离渲染区域中心更近的地方,这个中心的作用就像一个消失点,中心点位置是(0,0,0)。OpenGL正是用这种方式欺骗我们的,使我们看见一个三维的场景。
例如有2个坐标(1,1,1,1)和(1,1,1,2),在OpenGL把这些作为归一化设备坐标使用之前,它会进行透视除法,将前3个分量都除以w的值
因此(1/1,1/1,1/1)变成(1,1,1),以及(1/2,1/2,1/2)变成了(0.5,0.5,0.5),可见有较大的w值的坐标被移动到距离(0,0,0)更近的位置了
(0,0,0)就是归一化坐标系统里渲染区域的中心。在OpenGL中,三维效果是线性的,并且是沿着直线完成的。

四、视口变换

在我们看到屏幕的显示结果之前,OpenGL需要把归一化设备坐标x和y分量映射到屏幕的一个区域内,这个区域是操作系统预留出来用于显示的,称为视口(viewport);这些被映射的坐标被称为窗口坐标,除了要告诉OpenGL怎么映射之外,我们不需要关心这些坐标,因为在代码中我们已经在onSurfaceChanged中用glViewport方法告诉了OpenGL了。
当OpenGL做映射的时候,会把(-1,-1,-1)到(1,1,1)的范围映射到那个显示预留的窗口上,而范围之外的坐标将会被裁剪掉。

五、添加w分量创建三维图

现在我们需要使用w分量加入到顶点的数据数组中,如下图所示:

矩形区域的每个顶点都有4个分量,x、y、z、w,其中w用来控制距离,因此代码中的tableVerticesWithTriangles数组就需要做下调整了。

val tableVerticesWithTriangles = floatArrayOf(
    // Order of coordinates: X, Y, Z, W, R, G, B
    // Triangle Fan
    0f, 0f, 0f, 1.5f, 1f, 1f, 1f,
    -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
    0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
    0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
    -0.5f, 0.8f, 0f, 2f, 0.7f, 0.7f, 0.7f,
    -0.5f, -0.8f, 0f, 1f, 0.7f, 0.7f, 0.7f,
    // Line 1
    -0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
    0.5f, 0f, 0f, 1.5f, 1f, 0f, 0f,
    // Mallets
    0f, -0.4f, 0f, 1.25f, 0f, 0f, 1f,
    0f, 0.4f, 0f, 1.75f, 1f, 0f, 0f
)

同时还需要修改POSITION_COMPONENT_COUNT的值,将原来的2改成4,表示现在每个顶点有4个分量了。
由于我把矩形的底部顶点的w分量设置为1,顶部的设置为2,这样顶部的坐标在进行透视除法之后,就会更加靠近中心点的位置了,运行起来的效果就如下图所示:

六、透视投影

在介绍透视投影之前,我在上一篇文章中用过一个正交投影矩阵来处理屏幕的宽高比的问题,通过调整显示区域的宽度和高度使之变换为归一化设置的坐标。
在计算机三维图像中,投影可以看作是一种将三维坐标变换为二维坐标的方法,常用到的有正交投影和透视投影。正交投影多用于三维健模,正交投影的物体不会随距离观测点的位置而大小发生变化;透视投影则由于和人的视觉系统相似,多用于在二维平面中对三维世界的呈现,透视投影距离观测点越远,物体越小,距离观测点越近,物体越大。

透视投影(Perspective Projection)是为了获得接近真实三维物体的视觉效果而在二维的纸或者画布平面上绘图或者渲染的一种方法,也称为透视图 。它具有消失感、距离感、相同大小的形体呈现出有规律的变化等一系列的透视特性,能逼真地反映形体的空间形象。透视投影通常用于动画、视觉仿真以及其它许多具有真实性反映的方面。

6.1视椎体

一旦使用了投影矩阵,场景中的并行线就会在屏幕上的一个消失点出汇聚在一起,当物体离得越来越远,它会变得越来越小。如下图所示,这个形状叫做视椎体

这个观察空间是由一个透视投影矩阵和投影除法创建的,简单来说,视椎体只是一个立方体,其远端比近端大,从而使其变成了一个被截断的金字塔。两端的大小差别越大,观察的范围就越宽,能看到的也就更多。
一个视椎体有一个焦点,这个焦点就是从椎体较大端向较小端扩展出来的那些直线,一直向前通过较小端直到它们汇聚到一起的那个点。
当你用透视投影观察一个场景的时候,那个场景看上去就像你的眼睛放在了焦点位置上,焦点和视椎体小端的距离被称为焦距,它影响视椎体小端和大端的比例,以及其对应的视野,如下图所示就是从焦点处观察的到画面。

可见从焦点看上去视椎体两端在屏幕上占据了同样大小的空间,视椎体的远端虽然较大,但是它也比较远,它任然占据同样大小的空间,这与我们看到日食是一样的道理,月亮虽然比太阳小很多,但是因为它近了很多,使得它看上去很大,大到可以遮盖太阳的光辉。

来看看透视投影模型

设视点E位于原点,视平面P垂直于Z轴,且四边分别平行于x轴和y轴,如上图所示,我们将该模型称为透视投影的标准模型,其中视椎体的近平面离视点的距离为n,远平面离视点的距离为f,且一般取近平面为视平面。基本透视投影模型对视点E的位置和视平面P的大小都没有限制,只要视点不在视平面上即可。P无限大只适用于理论分析,实际情况总是限定P为一定大小的矩形平面,透视结果位于P之外的透视结果将被裁减。可以想象视平面为透明的玻璃窗,视点为玻璃窗前的观察者,观察者透过玻璃窗看到的外部世界,便等同于外部世界在玻璃窗上的透视投影。

6.2 透视投影矩阵

透视投影矩阵需要和透视除法一起发挥作用,投影矩阵不能自己做透视除法,而透视除法需要某些东西才能起作用。如果一个物体向屏幕中心移动,当它理我们越来越远时,它的大小也越来越小,因此,投影矩阵最重要的任务就是为w分量产生正确的值,这样当OpenGL做透视除法的时候,远处的物体才会看起来比近处的物体小。能实现这些方法之一是利用z分量,把他作为物体与焦点的距离并把这个距离映射到w分量,这个距离越大,w值就越大,物体就越小。

让我们来看一个更加通用的投影矩阵,它可以调整视野以及屏幕的宽高比

其中
1、变量a表示焦点,由 1/tan(视野/2)求得,tan表示正切函数,视野必须小于180度,比如一个90度的视野,它的焦距就是1/tan45,也就是1/1=1;
2、aspect表示屏幕宽高比,等于宽度/高度;
3、f表示到远处平面的距离,必须是正值且大于到近处平面的距离;
4、n表示到近处平面的距离,必须是正值。比如,如果此值被设为1,那近处平面就位于一个z值为-1的处。

当视野变小,tan(视野/2)就变小,焦距就会变长,可以映射到归一化坐标中[-1,1]范围内的x和y值的范围就越窄(小)。

上图左边的是90°视角看到的内容,右边是45°视野看到的内容。

七、在代码中使用投影矩阵

创建MatrixHelper工具类

object MatrixHelper {

    /**
     * 这个方法用来创建投影矩阵,和Android的perspectiveM相似,但是perspectiveM是高版本才有的
     * @param m 目标矩阵
     * @param yFovInDegrees 视野
     * @param aspect 宽高比
     * @param n 表示到近处平面的距离
     * @param f 表示到远处屏幕的距离
     */
    fun perspectiveM(m: FloatArray, yFovInDegrees: Float, aspect: Float, n: Float, f: Float) {
        // 视野
        val angleInRadians = (yFovInDegrees * Math.PI / 180.0).toFloat()
        // 首先计算焦距
        val a = (1.0 / tan(angleInRadians / 2.0)).toFloat()
        // 给矩阵填充值,OpenGL把矩阵数据按照以“列”为主的顺序存储,意味着我们一次写一列数据,而不是一行。
        // 第一列
        m[0] = a / aspect
        m[1] = 0f
        m[2] = 0f
        m[3] = 0f

        // 第二列
        m[4] = 0f
        m[5] = a
        m[6] = 0f
        m[7] = 0f

        // 第三列
        m[8] = 0f
        m[9] = 0f
        m[10] = -((f + n) / (f - n))
        m[11] = -1f

        // 第四列
        m[12] = 0f
        m[13] = 0f
        m[14] = -((2f * f * n) / (f - n))
        m[15] = 0f

    }
}

上面的代码就是完全按照6.2的矩阵图来填充数据的,只是填充的顺序是按列进行填充的。

然后回到自定义的Renderer类,并删除onSurfaceChanged内的所有代码,只保留glViewport()的调用,然后使用MatrixHelper类创建投影矩阵并赋值给projectionMatrix矩阵数组

// 调用正交投影矩阵帮助类创建投影矩阵
MatrixHelper.perspectiveM(
    projectionMatrix!!,
    45f, // 这里使用45°视野创建一个透视投影
    width.toFloat() / height.toFloat(),
    1f, // 视点离近平面的距离,相当于z的起始坐标是-1
    10f // 视点离远平面的距离,相当于z的终止坐标是-10
)

上面代码的意思是用45°的视野创建一个透视投影,这个视椎体从z值为-1的位置开始,在-10的位置结束,也就是说视点到近平面的距离是1,到远平面的距离是10。

然后还需要修改tableVerticesWithTriangles 数组,将顶点坐标的z和w分量去掉,同时将POSITION_COMPONENT_COUNT的值修改为2,表示现在只需要用到2个分量来表示坐标,修改后的数组如下:

val tableVerticesWithTriangles = floatArrayOf(
    // Order of coordinates: X, Y, R, G, B
    // Triangle Fan
    0f, 0f, 1f, 1f, 1f, // 中心顶点,白色
    -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,  // 顶点2
    0.5f, -0.8f, 0.7f, 0.7f, 0.7f, // 顶点3
    0.5f, 0.8f, 0.7f, 0.7f, 0.7f,// 顶点4
    -0.5f, 0.8f, 0.7f, 0.7f, 0.7f,// 顶点5
    -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,  // 顶点2(为了能让1,5,2组成的三角形闭合,所以还是要重复定义顶点2)
    -0.5f, 0f, 1f, 0f, 0f,
    0.5f, 0f, 1f, 0f, 0f,
    // Mallets
    0f, -0.4f, 0f, 0f, 1f,
    0f, 0.4f, 1f, 0f, 0f
)

此时运行代码,会发现什么也看不到,只会看到黑屏,这是因为没有给图形指定z值的位置,那么默认的z值就是0,也就是位于视点的位置上,而我们定义的视椎体是从z值为-1的位置开始的,因此我们还需要把图形移动到那个范围[-1,-10]内,否则是无法看到的画面的,当然你可以修改顶点的坐标设置一个z值,但是我不推荐这种方式,这样相当于硬编码z的值了。我们可以使用平移矩阵来把图形移动到可视范围内。

八、利用模型矩阵移动图形

我们需要创建一个新的矩阵,就命名为模型矩阵吧,对应的数组是modelMatrix 

 // 定义模型矩阵用来移动物体的
 private var modelMatrix = FloatArray(16)

然后回到onSurfaceChanged方法内,加入如下代码

 setIdentityM(modelMatrix, 0)// 先将modelMatrix填充为单位矩阵
 translateM(modelMatrix, 0, 0f, 0f, -2f);// 然后基于单位矩阵修改z的值,沿z轴平移-2

接下来的操作就是要用投影矩阵与这个模型矩阵相乘,这样投影矩阵就会沿z轴进行平移,让视图出现在可视范围内。
现在我们需要做一个选择,我们还需要把这个模型矩阵应用于每个顶点上,有两种方案:
方案一:
先给顶点着色器新增一个额外的矩阵,然后把每个顶点都与这个模型矩阵相乘,让他们沿着z轴负方向移动2个单位,然后再把每个顶点和投影矩阵相乘,这样OpenGL就可以做透视除法,并把这些顶点变化到归一化设备坐标了。
方案二(推荐,本篇文章采用的也是这种):
我们把模型矩阵先和投影矩阵相乘,得到一个新的矩阵,然后在把这个矩阵传递给顶点着色器,这样我们的着色器就不需要额外定义矩阵了。

8.1 矩阵的乘法

矩阵与矩阵相乘的原理和矩阵与向量相乘的原理相似,如下图所示:

其实就是新的矩阵的每个元素的值=第一个矩阵的某一行与第二个矩阵的某一列的乘积之和,如上图所示为了求新矩阵的第一个元素(第一行,第一列),我们需要将第一个矩阵的第一行的每个元素与第二个矩阵的第一列每个元素相乘并求和。要求新矩阵的第二个元素(第一行,第二列),那就要将第一个矩阵的第一行的每个元素与第二个矩阵的第二列每个元素相乘并求和,其他的以此类推。

8.2 矩阵乘法的顺序

既然我们知道了如何把两个矩阵相乘了,那么还需要注意一点,当相乘的2个矩阵的位置互换时,结果是完全不一样的,例如:

 反过来相乘就是另一个结果了

可以看到此时的结果和第一次的结果刚好上下颠倒,左右颠倒了。
为了弄清楚我们应该使用哪种顺序,让我们看一下只使用投影矩阵的数学运算:
vertex(clip) = ProjectionMatrix * vertex(eye)
其中,vertex(eye)代表场景中的顶点在与投影矩阵相乘之前的位置。

一旦加入模型矩阵来移动图形,那么这个数学公式如下:
vertex(eye) = ModelMatrix * vertex(model)
其中,vertex(model)代表顶点在被模型矩阵放进场景中之前的位置。

合起来的公式如下:
vertex(clip) = ProjectionMatrix *ModelMatrix * vertex(model)
为了用一个矩阵替代这两个矩阵,我们不得不把投影矩阵乘以模型矩阵,就是把投影矩阵放左边,把模型矩阵放右边。

因此,我们还需要在onSurfaceChanged结尾处加入如下代码:

// 定义临时的矩阵,用来存储投影矩阵和平移矩阵相乘的结果
val temp = FloatArray(16)
// 将投影矩阵和模型矩阵相乘的结果赋给临时数组temp中
multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0)
// 然后再拷贝到projectionMatrix中,此时的projectionMatrix就包含了投影矩阵和模型矩阵的组合效果了
System.arraycopy(temp, 0, projectionMatrix, 0, temp.size)

ok,现在再运行下程序,将会看到下图效果:

可以看到图形显示出来了,因为我们已经把图形移动到了z轴的可视范围内了,但是看到的是俯瞰的效果,这个并不是我们想要的。我们需要对图形进行旋转操作。

九、增加旋转

9.1 旋转的方向

我们需要弄清楚需要围绕那个轴旋转以及旋转多少度,要搞清楚一个物体怎么围绕一个给定的轴旋转,我们使用右手坐标系统规则:伸出你的右手,握拳,让大母指指向正轴的方向,倘若是一个正角度的旋转,蜷曲的手指会告诉你一个物体是怎样围绕那个轴旋转的。

想象一下分别用x轴、y轴、和z轴试一下这个旋转,假设围绕着y轴旋转,桌子会绕着它的顶端底端水平旋转。如果绕着z轴旋转,桌子会在一个圆圈内旋转。而我们这里是要让图形绕着x轴向视点方向旋转,因为这样会让图形看起来更有层次感。

9.2 旋转矩阵

我们将使用一个旋转矩阵去做实际的旋转,旋转矩阵使用正弦和余旋三角函数把旋转角度换成缩放因子,下面就是绕x轴旋转所用的矩阵定义:

然后是绕y轴旋转所用的矩阵:

最后是绕z轴旋转用的矩阵:

以绕x轴旋转为例,假设旋转90°,那么按照定义,我们将得到一个如下的旋转矩阵

让我们把这个矩阵与(0,1,0,1)向量相乘,将会得到如下结果:

结果变由(0,1,0,1)变成了(0,0,1,1),坐标从y轴跑到了z轴上了。

9.2 在代码中加入旋转

回到onSurfaceChanged方法,修改平移的操作,加入如下代码

translateM(modelMatrix, 0, 0f, 0f, -2.5f) // 修改平移为沿z轴移动-2.5f
rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f) // 旋转-60°

可以看到我把之前的沿z轴平移-2.0f改成-2.5f了,因为一旦我们进行了旋转操作,它的底部会离我们视点变近了,因此我们需要再往远处移动一些,然后再绕x轴旋转-60°,现在在运行看看效果:

切换到横屏的效果:

完整的自意义Renderer代码如下:

package com.example.openglstudy.demo6

import android.content.Context
import android.opengl.GLES20.*
import android.opengl.GLSurfaceView
import android.opengl.Matrix.*
import com.example.openglstudy.R
import com.example.openglstudy.util.LoggerConfig
import com.example.openglstudy.util.MatrixHelper
import com.example.openglstudy.util.ShaderHelper.compileFragmentShader
import com.example.openglstudy.util.ShaderHelper.compileVertexShader
import com.example.openglstudy.util.ShaderHelper.linkProgram
import com.example.openglstudy.util.ShaderHelper.validateProgram
import com.example.openglstudy.util.TextResourceReader.readTextFileFromResource
import java.nio.ByteBuffer
import java.nio.ByteOrder
import java.nio.FloatBuffer
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10

class AirHockeyRenderer(private val context: Context) : GLSurfaceView.Renderer {
    private val vertexData: FloatBuffer
    private var program = 0
    private var aPositionLocation = 0
    private var aColorLocation = 0

    // 定义投影矩阵
    private val projectionMatrix: FloatArray? = FloatArray(16)

    // 定义保存矩阵unifrom的位置变量
    private var uMatrixLocation: Int = 0

    // 定义模型矩阵用来移动物体的
    private var modelMatrix = FloatArray(16)

    companion object {
        private const val A_POSITION = "a_Position"// a_Position名称对应attribute vec4 a_Position;中的名字
        private const val A_COLOR = "a_Color"// a_Color的名称对应attribute vec4 a_Color;
//        private const val POSITION_COMPONENT_COUNT = 4 // 顶点分量
        private const val POSITION_COMPONENT_COUNT = 2 // 顶点分量
        private const val COLOR_COMPONENT_COUNT = 3 // 颜色分量
        private const val BYTES_PER_FLOAT = 4// 每个浮点数占的字节大小

        // 每个顶点的分量X,Y,R,G,B(包括位置分量和颜色分量)*每个分量占用的字节大小
        private const val STRIDE =
            (POSITION_COMPONENT_COUNT + COLOR_COMPONENT_COUNT) * BYTES_PER_FLOAT

        // 定义保存顶点着色器中定义的那个新的uniform的名字
        private const val U_MATRIX = "u_Matrix"
    }

    init {
       
        val tableVerticesWithTriangles = floatArrayOf(
            // Order of coordinates: X, Y, R, G, B
            // Triangle Fan
            0f, 0f, 1f, 1f, 1f, // 中心顶点,白色
            -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,  // 顶点2
            0.5f, -0.8f, 0.7f, 0.7f, 0.7f, // 顶点3
            0.5f, 0.8f, 0.7f, 0.7f, 0.7f,// 顶点4
            -0.5f, 0.8f, 0.7f, 0.7f, 0.7f,// 顶点5
            -0.5f, -0.8f, 0.7f, 0.7f, 0.7f,  // 顶点2(为了能让1,5,2组成的三角形闭合,所以还是要重复定义顶点2)
            -0.5f, 0f, 1f, 0f, 0f,
            0.5f, 0f, 1f, 0f, 0f,
            // Mallets
            0f, -0.4f, 0f, 0f, 1f,
            0f, 0.4f, 1f, 0f, 0f
        )

        vertexData = ByteBuffer
            .allocateDirect(tableVerticesWithTriangles.size * BYTES_PER_FLOAT)
            .order(ByteOrder.nativeOrder()).asFloatBuffer()
        vertexData.put(tableVerticesWithTriangles)
    }

    override fun onSurfaceCreated(glUnused: GL10, config: EGLConfig) {
        glClearColor(0.0f, 0.0f, 0.0f, 0.0f)
        // 读取着色器的代码
        val vertexShaderSource = readTextFileFromResource(context, R.raw.simple_vertex_shader3)
        val fragmentShaderSource = readTextFileFromResource(context, R.raw.simple_fragment_shader2)

        // 编译着色器源码
        val vertexShader = compileVertexShader(vertexShaderSource)
        val fragmentShader = compileFragmentShader(fragmentShaderSource)

        // 链接程序并返回程序对象
        program = linkProgram(vertexShader, fragmentShader)
        if (LoggerConfig.ON) {
            validateProgram(program)
        }

        // 使用OpenGL程序
        glUseProgram(program)

        //获取matrix属性
        uMatrixLocation = glGetUniformLocation(program, U_MATRIX)
        // 获取位置属性
        aPositionLocation = glGetAttribLocation(program, A_POSITION)
        // 获取颜色属性
        aColorLocation = glGetAttribLocation(program, A_COLOR)

        // 绑定位置数据
        vertexData.position(0)
        glVertexAttribPointer(
            aPositionLocation,
            POSITION_COMPONENT_COUNT,
            GL_FLOAT,
            false,
            STRIDE,
            vertexData
        )
        glEnableVertexAttribArray(aPositionLocation)

        // 绑定颜色数据
        vertexData.position(POSITION_COMPONENT_COUNT)
        glVertexAttribPointer(
            aColorLocation,
            COLOR_COMPONENT_COUNT,// 第一个颜色是从缓冲区位置2开始
            GL_FLOAT,
            false,
            STRIDE,
            vertexData
        )
        glEnableVertexAttribArray(aColorLocation)
    }


    override fun onSurfaceChanged(glUnused: GL10, width: Int, height: Int) {
        // Set the OpenGL viewport to fill the entire surface.
        glViewport(0, 0, width, height)
        // 调用投影矩阵帮助类创建透视投影矩阵,设置投影的z值取值范围为[-1,-10]
        MatrixHelper.perspectiveM(
    	projectionMatrix!!,
    	45f, // 这里使用45°视野创建一个透视投影
    	width.toFloat() / height.toFloat(),
    	1f, // 视点离近平面的距离,相当于z的起始坐标是-1
    	10f // 视点离远平面的距离,相当于z的终止坐标是-10
         )

        // 平移操作
        setIdentityM(modelMatrix, 0)// 先将modelMatrix设置为单位矩阵
        translateM(modelMatrix, 0, 0f, 0f, -2.5f)// 然后基于单位矩阵修改z的值,沿z轴平移-2.5f
       
        // 加入旋转
        rotateM(modelMatrix, 0, -60f, 1f, 0f, 0f);

        val temp = FloatArray(16)
        // 将投影矩阵和模型矩阵相乘的结果赋给临时数组temp中
        multiplyMM(temp, 0, projectionMatrix, 0, modelMatrix, 0)
        // 然后再拷贝到projectionMatrix中,此时的projectionMatrix就包含了投影矩阵和模型矩阵的组合效果了
        System.arraycopy(temp, 0, projectionMatrix, 0, temp.size)

    }


    override fun onDrawFrame(glUnused: GL10) {
        // Clear the rendering surface.
        glClear(GL_COLOR_BUFFER_BIT)

        // 给着色器传递那个投影矩阵
        glUniformMatrix4fv(uMatrixLocation, 1, false, projectionMatrix, 0)

        // 现在不需要调用glUniform4f,因为我们已经将顶点数据和a_Color关联起来了,只需要调用glDrawArrays即可,
        // OpenGL会自动从顶点数据读入颜色属性
        // Draw the table.
        glDrawArrays(GL_TRIANGLE_FAN, 0, 6)

        // Draw the center dividing line.
        glDrawArrays(GL_LINES, 6, 2)

        // Draw the first mallet.
        glDrawArrays(GL_POINTS, 8, 1)

        // Draw the second mallet.
        glDrawArrays(GL_POINTS, 9, 1)
    }
}

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值