Android多媒體之GL-ES戰記第一集--勇者集結

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

前言

1.本系列借花獻佛,結合了很多前人的文章以及書籍,我儘可能去總結並用我的思想進行加工
2.OpenGL一直是我的心結,也是時候去解開了,本系列稱不上原創,但每行程式碼都有著我思考的痕跡
3.本系列所有的圖片都是[張風捷特烈]所畫,如果有什麼錯誤還請指出,定會最快改正
4.本系列文章允許轉載、擷取、公眾號釋出,請保留前言部分,希望廣大讀者悉心指教


NPC:開場詞

傳說,在這片程式碼大陸上,存在一個古老的種族,它們擁有無盡的力量,卻罕有人能夠駕馭
多媒體王國中存在一個隱蔽的角落,是這個種族的棲息之地,很少有人敢冒犯那裡
Android多媒體領域有一處:被後人稱為黑龍洞穴--OpenGL ES,其中埋藏著圖形界的無限財富
勇士們,舉起手中的劍,進發!


副本一: 黑龍洞口

NPC:黑龍洞口一片漆黑,其中隱藏著什麼規律,勇士們,一起尋找吧!

1.第一關卡:繪製全屏的紅色

LEVEL 1


1.1:GLSurfaceView的使用
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/9 0009:18:25<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:GL測試檢視
 */
public class GLView extends GLSurfaceView {
    private GLRenderer mRenderer;

    public GLView(Context context) {
        this(context,null);
    }

    public GLView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    private void init() {
        setEGLContextClientVersion(2);//設定OpenGL ES 2.0 context
        mRenderer = new GLRenderer();
        setRenderer(mRenderer);//設定渲染器
    }
}
複製程式碼

1.2:LSurfaceView.Renderer的使用
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/9 0009:18:56<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:GL渲染類
 */
public class GLRenderer implements GLSurfaceView.Renderer {
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//rgba
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);//GL視口
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        //清除顏色快取和深度快取
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    }
}
複製程式碼

1.3:Activity中

紅介面.png

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new GLView(this));
    }
}
複製程式碼

2.第二關卡:三角形的繪製

三角形.png

2.1:三角形
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/9 0009:20:09<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:三角形
 */
public class Triangle {
    private FloatBuffer vertexBuffer;//頂點緩衝
    private final String vertexShaderCode =//頂點著色程式碼
            "attribute vec4 vPosition;" +
                    "void main() {" +
                    "  gl_Position = vPosition;" +
                    "}";
    private final String fragmentShaderCode =//片元著色程式碼
            "precision mediump float;" +
                    "uniform vec4 vColor;" +
                    "void main() {" +
                    "  gl_FragColor = vColor;" +
                    "}";
    private final int mProgram;
    private int mPositionHandle;//位置控制程式碼
    private int mColorHandle;//顏色控制程式碼
    private final int vertexCount = sCoo.length / COORDS_PER_VERTEX;//頂點個數
    private final int vertexStride = COORDS_PER_VERTEX * 4; // 3*4=12
    // 陣列中每個頂點的座標數
    static final int COORDS_PER_VERTEX = 3;
    static float sCoo[] = {   //以逆時針順序
            0.0f, 0.0f, 0.0f, // 頂部
            -1.0f, -1.0f, 0.0f, // 左下
            1.0f, -1.0f, 0.0f  // 右下
    };
    // 顏色,rgba
    float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
    public Triangle() {
        //初始化頂點位元組緩衝區
        ByteBuffer bb = ByteBuffer.allocateDirect(sCoo.length * 4);//每個浮點數:座標個數* 4位元組
        bb.order(ByteOrder.nativeOrder());//使用本機硬體裝置的位元組順序
        vertexBuffer = bb.asFloatBuffer();// 從位元組緩衝區建立浮點緩衝區
        vertexBuffer.put(sCoo);// 將座標新增到FloatBuffer
        vertexBuffer.position(0);//設定緩衝區以讀取第一個座標
        int vertexShader = GLRenderer.loadShader(
                GLES20.GL_VERTEX_SHADER,//頂點著色
                vertexShaderCode);
        int fragmentShader = GLRenderer.loadShader
                (GLES20.GL_FRAGMENT_SHADER,//片元著色
                        fragmentShaderCode);
        mProgram = GLES20.glCreateProgram();//建立空的OpenGL ES 程式
        GLES20.glAttachShader(mProgram, vertexShader);//加入頂點著色器
        GLES20.glAttachShader(mProgram, fragmentShader);//加入片元著色器
        GLES20.glLinkProgram(mProgram);//建立可執行的OpenGL ES專案
    }
    public void draw() {
        // 將程式新增到OpenGL ES環境中
        GLES20.glUseProgram(mProgram);
        //獲取頂點著色器的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);
    }
}
複製程式碼

2.2:GL渲染類
/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/9 0009:18:56<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:GL渲染類
 */
public class GLRenderer implements GLSurfaceView.Renderer {
    Triangle mTriangle;
    /**
     * 載入作色器
     * @param type  頂點著色 {@link GLES20.GL_VERTEX_SHADER}
     *              片元著色 {@link GLES20.GL_FRAGMENT_SHADER}
     * @param shaderCode 著色程式碼
     * @return 作色器
     */
    public static int loadShader(int type, String shaderCode){
        int shader = GLES20.glCreateShader(type);//建立著色器
        GLES20.glShaderSource(shader, shaderCode);//新增著色器原始碼
        GLES20.glCompileShader(shader);//編譯
        return shader;
    }
    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//rgba
        mTriangle = new Triangle();
    }
    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);//GL視口
    }
    @Override
    public void onDrawFrame(GL10 gl) {
        //清除顏色快取和深度快取
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
        mTriangle.draw();
    }
}
複製程式碼

2.3:著色器

NPC:勇者,你陣亡了沒...如果現在退出還來得及,這將是一篇巨集偉的戰鬥史詩
如果你還想繼續,舉起你手中的劍,同我一起,進發!!!

著色器.png

/**
 * 載入作色器
 * @param type  頂點著色 {@link GLES20.GL_VERTEX_SHADER}
 *              片元著色 {@link GLES20.GL_FRAGMENT_SHADER}
 * @param shaderCode 著色程式碼
 * @return 作色器
 */
public static int loadShader(int type, String shaderCode){
    int shader = GLES20.glCreateShader(type);//建立著色器
    GLES20.glShaderSource(shader, shaderCode);//新增著色器原始碼
    GLES20.glCompileShader(shader);//編譯
    return shader;
}
複製程式碼

2.4:渲染器程式

program.png

private final String vertexShaderCode =//頂點著色程式碼
        "attribute vec4 vPosition;" +
                "void main() {" +
                "  gl_Position = vPosition;" +
                "}";
private final String fragmentShaderCode =//片元著色程式碼
        "precision mediump float;" +
                "uniform vec4 vColor;" +
                "void main() {" +
                "  gl_FragColor = vColor;" +
                "}";

int vertexShader = GLRenderer.loadShader(
        GLES20.GL_VERTEX_SHADER,//頂點著色
        vertexShaderCode);
int fragmentShader = GLRenderer.loadShader(
        GLES20.GL_FRAGMENT_SHADER,//片元著色
        fragmentShaderCode);
mProgram = GLES20.glCreateProgram();//建立空的OpenGL ES 程式
GLES20.glAttachShader(mProgram, vertexShader);//加入頂點著色器
GLES20.glAttachShader(mProgram, fragmentShader);//加入片元著色器
GLES20.glLinkProgram(mProgram);//建立可執行的OpenGL ES專案
複製程式碼

2.5:頂點緩衝

頂點緩衝.png

private FloatBuffer vertexBuffer;//頂點緩衝
private final int vertexCount = sCoo.length / COORDS_PER_VERTEX;//頂點個數
private final int vertexStride = COORDS_PER_VERTEX * 4; // 3*4=12
static final int COORDS_PER_VERTEX = 3;//陣列中每個頂點的座標數
static float sCoo[] = {   //以逆時針順序
        0.0f, 0.0f, 0.0f, // 頂部
        -1.0f, -1.0f, 0.0f, // 左下
        1.0f, -1.0f, 0.0f  // 右下
};

//初始化頂點位元組緩衝區
ByteBuffer bb = ByteBuffer.allocateDirect(sCoo.length * 4);//每個浮點數:座標個數* 4位元組
bb.order(ByteOrder.nativeOrder());//使用本機硬體裝置的位元組順序
vertexBuffer = bb.asFloatBuffer();// 從位元組緩衝區建立浮點緩衝區
vertexBuffer.put(sCoo);// 將座標新增到FloatBuffer
vertexBuffer.position(0);//設定緩衝區以讀取第一個座標
複製程式碼

2.6: 繪製

繪製.png

 public void draw() {
     // 將程式新增到OpenGL ES環境中
     GLES20.glUseProgram(mProgram);
     //獲取頂點著色器的vPosition成員的控制程式碼
     mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
     //啟用三角形頂點的控制程式碼
     GLES20.glEnableVertexAttribArray(mPositionHandle);
     //準備三角座標資料
     GLES20.glVertexAttribPointer(
             mPositionHandle,//int indx, 索引
             COORDS_PER_VERTEX,//int size,大小
             GLES20.GL_FLOAT,//int type,型別
             false,//boolean normalized,//是否標準化
             vertexStride,// int stride,//跨度
             vertexBuffer);// java.nio.Buffer ptr//緩衝
     // 獲取片元著色器的vColor成員的控制程式碼
     mColorHandle = GLES20.glGetUniformLocation(mProgram, "vColor");
     //為三角形設定顏色
     GLES20.glUniform4fv(mColorHandle, 1, color, 0);
     //繪製三角形
     GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vertexCount);
     //禁用頂點陣列:
     //禁用index指定的通用頂點屬性陣列。
     // 預設情況下,禁用所有客戶端功能,包括所有通用頂點屬性陣列。
     // 如果啟用,將訪問通用頂點屬性陣列中的值,
     // 並在呼叫頂點陣列命令(如glDrawArrays或glDrawElements)時用於呈現
     GLES20.glDisableVertexAttribArray(mPositionHandle);
 }
複製程式碼

副本二---龍之怒色

1.第一關卡:簡單認識OpenGL ES 著色指令碼語言

GLSL(OpenGL Shader Language)

1.一種面相過程的高階語言
2.基於C/C++的語法(子集)及流程控制
3.完美支援向量和矩陣的操作
4.通過型別限定符來管理輸入與輸出
複製程式碼

1.1:檔案的格式

沒有統一的擴充名,經過百度,感覺這種方式比較符合我的審美
而且AndroidStudio支援這些擴充名,你都叫.glsl也可以,能分清就像

assert.png

.vert - 頂點著色器
.tesc - 曲面細分控制著色器
.tese - 曲面細分評估著色器
.geom - 幾何著色器
.frag - 片元著色器
.comp - 計算著色器
複製程式碼

原生資料型別

標量:一維的數值操作

float   浮點型
bool    布林型
int     整型
|--- 支援 8進位制(0開頭)  16進位制(0x開頭)
複製程式碼

向量:儲存及操作 顏色、位置、紋理座標等

vec2    二維向量型-浮點型
vec3    三維向量型-浮點型
vec4    四維向量型-浮點型

ivec2    二維向量型-整型
ivec3    三維向量型-整型
ivec4    四維向量型-整型

bvec2    二維向量型-布林型
bvec3    三維向量型-布林型
bvec4    四維向量型-布林型
複製程式碼

矩陣:根據矩陣的運算進行變換操作

mat2    2X2矩陣-浮點型
mat3    3X3矩陣-浮點型
mat4    4X4矩陣-浮點型
複製程式碼

取樣器

sampler2D   二維紋理
sampler3D   三維紋理
samplerCube 立方貼圖紋理
複製程式碼

結構體:例如

struct ball{
    vec3 color;
    vec3 position;
}
複製程式碼

陣列

vec3 pos[]; //宣告不定大小的三維向量陣列
vec3 pos[6];//宣告6個三維向量陣列
複製程式碼

限定符
attribute 頂點的變數,如頂點位置,顏色
uniform 
varying 用於從定點著色器傳遞到片元作色器的變數
const 
precision 精度
|---lowp
|---mediump
|---highp
複製程式碼

2.第二關卡:資原始檔的讀取

載入著色指令碼的程式碼差不多,封裝一下,寫個GLUtils吧:

/**
 * 作者:張風捷特烈<br/>
 * 時間:2019/1/10 0010:10:58<br/>
 * 郵箱:1981462002@qq.com<br/>
 * 說明:OpenGL ES 輔助工具
 */
public class GLUtils {

    //從指令碼中載入shader內容的方法
    public static int loadShaderAssets(Context ctx, int type, String name) {
        String result = null;
        try {
            InputStream in = ctx.getAssets().open(name);
            int ch = 0;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            while ((ch = in.read()) != -1) {
                baos.write(ch);
            }
            byte[] buff = baos.toByteArray();
            baos.close();
            in.close();
            result = new String(buff, "UTF-8");
            result = result.replaceAll("\\r\\n", "\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return loadShader(type, result);
    }

    /**
     * 載入作色器
     *
     * @param type       著色器型別    頂點著色 {@link GLES20.GL_VERTEX_SHADER}
     *                   片元著色 {@link GLES20.GL_FRAGMENT_SHADER}
     * @param shaderCode 著色程式碼
     * @return 作色器
     */
    public static int loadShader(int type, String shaderCode) {
        int shader = GLES20.glCreateShader(type);//建立著色器
        if (shader == 0) {//載入失敗直接返回
            return 0;
        }
        GLES20.glShaderSource(shader, shaderCode);//載入著色器原始碼
        GLES20.glCompileShader(shader);//編譯
        return checkCompile(type, shader);
    }

    /**
     * 檢查shader程式碼是否編譯成功
     *
     * @param type   著色器型別
     * @param shader 著色器
     * @return 著色器
     */
    private static int checkCompile(int type, int shader) {
        int[] compiled = new int[1];//存放編譯成功shader數量的陣列
        //獲取Shader的編譯情況
        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
        if (compiled[0] == 0) {//若編譯失敗則顯示錯誤日誌並
            Log.e("ES20_COMPILE_ERROR",
                    "Could not compile shader " + type + ":" + GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);//刪除此shader
            shader = 0;
        }
        return shader;
    }
}
複製程式碼

3.第三關卡:tri.fragtri.vert的分析
3.1:先看片元:tri.frag

第一句是宣告片元的精度
第二句是宣告片元的顏色:一個vec4的變數--vColor
gl_FragColor = vColor; gl_FragColor是gl內定名,將vColor值賦給它

precision mediump float;
uniform vec4 vColor;
void main() {
  gl_FragColor = vColor;
}
複製程式碼

單看一下著色的操作流程:

片元的著色.png

所以從Java程式碼來看,重點在color,它是一個四值陣列,每個值0~1
分別對應r,g,b,a四值,即紅,綠,藍,透明四個顏色維度

// 顏色,rgba
float color[] = {0.63671875f, 0.76953125f, 0.22265625f, 1.0f};
複製程式碼

更換顏色:
rgba 132,197,240,255---->0.5176471f, 0.77254903f, 0.9411765f, 1.0f

更換顏色.png


3.2:再看定點:tri.vert

定義了一個四維的向量給gl_Position

attribute vec4 vPosition;
void main() {
  gl_Position = vPosition;
}
複製程式碼

關於頂點的緩衝 初始化階段將頂點資料經過基本處理

static float sCoo[] = {   //以逆時針順序
        0.0f, 0.0f, 0.0f, // 頂部
        -1.0f, -1.0f, 0.0f, // 左下
        1.0f, -1.0f, 0.0f  // 右下
};

/**
 * 緩衝資料
 */
private void bufferData() {
    ByteBuffer bb = ByteBuffer.allocateDirect(sCoo.length * 4);//每個浮點數:座標個數* 4位元組
    bb.order(ByteOrder.nativeOrder());//使用本機硬體裝置的位元組順序
    vertexBuffer = bb.asFloatBuffer();// 從位元組緩衝區建立浮點緩衝區
    vertexBuffer.put(sCoo);// 將座標新增到FloatBuffer
    vertexBuffer.position(0);//設定緩衝區以讀取第一個座標
}
複製程式碼

每三個數是一個頂點,分別代表(x,y,z),先卡z=0,也就是二維座標系
經過三個點的測試,可以發現是一箇中心在原點,左右跨度為1的座標系

座標系(二維).png

變動座標

變動座標.png


4.第三關卡:頂點著色

剛才是給片元進行著色的,現在看看怎麼給頂點著色,肯定要有頂點變數
前面關於修飾關鍵字:varying 用於從定點著色器傳遞到片元作色器的變數

4.1:頂點程式碼:tri.vert
attribute vec3 vPosition;//頂點座標
uniform mat4 uMVPMatrix; //總變換矩陣
attribute vec4 aColor;//頂點顏色
varying  vec4 vColor;//片元顏色

void main() {
  gl_Position = uMVPMatrix*vec4(vPosition,1);
  vColor = aColor;//將頂點顏色傳給片元
}
複製程式碼

4.2:片元程式碼:tri.frag
precision mediump float;
varying vec4 vColor;
void main() {
  gl_FragColor = vColor;
}
複製程式碼

4.3:使用:Triangle.java

三個點,第三個顏色,頂點+緩衝,跟頂點座標一個套路,取黃、藍、綠三色

//成員變數
private FloatBuffer mColorBuffer;//顏色緩衝
static final int COLOR_PER_VERTEX = 4;//向量維度
private final int vertexColorStride = COLOR_PER_VERTEX * 4; // 4*4=16
float colors[] = new float[]{
        1f, 1f, 0.0f, 1.0f,//黃
        0.05882353f, 0.09411765f, 0.9372549f, 1.0f,//藍
        0.19607843f, 1.0f, 0.02745098f, 1.0f//綠
};

//注意顏色控制程式碼不是uniform了,獲取片元著色器的vColor成員的控制程式碼
mColorHandle = GLES20.glGetAttribLocation(mProgram, "aColor");

//啟用三角形頂點顏色的控制程式碼
GLES20.glEnableVertexAttribArray(mColorHandle);

//準備三角頂點顏色資料
GLES20.glVertexAttribPointer(
                mColorHandle,
                COLOR_PER_VERTEX,
                GLES20.GL_FLOAT,
                false,
                vertexColorStride,
                mColorBuffer);  
複製程式碼

頂點著色.png


副本三---龍之赤瞳

先看這個圖,按這樣來畫個人臉,豈不是會扁掉?這怎麼能忍

座標系(二維).png


1.第一關卡:相機--Matrix.setLookAtM

一共11個引數,嚇得我一抖,經過百度,再加上我神級的Ps技能,繪圖如下
主要有三個點eye(相機/眼睛位置),center(觀察物的位置),up(抬頭的感覺,意會一下...)

setLookAtM.png

public static void setLookAtM(float[] rm, int rmOffset,
            float eyeX, float eyeY, float eyeZ,
            float centerX, float centerY, float centerZ, 
            float upX, float upY,float upZ) {
複製程式碼

2.第二關卡:透視投影--Matrix.frustumM

八個引數,還好還好,也不是太多...

frustumM.png

Matrix.frustumM(float[] m, int offset,
            float left, float right, float bottom, float top,
            float near, float far)
複製程式碼

3.第三關卡:修正視野,讓x,y看起來一致

修正視野.png

3.1.GLRenderer中:
//Model View Projection Matrix--模型檢視投影矩陣
private final float[] mMVPMatrix = new float[16];
//投影矩陣 mProjectionMatrix
private final float[] mProjectionMatrix = new float[16];
//檢視矩陣 mViewMatrix
private final float[] mViewMatrix = new float[16];

---->[GLRenderer#onSurfaceChanged]-------
float ratio = (float) width / height;
//透視投影矩陣--截錐
Matrix.frustumM(mProjectionMatrix, 0,
        -ratio, ratio, -1, 1, 
        3, 7);
// 設定相機位置(檢視矩陣)
Matrix.setLookAtM(mViewMatrix, 0,
        0, 0, -3,
        0f, 0f, 0f,
        0f, 1.0f, 0.0f);
        
---->[GLRenderer#onDrawFrame]-------  

 // 計算投影和檢視轉換
 Matrix.multiplyMM(
         mMVPMatrix, 0,
         mProjectionMatrix, 0,
         mViewMatrix, 0);
 mTriangle.draw(mMVPMatrix);
複製程式碼

3.2:tri.vert:為頂點新增矩陣變換
attribute vec3 vPosition;//頂點座標
uniform mat4 uMVPMatrix; //總變換矩陣
void main() {
  gl_Position = uMVPMatrix*vec4(vPosition,1);
}
複製程式碼

3.3:獲取控制程式碼,修正頂點:Triangle.java
//獲取程式中總變換矩陣uMVPMatrix成員的控制程式碼
muMVPMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");

---->[Triangle#draw]-------------
//對頂點進行矩陣變換
GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, mvpMatrix, 0);
複製程式碼

修正完畢.png


副本四--龍之振翼

1.第一關卡:旋轉30°

mMVPMatrix再進行矩陣變換就行了

旋轉30度.png

//變換矩陣
private float[] mOpMatrix = new float[16];

---->[GLRenderer#onDrawFrame]-------  
//mOpMatrix旋轉變換
Matrix.setRotateM(mOpMatrix, 0, 30, 0, 0, -1);

//使用mOpMatrix對mMVPMatrix進行變換
Matrix.multiplyMM(
        mMVPMatrix, 0,
        mViewMatrix, 0,
        mOpMatrix, 0);
        
Matrix.multiplyMM(
        mMVPMatrix, 0,
        mProjectionMatrix, 0,
        mMVPMatrix, 0);
複製程式碼

隱藏關卡--Matrix.multiplyMM

我知道你看得一臉懵X,現在看看multiplyMM是個什麼東西
怎麼看?當然先看原始碼啦,這是目前OpenGl ES 裡我見過註釋最多的...

將兩個4x4矩陣相乘,並將結果儲存在第三個4x4矩陣中。其中:result = lhs x rhs。
由於矩陣相乘的工作方式,結果矩陣的效果相當於先被右邊的矩陣乘,再被左邊的矩陣乘。
這跟你期望的情況是相反的。

result  儲存結果的浮點陣列
lhs     儲存左側矩陣的浮點陣列。
rhs     儲存右側矩陣的浮點陣列。

三個對應的offset--偏移量

public static native void multiplyMM(float[] result, int resultOffset,
        float[] lhs, int lhsOffset, float[] rhs, int rhsOffset);
複製程式碼

這裡都是用16個float的陣列成的矩陣,寫個方法列印出來再說

列印矩陣.png

public static void logM(float[] matrix) {
    logM(matrix, "Matrix");
}
/**
 * 列印方陣陣列
 *
 * @param matrix 
 * @param name
 */
public static void logM(float[] matrix, String name) {
    int wei = (int) Math.sqrt(matrix.length);
    StringBuffer sb = new StringBuffer("\n[");
    for (int i = 0; i < matrix.length; i++) {
        sb.append(matrix[i]);
        if ((i + 1) % wei == 0) {
            if (i == matrix.length - 1) {
                sb.append("]");
                continue;
            }
            sb.append("\n");
            continue;
        }
        sb.append(" , ");
    }
    Log.e("Matrix_TAG", name + ": " + sb.toString());
}
複製程式碼

multiplyMM.png

multiplyMM


現在回頭再來看看:
mOpMatrix本來全是0,經過setRotateM之後變成圖中第一個矩陣
第一個Matrix.multiplyMMmOpMatrix矩陣作用於mViewMatrix上,獲得結果矩陣:mMVPMatrix
第二個Matrix.multiplyMMmMVPMatrix矩陣作用於mProjectionMatrix上,獲得結果矩陣:mMVPMatrix
最後根據頂點變換矩陣的控制程式碼,將mMVPMatrix在tri.vert中作用在頂點上

//變換矩陣
private float[] mOpMatrix = new float[16];

//mOpMatrix旋轉變換
Matrix.setRotateM(mOpMatrix, 0, 30, 0, 0, -1);

//使用mOpMatrix對mMVPMatrix進行變換
Matrix.multiplyMM(
        mMVPMatrix, 0,
        mViewMatrix, 0,
        mOpMatrix, 0);
        
Matrix.multiplyMM(
        mMVPMatrix, 0,
        mProjectionMatrix, 0,
        mMVPMatrix, 0);
複製程式碼

2.第二關卡:不停旋轉

當GLSurfaceView的setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);時
RendereronDrawFrame(GL10 gl) {會不斷執行,更新的時間間隔和手機有關
我的真機在13~19ms之間,模擬器在16~48ms之間,看了一下,轉一圈用6s,
即6000ms,一共360°,每次+1°,使用平均每度(每次重新整理)用了16.667ms,好吧,完美的60fps

轉一圈.png

旋轉.gif

private int currDeg = 0;

---->[GLRenderer#onDrawFrame]-------  
//初始化變換矩陣
Matrix.setRotateM(mOpMatrix, 0, currDeg, 0, 0, -1);
Matrix.multiplyMM(mMVPMatrix, 0,
        mViewMatrix, 0,
        mOpMatrix, 0);
Matrix.multiplyMM(mMVPMatrix, 0,
        mProjectionMatrix, 0,
        mMVPMatrix, 0);
mTriangle.draw(mMVPMatrix);

currDeg++;
if (currDeg == 360) {
    currDeg = 0;
}

複製程式碼

3.第二關卡:不停旋轉著縮小

你拍照的時候怎麼讓成像縮小?----往後退唄!
根據後退為正,可以推測出座標系是一個右手系,也就是z軸朝向我們
執行很簡單:Matrix.translateM 就可以將mOpMatrix進行平移操作
以我們的視角(參考系):你可以想象成圖形(觀察物)一邊旋轉一邊原離我們,也可以反過來想想

引擎推動的不是飛船而是宇宙。飛船壓根就沒動過。--如果對矩陣有興趣,建議把這篇看十遍

//設定沿Z軸位移
Matrix.translateM(mOpMatrix, 0, 0, 0, currDeg/90.f);
複製程式碼

旋轉+縮小.gif


NPC: 恭喜您,完成第四副本,現在您獲得OpenGL-ES 新手戰士的稱號,請留下名號:
我(輸入):張風捷特烈
NPC: 張風捷特烈,是否繼續前行,下面的關卡將更加艱難
我:(點選確定) 執劍向前
NPC: 尊敬的勇者-張風捷特烈,祝您一路平安,成功斬殺黑龍...

第一集結束,下一集:"謎團立方" 敬請期待

後記:捷文規範

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

本文參考:
1.《Android 3D遊戲開發技術寶典 OpenGL ES 2.0》
2.OpenGL ES 學習記錄
3.opengl-tutorial:OpenGL基礎知識
4.廣大網友的文章零散參考,就不一一列舉了

icon_wx_200.png

相關文章