美文网首页Android技术知识手机移动程序开发Android进阶之路
Android多媒体之GL-ES战记第一集--勇者集结

Android多媒体之GL-ES战记第一集--勇者集结

作者: e4e52c116681 | 来源:发表于2019-01-13 08:27 被阅读1次

    前言

    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

    相关文章

      网友评论

        本文标题:Android多媒体之GL-ES战记第一集--勇者集结

        本文链接:https://www.haomeiwen.com/subject/kflxdqtx.html