當前位置:網站首頁>五、OpenGL ES 三維圖形的初探

五、OpenGL ES 三維圖形的初探

2022-01-28 00:14:35 mChenys

想象一下你站在一個桌子前面,從你的角度觀察桌子,你會看到你那端的桌子會顯得較大,因為你是從一個角度向下觀察的,而不是從空中俯視的。接下來我會繼續改造上一篇文章的圖形,讓他看起來有三維的效果。

一、三維的藝術

藝術家在作畫的時候可以把一個平面的二維圖形使用一些技巧(線性投影)繪制成三維的圖形,它的原理就是在一個想象中的消失點處把並行的線段聚合在一起,從而創建出立體的幻想。例如當我們站在鐵軌上向鐵軌的遠處看時,會發現遠處的鐵軌消失在地平線上的一個點上了,鐵軌上的枕木離我們越遠,它看起來就越小,如下圖所示:

二、裁剪空間

當頂點著色器把一個值寫到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)
    }
}

 

 

 

 

 

版權聲明
本文為[mChenys]所創,轉載請帶上原文鏈接,感謝
https://cht.chowdera.com/2022/01/202201280014346593.html

隨機推薦