OpenGL之繪制簡(jiǎn)單形狀

OpenGL之基礎(chǔ)

頂點(diǎn)和著色器

頂點(diǎn)

一個(gè)頂點(diǎn)就是代表幾何對(duì)象的一個(gè)拐角點(diǎn),這個(gè)點(diǎn)有很多附加屬性,最重要的屬性就是位置,代表這個(gè)頂點(diǎn)在空間中的定位

在代碼中定義二維頂點(diǎn)

用 float[] 存儲(chǔ)頂點(diǎn),這個(gè)數(shù)組通常稱(chēng)為頂點(diǎn)屬性 (attribute) 數(shù)組。因?yàn)樗鼈兌x在二維坐標(biāo)系里,所以每個(gè)頂點(diǎn)要用兩個(gè)浮點(diǎn)數(shù)進(jìn)行標(biāo)記,分別表示 x 軸和 y 軸的位置

由于之后會(huì)給頂點(diǎn)加三維數(shù)據(jù)、甚至顏色數(shù)據(jù),到時(shí)候一個(gè)頂點(diǎn)占據(jù)的就不是固定的兩個(gè)數(shù)據(jù)了,因此,可以用常量表示每個(gè)頂點(diǎn)的位置信息占 float[] 的幾個(gè)數(shù)據(jù),顏色信息占 float[] 的幾個(gè)數(shù)據(jù)

在OpenGL里,只能繪制點(diǎn)、直線以及三角形

點(diǎn)和直線可以用于某些特殊效果;只有三角形才能用來(lái)構(gòu)建擁有復(fù)雜的對(duì)象和紋理的場(chǎng)景。把組成三角形的點(diǎn)依次放在 float[] 中,再告訴OpenGL如何連接這些點(diǎn)形成三角形,再由足夠多的三角形組成復(fù)雜的對(duì)象

當(dāng)定義三角形的時(shí)候,以逆時(shí)針的順序排列頂點(diǎn)稱(chēng)為卷曲順序(winding order)。都使用這種一致的卷曲順序,可以?xún)?yōu)化性能。使用卷曲順序可以指出一個(gè)三角形屬于任何給定物體的前面或者后面,OpenGL可以忽略那些無(wú)論如何都無(wú)法被看到的后面的三角形

// 使用 float[] 存儲(chǔ)頂點(diǎn)屬性
float[] vertex = new float[]{
    // 第一個(gè)三角形的頂點(diǎn)位置信息
    -0.8f, -0.8f,
    0.8f, 0.8f,
    -0.8f, 0.8f,

    // 第二個(gè)三角形的頂點(diǎn)位置信息
    -0.8f, -0.8f,
    0.8f, -0.8f,
    0.8f, 0.8f,

    // 中間的線條的頂點(diǎn)位置信息
    -0.8f, 0f,
    0.8f, 0f,

    // 上下兩個(gè)點(diǎn)的頂點(diǎn)位置信息
    0f, -0.5f,
    0f, 0.5f
};

關(guān)于坐標(biāo)寫(xiě)法,無(wú)論是 x 還是 y 坐標(biāo),OpenGL 都會(huì)把屏幕映射到 [-1,1] 的范圍內(nèi),這就意味著屏幕的左邊對(duì)應(yīng)x軸的-1,而屏幕的右邊對(duì)應(yīng)+1,屏幕的底邊會(huì)對(duì)應(yīng)y軸的-1,而屏幕的頂邊就對(duì)應(yīng)+1,因此,頂點(diǎn)坐標(biāo)范圍要在 [-1,1] 的范圍內(nèi)

使頂點(diǎn)數(shù)據(jù)可以被OpenGL存取

完成了頂點(diǎn)的定義,但是,OpenGL 還不能存取它們,因?yàn)檫@些代碼的運(yùn)行環(huán)境與OpenGL運(yùn)行的環(huán)境使用了不同的語(yǔ)言

在模擬器或者手機(jī)設(shè)備上編譯和運(yùn)行Java代碼的時(shí)候,并不是直接運(yùn)行在硬件上的,而是運(yùn)行在虛擬機(jī)上,OpenGL是作為 native 系統(tǒng)庫(kù)直接運(yùn)行在硬件上,沒(méi)有虛擬機(jī),而運(yùn)行在虛擬機(jī)上的代碼不能直接訪問(wèn) native 環(huán)境,除非通過(guò)特定的API

有兩種方法可以與OpenGL通信:

  1. 使用Java本地接口 (JNI)
  2. 把內(nèi)存從Java堆復(fù)制到本地堆
使用Java本地接口 (JNI)

使用Java本地接口 (JNI),這個(gè)技術(shù)已經(jīng)由Android軟件開(kāi)發(fā)包提供了,當(dāng)調(diào)用android.opengl.GLES20包里的方法時(shí),實(shí)際上就是在后臺(tái)使用 JNI 調(diào)用本地系統(tǒng)庫(kù)

把內(nèi)存從Java堆復(fù)制到本地堆

就是改變內(nèi)存分配的方式,Java 有一個(gè)特殊的類(lèi)集合,它們可以分配本地內(nèi)存塊,并且把 Java 的數(shù)據(jù)復(fù)制到本地內(nèi)存。本地內(nèi)存可以被本地環(huán)境存取,不受垃圾回收器的管控,如下圖所示


image-20211220154219141.png

把內(nèi)存從Java堆復(fù)制到本地堆的代碼:

// 表示每個(gè)浮點(diǎn)數(shù)都占用4個(gè)字節(jié),把存儲(chǔ)頂點(diǎn)的內(nèi)存從Java堆復(fù)制到本地堆的時(shí)候使用
private static final int BYTES_PER_FLOAT = 4;
......
mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * BYTES_PER_FLOAT)
        .order(ByteOrder.nativeOrder())
        .asFloatBuffer()
        .put(vertex);
mVertexBuffer.position(0);

使用 ByteBuffer.allocateDirect() 分配了一塊本地內(nèi)存,這塊內(nèi)存不會(huì)被垃圾回收器管理。這個(gè)方法需要知道要分配多少字節(jié)的內(nèi)存塊,因?yàn)轫旤c(diǎn)都存儲(chǔ)在一個(gè)浮點(diǎn)數(shù)組里,并且每個(gè)浮點(diǎn)數(shù)有4個(gè)字節(jié),所以這塊內(nèi)存的大小應(yīng)該是 vertex.length * BYTES_PER_FLOAT

order(ByteOrder.nativeOrder()) 方法告訴字節(jié)緩沖區(qū) ( byte buffer)按照本地字節(jié)序( native byte order)組織它的內(nèi)容,本地字節(jié)序是指,當(dāng)一個(gè)值占用多個(gè)字節(jié)時(shí),比如32位整型數(shù),字節(jié)按照從最重要位到最不重要位或者相反順序排列,可以認(rèn)為這與從左到右或者從右到左寫(xiě)一個(gè)數(shù)類(lèi)似。知道這個(gè)排序并不重要,重要的是作為一個(gè)平臺(tái)要使用同樣的排序,調(diào)用order(ByteOrder.nativeOrder())可以保證這一點(diǎn)

調(diào)用asFloatBuffer()得到一個(gè)可以反映底層字節(jié)的FloatBuffer類(lèi)實(shí)例,就可以直接使用浮點(diǎn)數(shù)操作

調(diào)用vertexData.put(vertex) 把數(shù)據(jù)從Java的內(nèi)存復(fù)制到本地內(nèi)存了。當(dāng)進(jìn)程結(jié)束的時(shí)候,這塊內(nèi)存會(huì)被釋放掉,一般情況下不用關(guān)心它

著色器

OpenGL管道

要想把頂點(diǎn)轉(zhuǎn)變?yōu)閳D像顯示到屏幕上,需要在OpenGL的管道( pipeline)中傳遞,這就需要使用著色器( shader),這些著色器會(huì)告訴圖形處理單元(GPU)如何繪制數(shù)據(jù)


e8a9547dd85a74dc7fa66597e74e4f9c.jpeg

頂點(diǎn)著色器

頂點(diǎn)著色器的主要作用是:為每個(gè)頂點(diǎn)生成最終位置

由于著色器使用的語(yǔ)言是GLSL,因此最好使用 Android Studio 安裝 GLSL Support 插件,這樣關(guān)鍵字才會(huì)高亮

頂點(diǎn)著色器代碼

新建 raw/draw1_vertex.vert 文件,寫(xiě)入如下代碼

attribute vec4 a_Position;
void main(){
    gl_Position=a_Position;
    gl_PointSize=10.0;
}
attribute

一個(gè)頂點(diǎn)會(huì)有幾個(gè)屬性,比如顏色和位置,關(guān)鍵字 "attribute" 就是把這些屬性放進(jìn)著色器的手段,例如上面的 "attribute vec4 a_Position" 就會(huì)把前面定義過(guò)的頂點(diǎn)位置賦值給 a_Position

vec4

對(duì)于我們定義過(guò)的每一個(gè)頂點(diǎn),頂點(diǎn)著色器都會(huì)被調(diào)用一次,當(dāng)它被調(diào)用的時(shí)候,它會(huì)在a_Position 屬性里接收當(dāng)前頂點(diǎn)的位置,這個(gè)屬性被定義成vec4類(lèi)型

一個(gè)vec4是包含4個(gè)分量的向量,如果這個(gè)vec4代表位置,可以認(rèn)為這4個(gè)分量是x、y、z和w坐標(biāo),x、y和z對(duì)應(yīng)一個(gè)三維位置,而w是一個(gè)特殊的坐標(biāo),默認(rèn)情況下,OpenGL都是把向量的前三個(gè)坐標(biāo)設(shè)為0,并把最后一個(gè)坐標(biāo)設(shè)為1

main

是著色器的入口

gl_Position

頂點(diǎn)著色器一定要給輸出變量 gl_Position 賦值,因?yàn)?OpenGL 會(huì)把 gl_Position 中存儲(chǔ)的值作為當(dāng)前頂點(diǎn)的最終位置,并把這些頂點(diǎn)組裝成點(diǎn)、直線和三角形

gl_PointSize

指定點(diǎn)的大小

片段著色器

片段著色器的主要作用是:為每個(gè)片段生成最終顏色

對(duì)于基本圖元(點(diǎn)、直線和三角形)的每個(gè)片段,片段著色器都會(huì)被調(diào)用一次,因此,如果一個(gè)三角形被映射到10000個(gè)片段,片段著色器就會(huì)被調(diào)用10000次

光柵化(Rasterization)技術(shù)

將頂點(diǎn)幾何信息轉(zhuǎn)換成柵格組成的圖像的過(guò)程
光柵化的目的是找出一個(gè)幾何單元所覆蓋的像素,根據(jù)三角形頂點(diǎn)的位置,確定需要多少個(gè)像素點(diǎn)才能構(gòu)成這個(gè)三角形,以及每個(gè)像素點(diǎn)應(yīng)該得到哪些信息

片段著色器代碼

新建 raw/draw1_fragment.frag 文件,寫(xiě)入如下代碼

precision mediump float;
uniform vec4 u_Color;
void main(){
    gl_FragColor=u_Color;
}
精度限定符

在這個(gè)片段著色器中,"precision mediump float;" 定義了所有浮點(diǎn)數(shù)據(jù)類(lèi)型的默認(rèn)精度,就像在Java 代碼中選擇浮點(diǎn)數(shù)還是雙精度浮點(diǎn)數(shù)一樣,可以選擇lowp、mediump和 highp

頂點(diǎn)著色器同樣可以改變其默認(rèn)的精度,但是,對(duì)于一個(gè)頂點(diǎn)的位置而言,精確度是最重要的,因此默認(rèn)頂點(diǎn)著色器的精度設(shè)置成最高級(jí)highp

高精度數(shù)據(jù)類(lèi)型更加精確,但是這是以降低性能為代價(jià)的,對(duì)于片段著色器,選擇 mediump,是基于速度和質(zhì)量的權(quán)衡

uniform

這次我們傳遞一個(gè) uniform 類(lèi)型,叫做 u_Color,它不像 attribute 類(lèi)型,每個(gè)頂點(diǎn)都要設(shè)置一個(gè),一個(gè) uniform 會(huì)讓每個(gè)頂點(diǎn)都使用同一個(gè)值,除非我們?cè)俅胃淖兯?/p>

u_Color也是一個(gè) vec4,四個(gè)分量分別對(duì)應(yīng)紅色、綠色、藍(lán)色和阿爾法

gl_FragColor

片段著色器一定要給 gl_GragColor賦值,OpenGL會(huì)使用這個(gè)顏色作為當(dāng)前片段的最終顏色,例如上面的,把 u_Color 賦值給那個(gè)特殊的輸出變量 gl_FragColor

加載著色器

就是普通的讀取著色器代碼的文本內(nèi)容

public class FileUtil {
    public static String getRawText(Context context, int rawId) {
        InputStream inputStream = context.getResources().openRawResource(rawId);
        InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        StringBuilder builder = new StringBuilder();
        String line;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                builder.append(line);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return builder.toString();
    }
}

編譯著色器

public class ShaderHelper {
    private static final String TAG = "ShaderHelper";

    /**
     * 編譯頂點(diǎn)著色器
     *
     * @param context
     * @param rawId   頂點(diǎn)著色器代碼資源id
     * @return
     */
    public static int compileVertexShader(Context context, int rawId) {
        return compileShader(context, GLES20.GL_VERTEX_SHADER, rawId);
    }

    /**
     * 編譯片段著色器
     *
     * @param context
     * @param rawId   片段著色器代碼資源id
     * @return
     */
    public static int compileFragmentShader(Context context, int rawId) {
        return compileShader(context, GLES20.GL_FRAGMENT_SHADER, rawId);
    }

    /**
     * 編譯著色器
     *
     * @param context
     * @param type    著色器類(lèi)型,分為頂點(diǎn)著色器和片段著色器
     * @param rawId   著色器代碼資源id
     * @return
     */
    private static int compileShader(Context context, int type, int rawId) {
        // 更加type創(chuàng)建一個(gè)新的著色器對(duì)象,返回一個(gè)整型值,這個(gè)整型值就是OpenGL對(duì)象的引用
        // 無(wú)論后面什么時(shí)候想要引用這個(gè)對(duì)象,就要把這個(gè)整型值傳回OpenGL
        int shaderId = GLES20.glCreateShader(type);
        // 檢查著色器對(duì)象是否成功創(chuàng)建,返回值為 0 則創(chuàng)建失敗
        if (shaderId == 0) {
            Log.d(TAG, "compileShader: glCreateShader filed!");
        }

        // 得到著色器代碼
        String shaderSource = FileUtil.getRawText(context, rawId);
        // 告訴OpenGL讀入字符串shaderSource引用的源代碼,并把它與shaderId所引用的著色器對(duì)象關(guān)聯(lián)起來(lái)
        GLES20.glShaderSource(shaderId, shaderSource);
        // 告訴OpenGL編譯先前上傳到shaderId的源代碼
        GLES20.glCompileShader(shaderId);

        // 檢查OpenGL是否能成功地編譯這個(gè)著色器
        int[] status = new int[1];
        // 告訴OpenGL讀取與shaderId關(guān)聯(lián)的編譯狀態(tài),并把編譯結(jié)果寫(xiě)入status的第0個(gè)元素
        GLES20.glGetShaderiv(shaderId, GL_COMPILE_STATUS, status, 0);
        if (status[0] != GL_TRUE) {
            // 通過(guò)glGetShaderiv方法獲取編譯狀態(tài)的時(shí)候,OpenGL只給出一個(gè)簡(jiǎn)單的是或否的回答
            // 如果失敗了,可以通過(guò)調(diào)用glGetShaderInfoLog(shaderId)獲得關(guān)于著色器信息日志
            String shaderInfoLog = GLES20.glGetShaderInfoLog(shaderId);
            Log.d(TAG, "compileShader: glCompileShader failed! log=" + shaderInfoLog);

            // 編譯著色器失敗,就刪除這個(gè)著色器,并返回0
            GLES20.glDeleteShader(shaderId);
            return 0;
        }
        return shaderId;
    }
}

生成OpenGL程序

我們已經(jīng)加載并編譯了一個(gè)頂點(diǎn)著色器和一個(gè)片段著色器,下一步就是把它們綁定在一起放入一個(gè)OpenGL程序

public class ProgramHelper {
    private static final String TAG = "ProgramHelper";

    /**
     * 通過(guò)頂點(diǎn)和片段著色器生成OpenGL程序
     *
     * @param vertexId
     * @param fragmentId
     * @return
     */
    public static int getProgram(int vertexId, int fragmentId) {
        // 新建程序?qū)ο?,返回的整形值表示程序?qū)ο蟮囊?        int program = GLES20.glCreateProgram();
        // 檢查程序是否創(chuàng)建成功
        if (program == 0) {
            Log.d(TAG, "getProgram: create program failed!");
            return 0;
        }
        // 將頂點(diǎn)著色器和片段著色器都附加到程序?qū)ο笊?        GLES20.glAttachShader(program, vertexId);
        GLES20.glAttachShader(program, fragmentId);

        // 鏈接程序,把著色器聯(lián)合起來(lái)
        GLES20.glLinkProgram(program);
        // 檢查鏈接是否成功,鏈接成功并不能保證執(zhí)行成功
        int[] status = new int[1];
        GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, status, 0);
        if (status[0] != GLES20.GL_TRUE) {
            // 如果鏈接失敗,打印這個(gè)程序的信息日志
            String programInfoLog = GLES20.glGetProgramInfoLog(program);
            Log.d(TAG, "getProgram: glLinkProgram failed! log=" + programInfoLog);
            // 鏈接失敗,意味著這個(gè)程序無(wú)法使用,刪除它并返回0
            GLES20.glDeleteProgram(program);
            return 0;
        }

        // 正確創(chuàng)建程序后,代碼中就不再使用著色器引用了,因此刪除它
        GLES20.glDeleteShader(vertexId);
        GLES20.glDeleteShader(fragmentId);
        return program;
    }

    /**
     * 驗(yàn)證程序應(yīng)用程序有效性,在glUseProgram方法后調(diào)用
     *
     * @param program
     */
    public static void validate(int program) {
        // 是一個(gè)比較耗時(shí)的操作,一般只是用作調(diào)試
        if (LogUtil.isDebug) {
            int[] status = new int[1];
            GLES20.glGetProgramiv(program, GLES20.GL_VALIDATE_STATUS, status, 0);
            if (status[0] != GLES20.GL_TRUE) {
                String programInfoLog = GLES20.glGetProgramInfoLog(program);
                Log.d(TAG, "getProgram: program is invalidate log=" + programInfoLog);
                // 程序無(wú)效,意味著這個(gè)程序無(wú)法使用,刪除它并返回0
                GLES20.glDeleteProgram(program);
            }
        }
    }
}

在渲染類(lèi)中調(diào)用OpenGL程序

public class MyRenderer implements GLSurfaceView.Renderer {
    // 代表位置信息占據(jù)頂點(diǎn)數(shù)組幾個(gè)數(shù)據(jù)
    private static final int POSITION_COMPONENT_COUNT = 2;
    // 表示每個(gè)浮點(diǎn)數(shù)都占用4個(gè)字節(jié),把存儲(chǔ)頂點(diǎn)的內(nèi)存從Java堆復(fù)制到本地堆的時(shí)候使用
    private static final int BYTES_PER_FLOAT = 4;
    private final FloatBuffer mVertexBuffer;
    private Context mContext;
    private int u_color;
    private int a_position;
    private int program;

    public MyRenderer(Context context) {
        mContext = context;
        // 使用 float[] 存儲(chǔ)頂點(diǎn)屬性
        float[] vertex = new float[]{
                // 第一個(gè)三角形的頂點(diǎn)位置信息
                -0.8f, -0.8f,
                0.8f, 0.8f,
                -0.8f, 0.8f,

                // 第二個(gè)三角形的頂點(diǎn)位置信息
                -0.8f, -0.8f,
                0.8f, -0.8f,
                0.8f, 0.8f,

                // 中間的線條的頂點(diǎn)位置信息
                -0.8f, 0f,
                0.8f, 0f,

                // 上下兩個(gè)點(diǎn)的頂點(diǎn)位置信息
                0f, -0.5f,
                0f, 0.5f
        };
        // 將Java堆內(nèi)存的頂點(diǎn)數(shù)據(jù)復(fù)制到本地堆
        mVertexBuffer = ByteBuffer.allocateDirect(vertex.length * BYTES_PER_FLOAT)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer()
                .put(vertex);
    }

    /**
     * 當(dāng)Surface被創(chuàng)建的時(shí)候,GLSurfaceView會(huì)調(diào)用這個(gè)方法
     * 程序第一次運(yùn)行、設(shè)備被喚醒或者從其他activity切換回來(lái)時(shí),可能會(huì)被調(diào)用,因此可能會(huì)被調(diào)用多次
     *
     * @param gl10
     * @param eglConfig
     */
    @Override
    public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
        // 設(shè)置清空屏幕用的顏色
        glClearColor(1f, 1f, 1f, 1f);

        // 頂點(diǎn)著色器和片段著色器的引用
        int vertexShader = ShaderHelper.compileVertexShader(mContext, R.raw.draw1_vertex);
        int fragmentShader = ShaderHelper.compileFragmentShader(mContext, R.raw.draw1_fragment);
        // 利用頂點(diǎn)著色器和片段著色器生成OpenGL程序
        program = ProgramHelper.getProgram(vertexShader, fragmentShader);
        //將應(yīng)用程序設(shè)置為活動(dòng)程序,告訴OpenGL在繪制任何東西到屏幕上的時(shí)候要使用program程序
        glUseProgram(program);

        // 獲取在著色器中定義的uniform的位置,uniform的位置不能指定,當(dāng)OpenGL把著色器鏈接成一個(gè)程序的時(shí)候
        // 它用一個(gè)位置編號(hào)把著色器中定義的uniform關(guān)聯(lián)起來(lái)了,這些位置編號(hào)用來(lái)給著色器發(fā)送數(shù)據(jù)
        u_color = glGetUniformLocation(program, "u_Color");
        // 獲取在著色器中定義的attribute的位置,如果不調(diào)用glBindAttribLocation()方法
        // 給attribute分配位置編號(hào),OpenGL就會(huì)自動(dòng)分配
        a_position = glGetAttribLocation(program, "a_Position");

        // 告訴OpenGL從這個(gè)緩沖區(qū)中讀取數(shù)據(jù)之前,確保它會(huì)從開(kāi)頭處開(kāi)始讀取數(shù)據(jù)
        mVertexBuffer.position(0);
        // 關(guān)聯(lián)attribute與頂點(diǎn)數(shù)據(jù)的數(shù)組,也就是告訴OpenGL到哪里找到屬性a_Position對(duì)應(yīng)的數(shù)據(jù)
        glVertexAttribPointer(a_position, 2, GL_FLOAT, false, 0, mVertexBuffer);
        // 使能頂點(diǎn)數(shù)組,每個(gè)attribute都要調(diào)用一次,相當(dāng)于一個(gè)開(kāi)關(guān),調(diào)用后,OpenGL才知道到哪里找到屬性a_Position對(duì)應(yīng)的數(shù)據(jù)
        glEnableVertexAttribArray(a_position);
    }

    /**
     * Surface被創(chuàng)建以后,每次Surface尺寸變化時(shí),這個(gè)方法都會(huì)被GLSurfaceView調(diào)用
     * 例如在橫屏、豎屏來(lái)回切換的時(shí)候,Surface尺寸會(huì)發(fā)生變化
     *
     * @param gl10
     * @param width
     * @param height
     */
    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        // 設(shè)置視口(viewport)尺寸,告訴OpenGL可以用來(lái)渲染的surface的大小
        glViewport(0, 0, width, height);
    }

    /**
     * 當(dāng)繪制一幀時(shí),這個(gè)方法會(huì)被GLSurfaceView調(diào)用,在這個(gè)方法中,一定要繪制一些東西,即使只是清空屏幕
     * 因?yàn)?,在這個(gè)方法返回后,渲染緩沖區(qū)會(huì)被交換并顯示在屏幕上,如果什么都沒(méi)畫(huà),可能會(huì)看到閃爍效果
     *
     * @param gl10
     */
    @Override
    public void onDrawFrame(GL10 gl10) {
        // 清空屏幕,會(huì)擦除屏幕上的所有顏色,并用之前glClearColor()調(diào)用定義的顏色填充整個(gè)屏幕
        glClear(GL_COLOR_BUFFER_BIT);
        // 調(diào)用glUniform4f()更新著色器代碼中的u_Color的值,與屬性不同,uniform 的分量沒(méi)有默認(rèn)值
        // 因此,如果一個(gè)uniform在著色器中被定義為vec4類(lèi)型,需要提供所有四個(gè)分量的值
        glUniform4f(u_color, 0f, 1f, 0f, 1f);
        // 一個(gè)參數(shù)告訴OpenGL想要畫(huà)的形狀
        // 第二個(gè)參數(shù)告訴OpenGL從頂點(diǎn)數(shù)組的哪里開(kāi)始讀頂點(diǎn)
        // 第三個(gè)參數(shù)是告訴OpenGL讀入幾個(gè)頂點(diǎn)
        glDrawArrays(GL_TRIANGLES, 0, 6);

        glUniform4f(u_color, 0.0f, 0.0f, 0.0f, 1.0f);
        glDrawArrays(GL_LINES, 6, 2);

        glUniform4f(u_color, 0f, 0f, 1f, 1f);
        glDrawArrays(GL_POINTS, 8, 1);
        glDrawArrays(GL_POINTS, 9, 1);
    }
}

glVertexAttribPointer參數(shù)說(shuō)明

參數(shù) 說(shuō)明
int index 屬性位置,通過(guò) glGetAttribLocation() 方法獲取的值
int size 屬性占幾個(gè)Float,我們?yōu)槊總€(gè)頂點(diǎn)只傳遞了兩個(gè)分量,代表 x 和 y 坐標(biāo),但是在著色器中,a_Position被定義為vec4,它有4個(gè)分量。如果分量沒(méi)有被指定值,默認(rèn)情況下,OpenGL 會(huì)把前3個(gè)分量設(shè)為0,最后一個(gè)分量設(shè)為1
int type 數(shù)據(jù)的類(lèi)型,一般都是 GL_FLOAT
boolean normalized 只有使用整型數(shù)據(jù)的時(shí)候,這個(gè)參數(shù)才有意義,因此可以暫時(shí)把它安全地忽略掉
int stride 當(dāng)一個(gè)數(shù)組存儲(chǔ)多于一個(gè)屬性時(shí),例如同時(shí)存儲(chǔ)顏色和位置信息,應(yīng)該填入 (顏色+位置) 占的 Float個(gè)數(shù) * Float 所占字節(jié)數(shù)
Buffer ptr 填入 從Java堆內(nèi)存的數(shù)據(jù)復(fù)制到本地堆的 Buffer

效果

最好在真機(jī)上調(diào)試,我運(yùn)行在 Genymotion 模擬器上的時(shí)候,沒(méi)有任何報(bào)錯(cuò)信息,但中間的橫線畫(huà)不出來(lái)


image-20211220213002945.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容