• OpenGL ES 学习初识(1)


    1.认识OpenGL ES

    OpenGL是跨平台2D/3D图形API,而OpenGL ES是在前者基础上扩展的版本,适用于手机等嵌入式设备。具有以下版本:

    版本解释
    OpenGL ES 1.02003年发布
    OpenGL ES 1.1支持了多纹理,顶点缓冲对象等
    OpenGL ES 2.02007年发布,基于OpenGL 2.0,删除了1.x中的固定管线(pipeline),提供可编程的管线
    OpenGL ES 3.02012年发布,向后兼容2.x
    OpenGL ES 3.12014年发布
    OpenGL ES 3.22015年发布

    OpenGL ES 1.x 和OpenGL ES2.0不兼容的,是完全两种不同的实现。

    • OpenGL EX 1.x仅支持固定管线渲染,可以固件支持或软件模拟,但渲染能力有限。

    • OpenGL EX 2.X采用可编程的渲染管线,提高了渲染能力。但编程难度也提高了,且要求设备中必须有对应GPU硬件支持。
      图源Android3D游戏开发技术宝典图源Android3D游戏开发技术宝典
      上面图3-16和图3-21分别是OpenGL 1.x和OpenGL 2.x渲染管线过程示意图。

    • “变换和光照”---->“顶点着色器”

    • “纹理环境和颜色求和”、“雾”、“Alpha测速”---->“片元着色器”

    本系列文章将主要介绍OpenGL ES2.x

    2.初识OpenGL ES2.x程序面貌

    在认识了OpenGL ES基础后,下面直接带来一个入门级Demo(本程序官网也有类似的可以学习)。代码中添加了注释,可以更好帮助入门学习。

    2.1 效果图

    在这里插入图片描述

    2.2 工具类ShaderUtil.java

    public class ShaderUtil {
        private static final String TAG = "ShaderUtil";
    
        /**
         * 加载着色器编码进GPU并编译
         *
         * @param shaderType 着色器类型
         * @param source     着色器脚本字符串
         * @return
         */
        public static int loadShader(int shaderType, String source) {
            int shader = GLES20.glCreateShader(shaderType);
            if (shader != 0) {
                GLES20.glShaderSource(shader, source);
                GLES20.glCompileShader(shader);
                int[] compiled = new int[1];
                GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);
                if (compiled[0] == 0) {
                    Log.e(TAG, "loadShader: ES20_ERROR,Could not compile shader " + shaderType + ";");
                    Log.e(TAG, "loadShader: ES20_ERROR,shader = " + GLES20.glGetShaderInfoLog(shader));
                    GLES20.glDeleteShader(shader);
                    shader = 0;
                }
            }
            return shader;
        }
    
        /**
         * 创建着色器程序
         *
         * @param vertexSource
         * @param fragmentSource
         * @return
         */
        public static int createProgram(String vertexSource, String fragmentSource) {
            //加载顶点着色器
            int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);
            if (vertexShader == 0) {
                return 0;
            }
    
            //加载片元着色器
            int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);
            if (pixelShader == 0) {
                return 0;
            }
            int program = GLES20.glCreateProgram();
            if (program != 0) {
                GLES20.glAttachShader(program, vertexShader);
                checkGLError("glAttachShader");
                GLES20.glAttachShader(program, pixelShader);
                checkGLError("glAttachShader");
                GLES20.glLinkProgram(program);
                int[] linkStatus = new int[1];
                GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, 0);
                if (linkStatus[0] != GLES20.GL_TRUE) {
                    Log.e(TAG, "createProgram: ES20_ERROR,Could not link program");
                    Log.e(TAG, "createProgram: ES20_ERROR,program = " + GLES20.glGetProgramInfoLog(program));
                    GLES20.glDeleteProgram(program);
                    program = 0;
                }
            }
            return program;
        }
    
        /**
         * 检测每一步操作是否有错误的方法
         *
         * @param op
         */
        public static void checkGLError(String op) {
            int error;
            while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
                Log.d(TAG, ">>>>>> " + op + " :glError " + error);
                throw new RuntimeException(op + ":glError " + error);
            }
        }
    
        /**
         * 从sh脚本中加载着色器内容
         *
         * @param fname
         * @param resources
         * @return
         */
        public static String loadFromAssetsFile(String fname, Resources resources) {
            String result = null;
            try {
                InputStream in = resources.getAssets().open(fname);
                int ch = 0;
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                while ((ch = in.read()) != -1) {
                    baos.write(ch);//获取的信息写入流
                }
                byte[] buffer = baos.toByteArray();
                baos.close();
                in.close();
                result = new String(buffer, "UTF-8");
                result = result.replaceAll("\\r\\n", "\n");
    
    
            } catch (IOException e) {
                e.printStackTrace();
            }
            Log.d(TAG, "loadFromAssetsFile: result = " + result);
            return result;
        }
    
    
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110

    2.3 三角形Triangle

    public class Triangle {
        public static float[] mProjMatrix = new float[16];//4x4投影矩阵
        public static float[] mVMatrix = new float[16];//摄像机位置朝向的参数矩阵
        public static float[] mMvPMatrix;//总变换矩阵
        private int program;//自定义渲染管线着色器程序id
        private int muMVPMatrixHandle;//总变换矩阵引用
        private int maPositionHandle;//顶点位置属性引用
        private int maColorHandle;//顶点颜色属性引用
        private String mVertexShader;//顶点着色器代码脚本
        private String mFragmentShader;//片元着色器代码脚本
        private static float[] mMMatrix = new float[16];//具体物体的3D变换矩阵,包括旋转、平移、缩放
        private FloatBuffer mVertexBuffer;//顶点坐标数据缓冲
        private FloatBuffer mColorBuffer;//顶点着色数据缓冲
        private int vCount = 0;//顶点数量
        public float xAngle = 0;//绕X轴旋转的角度
    
        public Triangle(MyTDView myTDView) {
            initVertexData();
            initShader(myTDView);
        }
    
        /**
         * 初始化着色器
         * @param myTDView
         */
        private void initShader(MyTDView myTDView) {
            mVertexShader = ShaderUtil.loadFromAssetsFile("vertex.sh", myTDView.getResources());
            mFragmentShader = ShaderUtil.loadFromAssetsFile("frag.sh", myTDView.getResources());
            program = ShaderUtil.createProgram(mVertexShader, mFragmentShader);
            maPositionHandle = GLES20.glGetAttribLocation(program, "aPosition");
            maColorHandle = GLES20.glGetAttribLocation(program, "aColor");
            muMVPMatrixHandle = GLES20.glGetUniformLocation(program, "uMVPMatrix");
        }
    
        /**
         * 绘制三角形
         */
        public void drawSelf() {
            GLES20.glUseProgram(program);
            Matrix.setRotateM(mMMatrix, 0, 0, 0, 1, 0);//初始化变换矩阵
            Matrix.translateM(mMMatrix, 0, 0, 0, 1);//设置沿Z轴正向位移
            Matrix.rotateM(mMMatrix, 0, xAngle, 1, 0, 0);//设置绕X轴旋转
            GLES20.glUniformMatrix4fv(muMVPMatrixHandle, 1, false, Triangle.getFinalMatrix(mMMatrix), 0);
            GLES20.glVertexAttribPointer(maPositionHandle, 3, GLES20.GL_FLOAT, false, 3 * 4, mVertexBuffer);
            GLES20.glVertexAttribPointer(maColorHandle, 4, GLES20.GL_FLOAT, false, 4 * 4, mColorBuffer);
            GLES20.glEnableVertexAttribArray(maPositionHandle);
            GLES20.glEnableVertexAttribArray(maColorHandle);
            GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, vCount);
        }
    
        /**
         * 初始化顶点数据
         */
        private void initVertexData() {
            vCount = 3;
            final float UNIT_SIZE = 0.2f;//设置单位长度
            //顶点坐标数组
            float vertices[] = new float[]{
                    -4 * UNIT_SIZE, 0, 0,
                    0, -4 * UNIT_SIZE, 0,
                    4 * UNIT_SIZE, 0, 0
            };
            ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
            vbb.order(ByteOrder.nativeOrder());//设置字节顺序为本地操作系统顺序
            mVertexBuffer = vbb.asFloatBuffer();//转换为浮点型缓冲
            mVertexBuffer.put(vertices);//在缓冲区内写入数据
            mVertexBuffer.position(0);//设置缓冲区起始位置
    
            //顶点颜色数组
            float colors[] = new float[]{
                    1, 1, 1, 0,
                    0, 0, 1, 0,
                    0, 1, 0, 0
            };
            ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length * 4);
            cbb.order(ByteOrder.nativeOrder());
            mColorBuffer = cbb.asFloatBuffer();
            mColorBuffer.put(colors);
            mColorBuffer.position(0);
        }
    
        /**
         * 最终变换矩阵
         *
         * @param spec
         * @return
         */
        public static float[] getFinalMatrix(float[] spec) {
            //初始化总变换矩阵
            mMvPMatrix = new float[16];
            Matrix.multiplyMM(mMvPMatrix, 0, mVMatrix, 0, spec, 0);
            Matrix.multiplyMM(mMvPMatrix, 0, mProjMatrix, 0, mMvPMatrix, 0);
            return mMvPMatrix;
        }
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95

    2.4 渲染类MyTDView

    GLSurfaceView和GLSurfaceView.Render的使用

    public class MyTDView extends GLSurfaceView {
        private static final String TAG = "MyTDView";
        //每次三角形旋转的角度
        private final float ANGLE_SPAN = 0.375f;
        //线程
        private RotateThread mRotateThread;
        //自定义渲染器
        private SceneRenderer mSceneRenderer;
    
    
        public MyTDView(Context context) {
            super(context);
            //使用OpenGLES2.0
            this.setEGLContextClientVersion(2);
            mSceneRenderer = new SceneRenderer();
            this.setRenderer(mSceneRenderer);
            this.setRenderMode(GLSurfaceView.RENDERMODE_CONTINUOUSLY);
        }
    
    
        private class SceneRenderer implements GLSurfaceView.Renderer {
            Triangle mTriangle;
    
            @Override
            public void onSurfaceCreated(GL10 gl, EGLConfig config) {
                GLES20.glClearColor(0, 0, 0, 1.0f);//设置屏幕背景颜色
                mTriangle = new Triangle(MyTDView.this);
                GLES20.glEnable(GLES20.GL_DEPTH_TEST);
                mRotateThread = new RotateThread();
                mRotateThread.start();
    
            }
    
            @Override
            public void onSurfaceChanged(GL10 gl, int width, int height) {
                GLES20.glViewport(0, 0, width, height);//设置视口
                float ratio = width / height;
                //设置透视投影
                Matrix.frustumM(Triangle.mProjMatrix, 0, -ratio, ratio, -1, 1, 1, 10);
                //设置摄像机
                Matrix.setLookAtM(Triangle.mVMatrix, 0, 0, 0, 3,
                        0f, 0f, 0f, 0f, 1.0f, 0.0f);
    
    
            }
    
            @Override
            public void onDrawFrame(GL10 gl) {
                GLES20.glClear(GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_COLOR_BUFFER_BIT);
                mTriangle.drawSelf();
            }
        }
    
        private class RotateThread extends Thread {
            //设置循环标识位
            public boolean flag = true;
    
            @Override
            public void run() {
                super.run();
                while (flag) {
                    mSceneRenderer.mTriangle.xAngle = mSceneRenderer.mTriangle.xAngle + ANGLE_SPAN;
                    try {
                        Thread.sleep(20);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
    
                }
            }
        }
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

    2.5 顶点着色器和片元着色器代码

    顶点着色器和片元着色器资源放置在assets目录下。本案例使用的如下所示:

    2.5.1 frag.sh

    precision mediump float;
    varying vec4 vColor;
    void main(){
      gl_FragColor = vColor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.5.2 vertex.sh顶点着色器

    uniform mat4 uMVPMatrix;//总变换矩阵
    attribute vec3 aPosition;//顶点位置
    attribute vec4 aColor;//顶点颜色
    varying vec4 vColor;//用于传递给片元着色器的易变变量
    void main(){
        gl_Position = uMVPMatrix * vec4(aPosition,1);//根据总变换矩阵计算此次绘制顶点的位置
        vColor = aColor;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.6 其他

    public class MainActivity extends AppCompatActivity {
        private MyTDView mTDView;
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
            mTDView = new MyTDView(this);
            mTDView.requestFocus();
            mTDView.setFocusableInTouchMode(true);
            setContentView(mTDView);
        }
    
        @Override
        protected void onResume() {
            super.onResume();
            mTDView.onResume();
        }
    
        @Override
        protected void onPause() {
            super.onPause();
            mTDView.onPause();
        }
    
        @Override
        protected void onDestroy() {
            super.onDestroy();
    
        }
    }
    
    • 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
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    本示例程序可以直接运行,查看效果。

    【参考】

    1. https://www.khronos.org/opengles/
    2. 《OpenGL ES应用开发实践指南 Android卷 Kevin Brothaler》
    3. https://en.wikipedia.org/wiki/OpenGL_ES
    4. https://developer.android.google.cn/guide/topics/graphics/opengl
    5. https://developer.android.google.cn/training/graphics/opengl
    6. Android3D游戏开发技术宝典OpenGL ES 2.0
  • 相关阅读:
    WireShark 常用协议分析
    python将二进制转换为32位浮点数float/两个16位整数(高位字/低位字)Uint16转换为32位浮点数float
    wps表格按分隔符拆分单元格
    【数据结构】什么是堆,如何使用无序数组生成一个堆?
    【Vue 本地项目运行https服务】
    JVM学习-Class文件结构
    优思学院|看板方式与传统生产方式的对比
    Android 13.0 第三方应用默认横屏显示
    含文档+PPT+源码等]精品spring boot+MySQL微人事系统设计与实现vue[包运行成功]计算机毕设Java项目源码
    【线性代数】四、二次型
  • 原文地址:https://blog.csdn.net/ITMonkeyKing/article/details/125618082