【我的安卓進階之旅】Opengl Es(2)投影變換之繪製等腰三角形(附Github地址)

上一篇博客中我們已經繪製出了一個三角形。我們在代碼中修改下三角形的頂點座標,使其成爲一個等腰直角三角形。

    //設置三角形頂點數組,默認按逆時針方向繪製
    public  static float[] triangleCoords = {
            0.0f, 0.5f, 0.0f, // 頂點
            0.0f, -0.5f, 0.0f, // 左下角
            1.0f, -0.5f, 0.0f  // 右下角
    };

運行程序

發現雖然我們相對於座標,我們設置的直角三角形的兩腰是相等的,但是實際上展示出來的卻並不是這樣,(因爲手機屏幕的長寬不一樣導致的)雖然通過計算屏幕比,我們可以把三角形的兩腰計算一下比例,使它們在座標上不等,但是現實出來相等,但是當繪製的圖形比較複雜的話,這個工作量對我們來說實在太龐大了。那麼我們怎麼做呢?答案是,使用變換矩陣,把計算交給OpenGL。

矩陣

在數學中,矩陣(Matrix)是一個按照長方陣列排列的複數或實數集合 ,最早來自於方程組的係數及常數所構成的方陣。這一概念由19世紀英國數學家凱利首先提出。

矩陣常被用於圖像處理、遊戲開發、幾何光學、量子態的線性組合及電子學等多種領域。我們現在相當於是在圖像處理或者遊戲開發的領域來使用矩陣。在大學的高數課中,有學習到矩陣,很多人在大學學習高數、線性代數之類的課程時,總是覺得學習這些東西沒什麼用(我也是)。實際上,這些這是對於程序員,尤其是需要做遊戲開發、圖像視頻處理的程序員來說是非常重要的。扯偏了。。。
在三維圖形學中,一般使用的是4階矩陣。在DirectX中使用的是行向量,如[xyzw][xyzw],所以與矩陣相乘時,矩陣在前,向量在後。關於矩陣的具體知識,博客中不詳細講解,需要了解的同學可以自行查閱。
如果要自己去寫變換的矩陣,然後把矩陣交給OpenGL處理,也是一個比較麻煩的事情,那麼怎麼辦呢?這時候需要用到相機和投影,生成需要的矩陣。

相機和投影

相機

根據現實生活中的經歷我們指導,對一個場景,隨着相機的位置、姿態的不同,拍攝出來的畫面也是不相同。將相機對應於OpenGL的世界,決定相機拍攝的結果(也就是最後屏幕上展示的結果),包括相機位置、相機觀察方向以及相機的UP方向。

  • 相機位置:相機的位置是比較好理解的,就是相機在3D空間裏面的座標點。
  • 相機觀察方向:相機的觀察方向,表示的是相機鏡頭的朝向,你可以朝前拍、朝後拍、也可以朝左朝右,或者其他的方向。
  • 相機UP方向:相機的UP方向,可以理解爲相機頂端指向的方向。比如你把相機斜着拿着,拍出來的照片就是斜着的,你倒着拿着,拍出來的就是倒着的。

在Android OpenGLES程序中,我們可以通過以下方法來進行相機設置:

Matrix.setLookAtM (float[] rm,      //接收相機變換矩陣
                int rmOffset,       //變換矩陣的起始位置(偏移量)
                float eyeX,float eyeY, float eyeZ,   //相機位置
                float centerX,float centerY,float centerZ,  //觀測點位置
                float upX,float upY,float upZ)  //up向量在xyz上的分量
  • 1
  • 2
  • 3
  • 4
  • 5

投影

用相機看到的3D世界,最後還需要呈現到一個2D平面上,這就是投影了。在[【我的android進階之旅】Opengl Es2.0學習前知識預備 ](https://blog.csdn.net/wzy901213/article/details/89492533)也有提到關於投影。Android OpenGLES的世界中,投影有兩種,一種是正交投影,另外一種是透視投影

  • 使用正交投影,物體呈現出來的大小不會隨着其距離視點的遠近而發生變化。在Android OpenGLES程序中,我們可以使用以下方法來設置正交投影:
Matrix.orthoM (float[] m,           //接收正交投影的變換矩陣
                int mOffset,        //變換矩陣的起始位置(偏移量)
                float left,         //相對觀察點近面的左邊距
                float right,        //相對觀察點近面的右邊距
                float bottom,       //相對觀察點近面的下邊距
                float top,          //相對觀察點近面的上邊距
                float near,         //相對觀察點近面距離
                float far)          //相對觀察點遠面距離
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 使用透視投影,物體離視點越遠,呈現出來的越小。離視點越近,呈現出來的越大。。在Android OpenGLES程序中,我們可以使用以下方法來設置透視投影:
Matrix.frustumM (float[] m,         //接收透視投影的變換矩陣
                int mOffset,        //變換矩陣的起始位置(偏移量)
                float left,         //相對觀察點近面的左邊距
                float right,        //相對觀察點近面的右邊距
                float bottom,       //相對觀察點近面的下邊距
                float top,          //相對觀察點近面的上邊距
                float near,         //相對觀察點近面距離
                float far)          //相對觀察點遠面距離
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

使用變換矩陣

實際上相機設置和投影設置並不是真正的設置,而是通過設置參數,得到一個使用相機後頂點座標的變換矩陣,和投影下的頂點座標變換矩陣,我們還需要把矩陣傳入給頂點着色器,在頂點着色器中用傳入的矩陣乘以座標的向量,得到實際展示的座標向量。注意,是矩陣乘以座標向量,不是座標向量乘以矩陣,矩陣乘法是不滿足交換律的
而通過上面的相機設置和投影設置,我們得到的是兩個矩陣,爲了方便,我們需要將相機矩陣和投影矩陣相乘,得到一個實際的變換矩陣,再傳給頂點着色器。矩陣相乘:

Matrix.multiplyMM (float[] result, //接收相乘結果
                int resultOffset,  //接收矩陣的起始位置(偏移量)
                float[] lhs,       //左矩陣
                int lhsOffset,     //左矩陣的起始位置(偏移量)
                float[] rhs,       //右矩陣
                int rhsOffset)     //右矩陣的起始位置(偏移量)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

等腰直角三角形的實現

在上篇博客的基礎上,我們需要做以下步驟即可實現繪製一個等腰直角三角形:

1.修改頂點着色器,增加矩陣變換:

attribute vec4 vPosition;
uniform mat4 vMatrix;
void main() {
    gl_Position = vMatrix*vPosition;
}
  • 1
  • 2
  • 3
  • 4
  • 5

2.設置相機和投影,獲取相機矩陣和投影矩陣,然後用相機矩陣與投影矩陣相乘,得到實際變換矩陣:

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
    //計算寬高比
    float ratio=(float)width/height;
    //設置透視投影
    Matrix.frustumM(mProjectMatrix, 0, -ratio, ratio, -1, 1, 3, 7);
    //設置相機位置
    Matrix.setLookAtM(mViewMatrix, 0, 0, 0, 7.0f, 0f, 0f, 0f, 0f, 1.0f, 0.0f);
    //計算變換矩陣
    Matrix.multiplyMM(mMVPMatrix,0,mProjectMatrix,0,mViewMatrix,0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

3.將變換矩陣傳入頂點着色器:

@Override
public void onDrawFrame(GL10 gl) {
    //將程序加入到OpenGLES2.0環境
    GLES20.glUseProgram(mProgram);
    //獲取變換矩陣vMatrix成員句柄
    mMatrixHandler= GLES20.glGetUniformLocation(mProgram,"vMatrix");
    //指定vMatrix的值
    GLES20.glUniformMatrix4fv(mMatrixHandler,1,false,mMVPMatrix,0);
    //獲取頂點着色器的vPosition成員句柄
    mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
    //啓用三角形頂點的句柄
    GLES20.glEnableVertexAttribArray(mPositionHandle);
    //準備三角形的座標數據
    GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX,
            GLES20.GL_FLOAT, false,
            vertexStride, vertexBuffer);
    //獲取片元着色器的vColor成員的句柄
    mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
    //設置繪製三角形的顏色
    GLES20.glUniform4fv(mColorHandle, 1, color, 0);
    //繪製三角形
    GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
    //禁止頂點數組的句柄
    GLES20.glDisableVertexAttribArray(mPositionHandle);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

運行即可得到一個等腰直角三角形:
這裏寫圖片描述

彩色的三角形

老顯示一個白色的三角形實在太單調了,我們需要讓這個三角形變成彩色的。該怎麼做?頂點着色器是確定頂點位置的,針對每個頂點執行一次。片元着色器是針對片元顏色的,針對每個片元執行一次。而在我們的片元着色器中,我們是直接給片元顏色賦值,外部我們也只傳入了一個顏色值,要使三角形呈現爲彩色,我們需要在不同的片元賦值不同的顏色。爲了處理簡單,我們在上個等腰三角形的實例中修改頂點着色器,達到讓三角形呈現爲彩色的目的:

attribute vec4 vPosition;
uniform mat4 vMatrix;
varying  vec4 vColor;
attribute vec4 aColor;
void main() {
  gl_Position = vMatrix*vPosition;
  vColor=aColor;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

可以看到我們增加了一個aColor(頂點的顏色)作爲輸入量,傳遞給了vColor。vColor的前面有個varying。像attribute、uniform、varying都是在OpenGL的着色器語言中表示限定符,attribute一般用於每個頂點都各不相同的量。uniform一般用於對同一組頂點組成的3D物體中各個頂點都相同的量。varying一般用於從頂點着色器傳入到片元着色器的量。還有個const表示常量。在[【我的android進階之旅】Opengl Es2.0學習前知識預備 ](https://blog.csdn.net/wzy901213/article/details/89492533)
也有相關介紹 修改片元着色器

//片元着色器
    public static final String FRAGMENT_SHADER =
                    "//設置float類型默認精度,頂點着色器默認highp,片元着色器需要用戶聲明\n" +
                    "precision mediump float;" +
                   "//顏色值,varying是從頂點着色器傳遞過來的\n" +
                    "varying vec4 vColor;" +
                    "void main() {" +
                    "//該片元最終顏色值\n" +
                    "  gl_FragColor = vColor;" +
                    "}";

varying一般用於從頂點着色器傳入到片元着色器的量。

然後,我們需要傳入三個不同的頂點顏色到頂點着色器中:

//設置顏色
float color[] = {
        0.0f, 1.0f, 0.0f, 1.0f ,
        1.0f, 0.0f, 0.0f, 1.0f,
        0.0f, 0.0f, 1.0f, 1.0f
};
ByteBuffer dd = ByteBuffer.allocateDirect(
                color.length * 4);
dd.order(ByteOrder.nativeOrder());
FloatBuffer colorBuffer = dd.asFloatBuffer();
colorBuffer.put(color);
colorBuffer.position(0);
//獲取片元着色器的vColor成員的句柄
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");
//設置繪製三角形的顏色
GLES20.glEnableVertexAttribArray(mColorHandle);
GLES20.glVertexAttribPointer(mColorHandle,4,
        GLES20.GL_FLOAT,false,
        0,colorBuffer);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

運行得到一個彩色的等腰三角形:
這裏寫圖片描述

源碼

附上github下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章