学习OpenGL ES for Android(二)

本章将学习OpenGL ES中点和线的绘制,在绘制之前需要先了解这些:坐标系统;着色器;GLSL(OpenGL ES Shading Language),OpenGL ES着色语言;

坐标系统

和android布局的坐标不同,OpenGL是一个右手坐标系。简单来说,就是正x轴在你的右手边,正y轴朝上,而正z轴是朝向后方的。想象你的屏幕处于三个轴的中心,则正z轴穿过你的屏幕朝向你。坐标系画起来如下:

学习OpenGL ES for Android(二)_第1张图片

如果你想要绘制一个在屏幕中心的点时,那么这个点的坐标就是:0,0,0,当你绘制的是不是三维图像时可以无视z轴(你设置0或者1都不会影响显示效果)。坐标的最大值是1,如果你的坐标值大于1的话,这个坐标是无法显示的。

可以参考https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/来学习坐标系统,当然还有一些概念,例如正射投影,透视投影,我们暂时用不到用到的时候我们再来学习。

着色器

从我们输入数据到显示图像,这一流程可以查看下图,

学习OpenGL ES for Android(二)_第2张图片

其中需要我们来写是顶点着色器和片段着色器的代码。

  • 顶点着色程序 - 用于渲染形状的顶点的 OpenGL ES 图形代码。
  • 片段着色程序 - 用于使用颜色或纹理渲染形状面的 OpenGL ES 代码。

GLSL

GLSL是一种类C语言,用来编写顶点着色程序和片段着色程序。GLSL语言还是比较复杂的,OpenGL ES2.0的GLSL官方文档地址是https://www.khronos.org/registry/OpenGL/specs/es/2.0/GLSL_ES_Specification_1.00.pdf,如果英文水平比较高可阅读参考,否则可用参考http://colin1994.github.io/2017/11/11/OpenGLES-Lesson04/,http://colin1994.github.io/2017/11/12/OpenGLES-Lesson05这两篇文章,写的还是不错的。

变量类型

变量类别 变量类型 描述
void 用于无返回值的函数或空的参数列表
标量 float, int, bool 浮点型,整型,布尔型的标量数据类型
浮点型向量 float, vec2, vec3, vec4 包含1,2,3,4个元素的浮点型向量
整数型向量 int, ivec2, ivec3, ivec4 包含1,2,3,4个元素的整型向量
布尔型向量 bool, bvec2, bvec3, bvec4 包含1,2,3,4个元素的布尔型向量
矩阵 mat2, mat3, mat4 尺寸为2x2,3x3,4x4的浮点型矩阵
纹理句柄 sampler2D, samplerCube 表示2D,立方体纹理的句柄

限定符

限定符 描述
< none: default > 局部可读写变量,或者函数的参数
const 编译时常量,或只读的函数参数
attribute 由应用程序传输给顶点着色器的逐顶点的数据
uniform 在图元处理过程中其值保持不变,由应用程序传输给着色器
varying 由顶点着色器传输给片段着色器中的插值数据

先说几个会用到内置的变量,

gl_Position:只能用在顶点着色程序中,用来定义需要写入的顶点。
gl_PointSize:定义顶点的大小,单位是像素。设置的越大,点就越大,当然显示的点时正方形的。
gl_FragColor :用于片段着色程序中,用来设置图像的颜色。

下面先写顶点着色程序和片段着色程序,

    String vertexShaderCode =
            "attribute vec4 aPosition;" +
                    "void main() {" +
                    "  gl_Position = aPosition;" +
                    "  gl_PointSize = 19.0;" +
                    "}";
   String fragmentShaderCode =
                "precision mediump float;" +
                        "uniform vec4 vColor;" +
                        "void main() {" +
                        "  gl_FragColor = vColor;" +
                        "}";

然后我们定义一个顶点的数组,

    private final float[] triangleCoords = {
            -0.9f, 0.9f, 0.0f,
            -0.9f, 0.8f, 0.0f,

            -0.8f, -0.1f, 0.0f,
            -0.6f, -0.5f, 0.0f,
            -0.5f, -0.8f, 0.0f,

            -0.4f, 0.4f, 0.0f,
            -0.2f, 0.1f, 0.0f,
            -0.0f, 0.5f, 0.0f,
            0.1f, 0f, 0.0f,
            0.3f, -0.5f, 0.0f,

            0.4f, -0.2f, 0.0f,
            0.6f, -0.5f, 0.0f,
            0.9f, -0.6f, 0.0f,
            0.9f, -0.9f, 0.0f,
    };

定义一个颜色的数组,用来绘制颜色,四个数值分别表示rbga。 

 // 0 到1 代表 0- 256 如 181 用0.70703125f
private float[] color = {0.70703125f, 0.10546875f, 0.84375f, 1.0f};

在上篇文章中我们显示了一个窗口,现在我们绘制点和线,在onDrawFrame中进行绘制

首先我们加载着色器,加载着色器的代码如下

public static int loadShader(int shaderType, String source) {
        int shader = GLES20.glCreateShader(shaderType);
        checkGlError("glCreateShader type=" + shaderType);
        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) {
            LogUtil.e("Could not compile shader " + shaderType + ":" + GLES20.glGetShaderInfoLog(shader));
            GLES20.glDeleteShader(shader);
            shader = 0;
        }
        return shader;
    }

主要的几个方法
glCreateShader:创建shader,参数值:GLES20.GL_VERTEX_SHADER(顶点着色器)或GLES20.GL_FRAGMENT_SHADER(片段着色器),返回创建的shader值。
glShaderSource:加载着色器代码,参数为create的shader值和GLSL代码。
glCompileShader:加载着色器完成。
glGetShaderiv:获取加载完成的状态,主要用来检测是否加载完成,如果加载失败了则使用glDeleteShader来删除错误的shader。

checkGlError是封装好的检测是否有错误的方法,内容如下

    public static void checkGlError(String op) {
        int error = GLES20.glGetError();
        if (error != GLES20.GL_NO_ERROR) {
            String msg = op + ": glError 0x" + Integer.toHexString(error);
            LogUtil.e(msg);
            throw new RuntimeException(msg);
        }
    }

使用glCreateProgram创建一个空的程序,返回一个非0值。使用glAttachShader来连接当前的程序和创建好的shader值,最后使用glLinkProgram来完成链接program,如果不需要Program后可以使用glDeleteProgram删除program。封装好之后的代码如下

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();
        checkGlError("glCreateProgram");
        if (program == 0) {
            LogUtil.e("Could not create program");
        }
        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) {
            LogUtil.e("Could not link program: ");
            LogUtil.e(GLES20.glGetProgramInfoLog(program));
            GLES20.glDeleteProgram(program);
            program = 0;
        }
        return program;
    }

封装了一个OpenGLUtil工具类来进行shader的加载,便于使用。

加载完成后进行顶点的绘制,完整的onDrawFrame代码如下

    @Override
    public void onDrawFrame(GL10 gl) {
        super.onDrawFrame(gl);
        int shaderProgram = OpenGLUtil.createProgram(vertexShaderCode, fragmentShaderCode);
        GLES20.glUseProgram(shaderProgram);
        int positionHandle = GLES20.glGetAttribLocation(shaderProgram, "aPosition");
        GLES20.glEnableVertexAttribArray(positionHandle);
        FloatBuffer vertexBuffer = OpenGLUtil.createFloatBuffer(triangleCoords);
        GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT,
                false, 3 * 4, vertexBuffer);
        int colorHandle = GLES20.glGetUniformLocation(shaderProgram, "vColor");
        GLES20.glUniform4fv(colorHandle, 1, color, 0); // 设置颜色
        // 画点
        GLES20.glDrawArrays(GLES20.GL_POINTS, 0, 13); // 画点
        // 设置线宽
        GLES20.glLineWidth(18);
        // 画线,不连续的线,例如:有1,2,3,4四个点,1和2是一条线,3,4是一条线
        GLES20.glDrawArrays(GLES20.GL_LINES, 2, 4);
        // 画线,封闭的线,例如:有1,2,3,4四个点,1,2,3,4,1会连接2,2连接3,3连接4,4连接1
        GLES20.glDrawArrays(GLES20.GL_LINE_LOOP, 6, 4);
        // 画线,不封闭的线
        GLES20.glDrawArrays(GLES20.GL_LINE_STRIP, 10, 4);
        GLES20.glDisableVertexAttribArray(positionHandle);
    }

一些主要方法
glUseProgram:使用创建好的program
glGetAttribLocation:查询由程序指定的先前链接的程序对象,返回aPosition变量顶点属性的索引
glEnableVertexAttribArray:启用由索引指定的顶点属性数组。 
createFloatBuffer:封装的生成FloatBuffer代码。
glVertexAttribPointer:指定顶点数组的位置和数据格式,几个参数:index,索引;size,每个顶点的数,当前是3;type,类型,float,short等等,当前是float;normalized,设置为false;stride,顶点间的偏移,每个顶点3个float值,每个float是4个字节;pointer,顶点的集合。
glGetUniformLocation:获取片段着色器中属性的索引。
glUniform4fv:修改统一变量或统一变量数组的值。4fv表示 vec4,同样的方法有很多:glUniform3fv,glUniform3iv等等,可以参考官网的glUniform文档。
glDrawArrays:开始绘制。参数mode类型:有GL_POINTS(点),GL_LINES(线),GL_LINE_STRIP,GL_LINE_LOOP,GL_TRIANGLE_STRIP,GL_TRIANGLE_FAN和GL_TRIANGLE;参数first:开始位置;count:总数。
glDisableVertexAttribArray:禁用顶点属性数组,当绘制完成后可以调用。

显示效果如下

学习OpenGL ES for Android(二)_第3张图片

源码地址https://github.com/jklwan/OpenGLProject/blob/master/sample/src/main/java/com/chends/opengl/view/window/PointLineView.java

这一章了解了基本的OpenGL ES图像的绘制方式,下一章会来学习面的显示。

你可能感兴趣的:(Android,OpenGL,ES,Android,Android,OpenGL,ES)