Android多媒體之GLES2戰記第四集--移形換影

張風捷特烈發表於2019-01-15

視野限制了人對這個宇宙的認知,但沒有視野,人將會一無所知

上集說到勇者墜入黑暗之淵,憑藉對世界的認知構建出了世界系
到此為止,OpenGL的世界觀已經映入腦海,新手十二副本已經通過
接下來等待他們的將是什麼,普通副本開啟....


普通副本一:斗轉星移

第一關卡:繪製矩形

這關簡單,回頭看看,是不是感覺清晰了許多,下面列一下關鍵點,其他不說了
這個矩形可不是一開始摸黑瞎畫的,而是軸系下可感的矩形,地址:matrix.MatrixRectangle
視角移回正方向0f, 0f, -6,這時候畫出的是後面

軸系下的矩形.png

//頂點座標
private float mVertex[] = {   //以逆時針順序
        -1f, 1f, 1.0f, // p0
        -1f, -1f, 1.0f, // p1
        1f, -1f, 1.0f, // p2
        1f, 1f, 1.0f, //p3
};

//索引陣列
private short[] idx = {
        0, 1, 3,
        1, 2, 3
};

//頂點顏色
float colors[] = new float[]{
        1f, 1f, 0.0f, 1f,//黃
        0.05882353f, 0.09411765f, 0.9372549f,1f,//藍
        0.19607843f, 1.0f, 0.02745098f, 1f,//綠
        1.0f, 0.0f, 1.0f,1f,//紫色
};
複製程式碼

2.第二關卡:封裝矩陣變換

檢視的矩陣變換和投影矩陣感覺在WorldRenderer裡也有點麻煩
封裝一下吧,還是那四個矩陣

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/14/014:11:29<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:矩陣變化棧
 */

public class MatrixStack {
    private static float[] mProjectionMatrix = new float[16];//投影矩陣
    private static float[] mViewMatrix = new float[16];//相機矩陣
    private static float[] mOpMatrix;//變換矩陣
    //獲取具體物體的總變換矩陣
    private static float[] mMVPMatrix = new float[16];

    /**
     * 獲取不變換初始矩陣
     */
    public static void reset() {
        //mOpMatrix轉化為單位陣
        mOpMatrix = new float[]{
                1, 0, 0, 0,
                0, 1, 0, 0,
                0, 0, 1, 0,
                0, 0, 0, 1,
        };
    }
    /**
     * 設定沿xyz軸移動
     *
     * @param x 移動的 x 分量
     * @param y 移動的 y 分量
     * @param z 移動的 z 分量
     */
    public static void translate(float x, float y, float z) {
        Matrix.translateM(mOpMatrix, 0, x, y, z);
    }

    /**
     * 設定沿(x,y,z)點旋轉
     *
     * @param deg 角度
     * @param x   旋轉點的 x 分量
     * @param y   旋轉點的 y 分量
     * @param z   旋轉點的 z 分量
     */
    public static void rotate(float deg, float x, float y, float z) {
        Matrix.rotateM(mOpMatrix, 0, deg, x, y, z);
    }

    /**
     * 設定縮放
     * @param x   縮放的 x 分量
     * @param y   縮放的 y 分量
     * @param z   縮放的 z 分量
     */
    public static void scale(float x, float y, float z) {
        Matrix.scaleM(mOpMatrix, 0, x, y, z);
    }

    /**
     * 相機的位置
     * @param cx  攝像機位置x
     * @param cy  攝像機位置y
     * @param cz  攝像機位置z
     * @param tx  攝像機目標點x
     * @param ty  攝像機目標點y
     * @param tz  攝像機目標點z
     * @param upx 攝像機UP向量X分量
     * @param upy 攝像機UP向量Y分量
     * @param upz 攝像機UP向量Z分量
     */
    public static void lookAt(float cx, float cy, float cz,
                              float tx, float ty, float tz,
                              float upx, float upy, float upz) {
        Matrix.setLookAtM(mViewMatrix, 0,
                cx, cy, cz,
                tx, ty, tz,
                upx, upy, upz);
    }

    /**
     *  設定透視投影引數
     * @param left   near面的left
     * @param right  near面的right
     * @param bottom near面的bottom
     * @param top    near面的top
     * @param near   near面距頂點長
     * @param far    far面距頂點長
     */
    public static void frustum(
            float left, float right, float bottom,
            float top, float near, float far) {
        Matrix.frustumM(mProjectionMatrix, 0,
                left, right, bottom,
                top, near, far);
    }
    
    /**
     * 檢視棧頂的變換矩陣
     *
     * @return mMVPMatrix
     */
    public static float[] peek() {
        submit();
        return mMVPMatrix;
    }
    
    /**
     * 提交變換
     */
    private static void submit() {
        Matrix.multiplyMM(
                mMVPMatrix, 0,
                mViewMatrix, 0,
                mOpMatrix, 0);

        Matrix.multiplyMM(
                mMVPMatrix, 0,
                mProjectionMatrix, 0,
                mMVPMatrix, 0);
    }

    //獲取具體物體的變換矩陣
    public static float[] getOpMatrix() {
        return mOpMatrix;
    }
}
複製程式碼

MatrixStack使用

---->[WorldRenderer#onSurfaceChanged]------------
MatrixStack.frustum(
        -ratio, ratio, -1, 1,
        3, 9);
MatrixStack.lookAt(2, 2, -6,
        0f, 0f, 0f,
        0f, 1.0f, 0.0f);
MatrixStack.initStack();

---->[WorldRenderer#onDrawFrame]------------
MatrixStack.rotate(currDeg, 0, 1, 0);
mWorldShape.draw(MatrixStack.peek());
複製程式碼

加面旋轉.gif


3.第三關卡:操作矩陣的狀態棧

想一下,如果我平移畫一個立方,mOpMatrix會變化,
我再平移畫一個立方時mOpMatrix會在上一個mOpMatrix的基礎上進行變換
這種情況下我們是希望mOpMatrix在畫完後回到之前狀態的,這就涉及棧的概念
此處沒用Java的Stack類,因為元素是操作矩陣,即float[],不好放

3.1:沒有恢復狀態時

一個x方向-1.5,另一個x方向+1.5

沒有恢復狀態.png

MatrixStack.translate(-1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.translate(1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
複製程式碼

3.2:MatrixStack新增恢復機制
//預設棧深為10,棧中元素為float[16]--即變換矩陣
private static float[][] mStack = new float[10][16];
private static int stackTop = -1;//棧頂無元素時為-1

/**
 * 矩陣操作準備入棧---儲存mOpMatrix狀態
 */
public static void save() {
    stackTop++;//棧頂+1
    //op矩陣入棧頂
    System.arraycopy(mOpMatrix, 0, mStack[stackTop], 0, 16);
}

/**
 * 棧頂出棧--恢復mOpMatrix
 */
public static void restore() {
    System.arraycopy(mStack[stackTop], 0, mOpMatrix, 0, 16);
    stackTop--;//棧頂下移
}
複製程式碼

3.3:使用狀態恢復機制

一個x方向-1.5,另一個x方向+1.5,這才是我們想要的效果

有恢復狀態.png

MatrixStack.save();
MatrixStack.translate(-1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();

MatrixStack.save();
MatrixStack.translate(1.5f, 0, 0);
mWorldShape.draw(MatrixStack.peek());
MatrixStack.restore();
複製程式碼

4.第四關卡:總結移動旋轉縮放

綜合變換.gif

4.1:注意setRotateMrotateM的區別

一開始寫成setRotateM了,效果疊加不上,然後debug一下,發現:
原始碼中setRotateM會將一些之置零或1,也就是重置之前的變換,所以

public static void setRotateM(float[] rm, int rmOffset,
        float a, float x, float y, float z) {
    rm[rmOffset + 3] = 0;
    rm[rmOffset + 7] = 0;
    rm[rmOffset + 11]= 0;
    rm[rmOffset + 12]= 0;
    rm[rmOffset + 13]= 0;
    rm[rmOffset + 14]= 0;
    rm[rmOffset + 15]= 1;
複製程式碼

4.2.使用:
 MatrixStack.save();
 MatrixStack.translate(-1.5f, 0, 0);
 MatrixStack.rotate(currDeg, -1.5f, 0, 0);
 mWorldShape.draw(MatrixStack.peek());
 MatrixStack.restore();
 
 MatrixStack.save();
 MatrixStack.translate(1.5f, 0, 0);
 MatrixStack.rotate(currDeg, 0, 1, 0);
 MatrixStack.scale(0.5f,1,0.5f);
 mWorldShape.draw(MatrixStack.peek());
 MatrixStack.restore();
複製程式碼

普通副本二:變心之陣

剛才我們用的是Matrix自帶的變換方法,自帶的靈活性肯定欠佳
下面我們來看一下這16個數字是怎麼讓圖形變換的

1.第一關卡:Matrix.translateM分析

怎麼分析呢?廢話,當然是看原始碼了

/**
 * Translates matrix m by x, y, and z in place.
 *
 * @param m matrix
 * @param mOffset index into m where the matrix starts
 * @param x translation factor x
 * @param y translation factor y
 * @param z translation factor z
 */
public static void translateM(
        float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
    }
}
複製程式碼

mOffset可以看出是索引的偏移量,正常情況下都是0,所以不管他
核心的就是四個for內語句,i從0~3,然後就是這句迷之程式碼

m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;

mOffset為0時,簡化一下:
m[12 + i] += m[i] * x + m[4 + i] * y + m[8 + i] * z;

感覺還是迷之程式碼的,沒關係,再分析一下
i=0:  m[12] += m[0]*x + m[4]*y + m[8]*z
i=1:  m[13] += m[1]*x + m[5]*y + m[9]*z
i=2:  m[14] += m[2]*x + m[6]*y + m[10]*z
i=3:  m[15] += m[3]*x + m[7]*y + m[11]*z

Why ?
複製程式碼

2.第二關卡:座標變換的源頭

所以的根源在於這句話,它代表了什麼意思?
注意:OpenGL 中的向量是列主元,也就是豎著排

座標變換.png

IMG20190114155647.jpg


然後算出gl_Position的結果會是一個四維向量
這也就是m12,m13,m14,m15為什麼特別,m0,m1,m2,m3為什麼和x息息相關

結果.png

再換個角度來看,gl_Position中的四個維度分別代表:x,y,z,w

gl_Position.x = m0*x + m4*y + m8*z + m12
gl_Position.y = m1*x + m5*y + m9*z + m13
gl_Position.z = m2*x + m6*y + m10*z + m14
 
這也說明 m12 m13 m14 三個數分別控制x,y,z的位移 
複製程式碼

現在再看translateM,可以看出它的作用是改變第四行
我們傳入的是行向量,傳入渲染器中變成列向量,相當於轉置,
也就是處理時uMVPMatrix的第四列,這樣一來路就走通了,
translateM通過改變第四行的向量來操作頂點的位置,yes!


3.第三關卡:Matrix.scaleM分析
public static void scaleM(float[] m, int mOffset,
        float x, float y, float z) {
    for (int i=0 ; i<4 ; i++) {
        int mi = mOffset + i;
        m[     mi] *= x;
        m[ 4 + mi] *= y;
        m[ 8 + mi] *= z;
    }
}

這個就簡單了:
i=0:  m[0] = m[0]*x     m[4] = m[4]*y   m[8] = m[8]*z
i=1:  m[1] = m[1]*x     m[5] = m[5]*y   m[9] = m[9]*z
i=2:  m[2] = m[2]*x     m[6] = m[4]*y   m[10] = m[10]*z
i=3:  m[3] = m[3]*x     m[7] = m[7]*y   m[11] = m[11]*z

>這樣一看是不是豁然開朗
複製程式碼

旋轉還是算了吧,沒幾張草稿紙還真算不清,原始碼看著也挺複雜
等以後哪天閒到懷疑人生的時候再來慢慢算算吧。


副本三:形之累變

在一個圓環上等分點,可以形成若干三角形,由此可以拼合出圖形
這個副本是為了練習一下規律型的運算,發現規律,加以使用

環.gif


1.第一關卡:環

Android多媒體之GLES2戰記第四集--移形換影

/**
 *  初始化頂點座標與顏色
 * @param splitCount 分割點數
 * @param r 內圓半徑
 * @param R 外圈半徑
 */
public void initVertex(int splitCount, float r, float R) {
    //頂點座標資料的初始化
    verticeCount = splitCount * 2 + 2;
    float[] vertices = new float[verticeCount * 3];//座標資料
    float thta = 360.f / splitCount;
    for (int i = 0; i < vertices.length; i += 3) {
        int n = i / 3;
        if (n % 2 == 0) {//偶數點--內圈
            vertices[i] = (float) (r * Math.cos(Change.rad((n / 2) * thta)));//x
            vertices[i + 1] = (float) (r * Math.sin(Change.rad((n / 2) * thta)));//y
            vertices[i + 2] = 0;//z
        } else {//奇數點--外圈
            vertices[i] = (float) (R * Math.cos(Change.rad((n / 2) * thta)));//x
            vertices[i + 1] = (float) (R * Math.sin(Change.rad((n / 2) * thta)));//y
            vertices[i + 2] = 0;//z
        }
    }
    //i+8 表示 每次跨度兩個點
    //橙色:0.972549f,0.5019608f,0.09411765f,1.0f
    float colors[] = new float[verticeCount * 4];
    for (int i = 0; i < colors.length; i += 8) {
        colors[i + 0] = 1;
        colors[i + 1] = 1;
        colors[i + 2] = 1;
        colors[i + 3] = 1;
        colors[i + 4] = 0.972549f;
        colors[i + 5] = 0.5019608f;
        colors[i + 6] = 0.09411765f;
        colors[i + 7] = 1.0f;
    }
    vertexBuffer = GLUtil.getFloatBuffer(vertices);
    mColorBuffer = GLUtil.getFloatBuffer(colors);
}
複製程式碼

這種情況下,使用GL_TRIANGLE_STRIP時極好的,相鄰三點組成三角形

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, verticeCount);
複製程式碼

八邊環.png

調整引數.png


2.第二關卡:GLES20.GL_TRIANGLE_FAN三角形

fan 即扇子,一箇中心,連線其他頂點,好處是比較節省頂點
這樣可以繪製任意正多邊形

fan.png

正八邊形.png

/**
 * 初始化頂點座標與顏色
 *
 * @param splitCount 分割點數
 * @param r          內圓半徑
 */
public void initVertex(int splitCount, float r) {
    //頂點座標資料的初始化
    verticeCount = splitCount + 2;
    float[] vertices = new float[verticeCount * 3];//座標資料
    float thta = 360.f / splitCount;
    vertices[0] = 0;
    vertices[1] = 0;
    vertices[2] = 0;
    for (int n = 1; n <= verticeCount - 1; n++) {
        vertices[n * 3] = (float) (r * Math.cos(Change.rad((n - 1) * thta)));//x
        vertices[n * 3 + 1] = (float) (r * Math.sin(Change.rad((n - 1) * thta)));//y
        vertices[n * 3 + 2] = 0;//z
    }
    //頂點顏色
    //橙色:0.972549f,0.5019608f,0.09411765f,1.0f
    float colors[] = new float[verticeCount * 4];
    colors[0] = 1;
    colors[1] = 1;
    colors[2] = 1;
    colors[3] = 1;
    for (int i = 1; i <= verticeCount - 1; i++) {
        colors[4 * i] = 0.972549f;
        colors[4 * i + 1] = 0.5019608f;
        colors[4 * i + 2] = 0.09411765f;
        colors[4 * i + 3] = 1.0f;
    }
    vertexBuffer = GLUtil.getFloatBuffer(vertices);
    mColorBuffer = GLUtil.getFloatBuffer(colors);
}
複製程式碼

繪製時使用:GLES20.GL_TRIANGLE_FAN

GLES20.glDrawArrays(GLES20.GL_TRIANGLE_FAN, 0, verticeCount);
複製程式碼

3.第三關卡:總結一下幾種繪製方式

Android多媒體之GLES2戰記第四集--移形換影

三角形系列

glDrawArrays:
---->[繪製點線]-------
GLES20.GL_POINTS            繪製點
GLES20.GL_LINES             兩點一線
GLES20.GL_LINE_STRIP        相鄰兩點一線(不連首尾)
GLES20.GL_LINE_LOOP         相鄰兩點一線(連首尾)

---->[繪製三角形]-------
GLES20.GL_TRIANGLES         三點一個
GLES20.GL_TRIANGLE_STRIP    相鄰三點一個
GLES20.GL_TRIANGLE_FAN      第一點中心,散射到其他點

glDrawElements:
---->[頂點索引繪製]------
GLES20.glDrawElements(
GLES20.GL_TRIANGLES, idx.length, 
GLES20.GL_UNSIGNED_SHORT, idxBuffer);
複製程式碼

普通副本四:世界之幕

1.第一關卡:再看投影矩陣

視野

下面是視點:(2,2,-6) far 9 near 3的單位立方,結合上面的圖來看看:

立方.png


near 和 far兩個面決定了視野,即兩面間的內容可見
near面的上下左右的長度也決定這物體的高矮胖瘦,比如左右減半後,
你應該能想了視野被"壓扁"了,物體也會隨之變扁

左右除以二.png

MatrixStack.frustum(
        -ratio/2, ratio/2, -1, 1,
        3f, 9);
複製程式碼

2.第二關卡:near面與平移變換

near面越近,成像越小,你可以根據那個圖,自己想想

near.png

當觀察到的事物在near和far面組成的稜臺之外,便不可見
所以說眼睛限制了你對這個宇宙的認知,但沒有眼睛你會一無所知

平移視點.png


3.平行投影

這個比較簡單,也就是沒有透視,立方的對線都是平行的,引數和透視投影一致
3D一般都是透視投影,既然有這個API,應該有特定的用途吧

public static void orthoM(
        float left, float right, float bottom,
        float top, float near, float far) {
    Matrix.orthoM(mProjectionMatrix, 0,
            left, right, bottom,
            top, near, far);
}
複製程式碼

平行投影0,0,-6.png

平行投影2,2,-6.png

好了,本集結束,下一集:宇宙之光

後記:捷文規範

1.本文成長記錄及勘誤表
專案原始碼 日期 備註
V0.1-github 2018-1-15 Android多媒體之GL-ES戰記第四集--移形換影
2.更多關於我
筆名 QQ 微信 愛好
張風捷特烈 1981462002 zdl1994328 語言
我的github 我的簡書 我的掘金 個人網站
3.宣告

1----本文由張風捷特烈原創,轉載請註明
2----歡迎廣大程式設計愛好者共同交流
3----個人能力有限,如有不正之處歡迎大家批評指證,必定虛心改正
4----看到這裡,我在此感謝你的喜歡與支援


icon_wx_200.png

相關文章