1. 程式人生 > >android平臺下OpenGL ES 3.0例項詳解頂點緩衝區物件(VBO)和頂點陣列物件(VAO)

android平臺下OpenGL ES 3.0例項詳解頂點緩衝區物件(VBO)和頂點陣列物件(VAO)

OpenGL ES 3.0學習實踐

目錄

頂點緩衝區物件(VBO)

頂點緩衝區物件(Vertex Buffer Object),簡稱VBO。如果不使用頂點緩衝區物件(VBO)則是將頂點、顏色、紋理座標等資料存放在記憶體(客戶記憶體)當中,在每次進行glDrawArxays或者gIDrawElements等繪圖呼叫時,必須從客戶記憶體複製到圖形記憶體。而頂點緩衝區物件使OpenGL ES 3.0應用程式可以在高效能的圖形記憶體中分配和快取頂點資料,並從這個記憶體進行渲染,從而避免在每次繪製圖元的時候重新發送資料。不僅是頂點資料,描述圖元頂點索引、作為glDrawElements

引數傳遞的元素索引也可以快取。

OpenGL ES 3.0支援兩類緩衝區物件:陣列緩衝區物件元素陣列緩衝區物件

GL_ARRAY_BUFFER標誌指定的陣列緩衝區物件用於建立儲存頂點資料的緩衝區物件。
GL_ELEMENT_ARRAY_BUFFER標誌指定的元素陣列緩衝區物件用於建立儲存圖元索引的緩衝區物件。
OpenGL ES 3.0中的其他緩衝區物件型別:統一變數緩衝區變換反饋緩衝區畫素解包緩衝區畫素包裝緩衝區複製緩衝區

OpenGL ES 3.0建議應用程式對頂點屬性資料和元素索引使用頂點緩衝區物件

基於之前的工程專案,新建VertexBufferRenderer.java

檔案:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description: 頂點緩衝區
 */
public class VertexBufferRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private final FloatBuffer vertexBuffer;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 點的座標
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    /**
     * 緩衝陣列
     */
    private int[] vboIds = new int[1];

    public VertexBufferRenderer() {
        //分配記憶體空間,每個浮點型佔4位元組空間
        vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //傳入指定的座標資料
        vertexBuffer.put(vertexPoints);
        vertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設定背景顏色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //編譯
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_buffer_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_buffer_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);

        //1. 生成1個緩衝ID
        GLES30.glGenBuffers(1, vboIds, 0);

        //2. 繫結到頂點座標資料緩衝
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        //3. 向頂點座標資料緩衝送入資料
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);

        //4. 將頂點位置資料送入渲染管線
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //5. 啟用頂點位置屬性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //6. 使用程式片段
        GLES30.glUseProgram(mProgram);

        //7. 開始繪製三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        //8. 解綁VBO
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER,0);
    }
}

頂點著色器

#version 300 es
layout (location = 0) in vec4 vPosition;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = vec4(0.2f,0.4f,0.6f,1.0f);
}

片段著色器

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
     fragColor = vColor;
}

執行輸出:

GLES30.glDeleteBuffers

頂點陣列物件(VAO)

載入頂點屬性的兩種不同方式:使用客戶頂點陣列和使用頂點緩衝區物件

頂點緩衝區物件優於客戶頂點陣列,因為它們能夠減少CPU和GPU之間複製的資料量,從而獲得更好的效能。在OpenGL ES 3.0中引人了一個新特性,使頂點陣列的使用更加高效:頂點陣列物件(VAO)

使用頂點緩衝區物件設定繪圖操作可能需要多次呼叫glBindBufferglVertexAttribPointerglEnableVertexAttribArray。為了更快地在頂點陣列配置之間切換,OpenGL ES 3.0推出了頂點陣列物件VAO提供包含在 頂點陣列/頂點緩衝區物件配置之間切換所需要的所有狀態的單一物件。

OpenGLES 3.0中總是有一個活動的頂點陣列物件。之前的一些例項預設都在頂點陣列物件上操作(預設VAO的ID為0)。要建立新的頂點陣列物件,可以使用glGenVertexArrays方法。

還是基於之前的工程專案,新建VertexArrayRenderer.java檔案:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description:
 */
public class VertexArrayRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private final FloatBuffer vertexBuffer;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 點的座標
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    /**
     * 緩衝陣列
     */
    private int[] vaoIds = new int[1];

    private int[] vboIds = new int[1];

    public VertexArrayRenderer() {
        //分配記憶體空間,每個浮點型佔4位元組空間
        vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //傳入指定的座標資料
        vertexBuffer.put(vertexPoints);
        vertexBuffer.position(0);
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設定背景顏色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //編譯
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_array_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_array_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //使用程式片段
        GLES30.glUseProgram(mProgram);

        //生成1個緩衝ID
        GLES30.glGenVertexArrays(1, vaoIds, 0);

        //繫結VAO
        GLES30.glBindVertexArray(vaoIds[0]);

        //1. 生成1個緩衝ID
        GLES30.glGenBuffers(1, vboIds, 0);
        //2. 向頂點座標資料緩衝送入資料把頂點陣列複製到緩衝中
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, vertexBuffer, GLES30.GL_STATIC_DRAW);

        //3. 將頂點位置資料送入渲染管線
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //啟用頂點位置屬性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

        //4. 解綁VAO
        GLES30.glBindVertexArray(0);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {

        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //使用程式片段
        GLES30.glUseProgram(mProgram);

        //5. 繫結VAO
        GLES30.glBindVertexArray(vaoIds[0]);

        //6. 開始繪製三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        //7. 解綁VAO
        GLES30.glBindVertexArray(0);

    }
}

當應用程式結束一個或者多個頂點陣列物件的使用時,可以刪除它們。

void glDeleteVeitexAnays();

對映緩衝區物件

上面的兩個例子基本已經瞭解了glBufferData的使用,應用程式也可以將緩衝區物件資料儲存對映到應用程式的地址空間(也可以解除對映)。應用程式對映緩衝區而不使用glBufferData或者glBufferSubData載入資料的原因在於:

  • 對映緩衝區可以減少應用程式的記憶體佔用,因為可能只需要儲存資料的一個副本。
  • 在使用共享記憶體的架構上,對映緩衝區返回GPU儲存緩衝區的地址空間的直接指標。

通過對映緩衝區,應用程式可以避免複製步驟,從而實現更好的更新效能。 glMapBufferRange方法返回指向所有或者一部分(範圍)緩衝區物件資料儲存的Buffer。 這個Buffer可以供應用程式使用,以讀取或者更新緩衝區物件的內容。

Buffer GLES30.glMapBufferRange(int target,int offset,int length,int access);
訪問標誌 描述
GL_MAP_READ_BIT 應用程式將從返回的指標讀取
GL_MAP_WRITE_BIT 應用程式將寫人返回的指標

此外,應用程式可以包含如下可選訪問標誌:

訪問標誌 描述
GL_MAP_INVALIDATE_RANGE_BIT 表示指定範圍內的緩衝區內容可以在返回指標之前由驅動程式放棄。這個標誌不能與GL_MAP_READ_BIT組合使用
GL_MAP_INVALIDATE_BUFFER_BIT 表示整個緩衝區的內容可以在返回指標之前由驅動程式放棄。這個標誌不能與GL_MAP_READ_BIT組合使用
GL_MAP_FLUSH_EXPLICIT_BIT 表示應用程式將明確地用glFlushMappedBufferRange重新整理對對映範圍子範圍的操作。這個標誌不能與GL_MAP_WRITE_BIT組合使用
GL一MAP一UNSYNCHRONIZED一BIT 表示驅動程式在返回緩衝區範圍的指標之前不需要等待緩衝物件上的未決操作。如果有未決的操作,則未決操作的結果和緩衝區物件上的任何未來操作都變為未定義

基於SDK中的glMapBufferRange返回的實際上是java.nio.Buffer物件,這個物件可以直接操作本地記憶體。

還是基於之前的工程專案,新建MapBufferRenderer.java檔案:

/**
 * @anchor: andy
 * @date: 2018-11-02
 * @description: 對映緩衝區物件
 */
public class MapBufferRenderer implements GLSurfaceView.Renderer {

    private static final String TAG = "VertexBufferRenderer";

    private static final int VERTEX_POS_INDEX = 0;

    private static final int VERTEX_POS_SIZE = 3;

    private static final int VERTEX_STRIDE = VERTEX_POS_SIZE * 4;

    private int mProgram;

    /**
     * 點的座標
     */
    private float[] vertexPoints = new float[]{
            0.0f, 0.5f, 0.0f,
            -0.5f, -0.5f, 0.0f,
            0.5f, -0.5f, 0.0f
    };

    private int[] vboIds = new int[1];

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        //設定背景顏色
        GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
        //編譯
        final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_map_buffer_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_map_buffer_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);

        //1. 生成1個緩衝ID
        GLES30.glGenBuffers(1, vboIds, 0);
        //2. 向頂點座標資料緩衝送入資料把頂點陣列複製到緩衝中
        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, vboIds[0]);
        GLES30.glBufferData(GLES30.GL_ARRAY_BUFFER, vertexPoints.length * 4, null, GLES30.GL_STATIC_DRAW);

        //3. 對映緩衝區物件
        ByteBuffer buffer = (ByteBuffer) GLES30.glMapBufferRange(GLES30.GL_ARRAY_BUFFER, 0, vertexPoints.length * 4, GLES30.GL_MAP_WRITE_BIT | GLES30.GL_MAP_INVALIDATE_BUFFER_BIT);
        //4. 填充資料
        buffer.order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexPoints).position(0);

        //5. 將頂點位置資料送入渲染管線
        GLES30.glVertexAttribPointer(VERTEX_POS_INDEX, VERTEX_POS_SIZE, GLES30.GL_FLOAT, false, VERTEX_STRIDE, 0);
        //啟用頂點位置屬性
        GLES30.glEnableVertexAttribArray(VERTEX_POS_INDEX);

        //解除對映
        GLES30.glUnmapBuffer(GLES30.GL_ARRAY_BUFFER);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES30.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
        //使用程式片段
        GLES30.glUseProgram(mProgram);

        //6. 開始繪製三角形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        GLES30.glBindBuffer(GLES30.GL_ARRAY_BUFFER, 0);
    }
}

頂點著色器

#version 300 es
layout (location = 0) in vec4 vPosition;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = vec4(0.1f,0.2f,0.3f,1.0f);
}

片段著色器

#version 300 es
precision mediump float;
in vec4 vColor;
out vec4 fragColor;
void main() {
     fragColor = vColor;
}

取消對映glUnmapBuffer方法如下:

//target	必須設定為 GL_ARRAY_BUFFER
boolean glUnmapBuffer(int target);

如果取消對映操作成功,則glUnmapBufferglUnmapBuffer返回trueglMapBufferRange返回的Buffer在成功執行取消對映之後不再可以使用,如果頂點緩衝區物件資料儲存中的資料在緩衝區對映之後已經破壞,glUnmapBuffer將返回false,這可能是因為螢幕解析度的變化,OpenGL ES上下文使用多個螢幕或者導致對映記憶體被拋棄的記憶體不足事件所導致。

專案地址:
https://github.com/byhook/opengles4android

參考:
《OpenGL ES 3.0 程式設計指南第2版》
《OpenGL ES應用開發實踐指南Android卷》