學習OpenGL ES for Android(二)

本章將學習OpenGL ES中點和線的繪製,在繪製之前需要先了解這些:座標系統;着色器;GLSL(OpenGL ES Shading Language),OpenGL ES着色語言;

座標系統

和android佈局的座標不同,OpenGL是一個右手座標系。簡單來說,就是正x軸在你的右手邊,正y軸朝上,而正z軸是朝向後方的。想象你的屏幕處於三個軸的中心,則正z軸穿過你的屏幕朝向你。座標系畫起來如下:

如果你想要繪製一個在屏幕中心的點時,那麼這個點的座標就是:0,0,0,當你繪製的是不是三維圖像時可以無視z軸(你設置0或者1都不會影響顯示效果)。座標的最大值是1,如果你的座標值大於1的話,這個座標是無法顯示的。

可以參考https://learnopengl-cn.github.io/01%20Getting%20started/08%20Coordinate%20Systems/來學習座標系統,當然還有一些概念,例如正射投影,透視投影,我們暫時用不到用到的時候我們再來學習。

着色器

從我們輸入數據到顯示圖像,這一流程可以查看下圖,

其中需要我們來寫是頂點着色器和片段着色器的代碼。

  • 頂點着色程序 - 用於渲染形狀的頂點的 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:禁用頂點屬性數組,當繪製完成後可以調用。

顯示效果如下

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

這一章瞭解了基本的OpenGL ES圖像的繪製方式,下一章會來學習面的顯示。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章