1. 程式人生 > >android平臺下OpenGL ES 3.0例項詳解頂點屬性、頂點陣列

android平臺下OpenGL ES 3.0例項詳解頂點屬性、頂點陣列

OpenGL ES 3.0學習實踐

目錄

頂點屬性

頂點資料也稱作頂點屬性,指定每個頂點的資料。如果你想要繪製固定顏色的三角形,可以指定一個常量值,用於三角形的全部3個頂點。但是,組成三角形的3個頂點的位置不同,所以我們指定一個頂點陣列來儲存3個位置值。

頂點屬性資料可以用一個頂點陣列對每個頂點指定,也可以將一個常量值用於一個圖元的所有頂點。
所有OpenGL ES 3.0實現必須支援最少16個頂點屬性

査詢OpenGL ES 3.0實現真正支援的頂點屬性的準確數:

int[]
maxVertexAttribs = new int[1]; GLES30.glGetIntegerv(GLES30.GL_MAX_VERTEX_ATTRIBS, maxVertexAttribs, 0);

常量頂點屬性

常量頂點屬性對於一個圖元的所有頂點都相同,所以對一個圖元的所有頂點只需指定一個值。可以用如下任何一個函式指定:

void GLES30.glVertexAttrib1f(int index, float x);
void GLES30.glVertexAttrib2£(int index, float x, float y);
void GLES30.glVertexAttrib3£(
int index, float x, float y, float z); void GLES30.glVertexAttrib4£(int index, float x, float y, float z, float w); void GLES30.glVertexAttrib1fv (int index, float[] values,int offset); void GLES30.glVertexAttrib2£v(int index, float[] values,int offset); void GLES30.glVertexAttrib3£v(int index, float[] values,int offset); void GLES30.glVertexAttrib4£v(int index, float[] values,int offset);

其中glVertexAttrib*方法用於載入index指定的通用頂點屬性。

  • glVertexAttrib1f和glVertexAttrib1fv在通用頂點屬性中載入(x, 0.0, 0.0, 1.0)
  • glVertexAttrib2f和glVertexAttrib2fv在通用頂點屬性中載入(x, y, 0.0, 1.0)
  • glVertexAttrib3f和glVertexAttrib3fV在通用頂點屬性中載入(x, y, z, 1.0)
  • glVertexAltrib4f和glVertexAttrib4fv在通用頂點屬性中載入(x, y, z, w)

頂點陣列

頂點陣列指定每個頂點的屬性,是儲存在應用程式地址空間(OpenGLES稱為客戶空間)的緩衝區。它們作為頂點緩衝物件的基礎,提供指定頂點屬性資料的一種高效、靈活的手段,頂點陣列用glVertexAttribPointer或glVertexAttribIPointer指定。

分配和儲存頂點屬性資料有兩種常用的方法:

  • 在一個緩衝區中儲存頂點屬性:這種方法稱為結構陣列。結構表示頂點的所有屬性,每個頂點有一個屬性的陣列。
  • 在單獨的緩衝區中儲存每個頂點屬性:這種方法稱為陣列結構

這裡要注意的是glVertexAttribPointer的倒數第二個引數stride

每個頂點由size指定的頂點屬性分量順序儲存。stride指定頂點索引 I 和(I+1)表示的頂點資料之間的位移。如果stride為0,則每個頂點的屬性資料順序儲存。如果stride大於0,則使用該值作為獲取下一個索引表示的頂點資料的跨距。

現在假定每個頂點有5個頂點屬性:位置和顏色,這些屬性一起儲存在為所有頂點分配的一個緩衝區中。頂點位置屬性以2個浮點數的向量的形式指定,頂點顏色3個浮點數向量的形式指定。

這個緩衝區的記憶體佈局如下圖所示:

緩衝區的跨距(STRIDE)為組成頂點的所有屬性總大小(8個位元組用於位置,12個位元組用於顏色),等於5個浮點數即:20個位元組

下面來實踐一下用glVertexAttribPointer指定頂點屬性

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

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

    private final FloatBuffer vertexBuffer;

    private int mProgram;

    /**
     * 位置頂點屬性的大小
     * 包含(x,y)
     */
    private static final int VERTEX_POSITION_SIZE = 2;

    /**
     * 顏色頂點屬性的大小
     * 包含(r,g,b)
     */
    private static final int VERTEX_COLOR_SIZE = 3;

    /**
     * 浮點型資料佔用位元組數
     */
    private static final int BYTES_PER_FLOAT = 4;

    /**
     * 跨距
     */
    private static final int VERTEX_ATTRIBUTES_SIZE = (VERTEX_POSITION_SIZE + VERTEX_COLOR_SIZE) * BYTES_PER_FLOAT;

    /**
     * 點的座標
     */
    private float[] vertexPoints = new float[]{
            //前兩個為座標,後三個為顏色
            0.0f, 0.0f, 1.0f, 1.0f, 1.0f,
            -0.5f, -0.5f, 1.0f, 1.0f, 1.0f,
            0.5f, -0.5f, 1.0f, 1.0f, 1.0f,
            0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
            -0.5f, 0.5f, 1.0f, 1.0f, 1.0f,
            -0.5f, -0.5f, 1.0f, 1.0f, 1.0f,
            //兩個點的頂點屬性
            0.0f, 0.25f, 0.5f, 0.5f, 0.5f,
            0.0f, -0.25f, 0.5f, 0.5f, 0.5f,
    };

    public VertexPointerRenderer() {
        //分配記憶體空間,每個浮點型佔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_pointer_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_pointer_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //使用程式片段
        GLES30.glUseProgram(mProgram);

        vertexBuffer.position(0);
        GLES30.glVertexAttribPointer(0, VERTEX_POSITION_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer);
        //啟用頂點屬性
        GLES30.glEnableVertexAttribArray(0);
        //定位本地記憶體的位置
        vertexBuffer.position(VERTEX_POSITION_SIZE);
        GLES30.glVertexAttribPointer(1, VERTEX_COLOR_SIZE, GLES30.GL_FLOAT, false, VERTEX_ATTRIBUTES_SIZE, vertexBuffer);

        GLES30.glEnableVertexAttribArray(1);
    }

    @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.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, 6);

        //繪製兩個點
        GLES30.glDrawArrays(GLES30.GL_POINTS, 6, 2);

    }
}

頂點著色器

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = aColor;
}

片段著色器

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

注意:OpenGLES 3.0建議應用程式使用頂點緩衝區物件,避免使用客戶端頂點陣列,以實現最佳效能,在OpenGLES3.0中,總是建議使用頂點緩衝區物件。

效能提示

OpenGLES 3.0硬體實現,在大部分情況下,答案是結構陣列。因為每個頂點的屬性資料可以順序方式讀取,這最有可能造成高效的記憶體訪問模式。使用結構陣列的缺點在應用程式需要修改特定屬性時變得很明顯。如果頂點屬性資料的一個子集需要修改(例如:紋理座標),這將造成頂點緩衝區的跨距更新。當頂點緩衝區以緩衝區物件的形式提供時,需要重新載入整個頂點屬性緩衝區。可以通過將動態的頂點屬性儲存在單獨的 緩衝區來避免這種效率低下的情況。

在常置頂點屬性和頂點陣列之間選擇

應用程式可以讓OpenGL ES使用常量資料或者來自頂點陣列的資料,glEnableVertexAttribArrayglDisableVertexAttribArray方法分別用於啟用和禁用通用頂點屬性陣列。如果某個通用屬性索引的頂點屬性陣列被禁用,將使用為該索引指定的常量頂點屬性資料。

選擇常量或者頂點陣列頂點屬性:

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

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

    private final FloatBuffer vertexBuffer, colorBuffer;

    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 float[] vertexColors = new float[]{
            0.5f, 0.5f, 0.8f, 1.0f
    };

    public EnableVertexRenderer() {
        //分配記憶體空間,每個浮點型佔4位元組空間
        vertexBuffer = ByteBuffer.allocateDirect(vertexPoints.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //傳入指定的座標資料
        vertexBuffer.put(vertexPoints);
        vertexBuffer.position(0);
        //為顏色資料分配本地記憶體空間
        colorBuffer = ByteBuffer.allocateDirect(vertexColors.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //傳入指定的座標資料
        colorBuffer.put(vertexColors);
        colorBuffer.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_enable_shader));
        final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_enable_shader));
        //連結程式片段
        mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
        //使用程式片段
        GLES30.glUseProgram(mProgram);
    }

    @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.glVertexAttrib4fv(1, colorBuffer);

        //獲取位置的頂點陣列
        GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
        //啟用位置頂點屬性
        GLES30.glEnableVertexAttribArray(0);

        //繪製矩形
        GLES30.glDrawArrays(GLES30.GL_TRIANGLES, 0, 3);

        //禁用頂點屬性
        GLES30.glDisableVertexAttribArray(0);
    }
}

頂點著色器

#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aColor;
out vec4 vColor;
void main() {
     gl_Position  = vPosition;
     gl_PointSize = 10.0;
     vColor = aColor;
}

片段著色器

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

上面的註釋已經很清楚了,頂點的顏色是一個常量值,直接使用glVertexAttrib4fv指定,不啟用頂點屬性陣列1,而位置屬性通過glVertexAttribPointer獲取,並且通過glEnableVertexAttribArray啟用,用來處理不同的位置座標。

頂點著色器中宣告頂點屬性

OpenGL ES 3.0頂點著色器中,變數通過使用in限定符宣告為頂點屬性。屬性變數也可以選擇包含一個佈局限定符,提供屬性索引

layout(location = 0) in vec4 apposition;
layout(location = 1) in vec2 a_texcoord;
layout(location = 2) in vec3 a_normal;

in限定符只能用於資料型別float、vec2、vec3、vec4、int、ivec2、ivec3、ivec4、uint、 uvec2、uvec3、uvec4、mat2、mat2x2、mat2x3、mat2x4、mat3、mat3x3、mat3x4、mat4、 mat4x2和mat4x3。屬性變數不能宣告為陣列或者結構。

OpenGL ES 3.0實現支援GL_MAX_VERTEX_ATTRIBS四分量向量頂點屬性。宣告為標量、二分量向量或者三分量向量的頂點屬性將被當作一個四分量向量屬性計算。宣告為二維、三維或者四維矩陣的頂點屬性將分別被作為2、3或者4個四分量向量屬性計算。與編譯器自動打包的統一變數及頂點著色器輸出/片段著色器輸入變數不同,屬性不進行打包。在用小於四分量向量的尺寸宣告頂點屬性時請小心考慮,因為可用的最少頂點屬性是有限的資源。將它們一起打包到單個四分量屬性可能比在頂點著色器中宣告為單個頂點屬性更好。

注意:在頂點著色器中宣告為頂點屬性的變數是隻讀變數,不能修改。屬性可以在頂點著色器內部宣告,但是如果沒有使用,就不會被認為是活動屬性,從而不會被計入限制。如果在頂點著色器中使用的屬性數量大於GL_MAX_VERTEX_ ATTRIBS,這個頂點著色器就無法連結。

將頂點屬性繫結到頂點著色器中的屬性變數

頂點屬性索引綁對映到頂點著色器中的一個屬性變數名稱,有三種方法:

  • 索引可以在頂點著色器原始碼中用layout (location = N)限定符指定,推薦使用這種方式
      將屬性繫結到一個位置的最簡單方法是簡單地使用layout(location = N)限定符,這種方法需要的程式碼最少。
  • OpenGL ES 3.0將通用頂點屬性索引繫結到屬性名稱。
  • 應用程式將頂點屬性索引繫結到屬性名稱。

glBindAttribLocation方法可用於將通用頂點屬性索引繫結到頂點著色器中的一個屬性變數。這種繫結在下一次程式連結時生效,不會改變當前連結的程式中使用的繫結。這個方法要在link program程式之前呼叫。

void GLES30.glBindAttribLocation(int program,int index,String name);

如果之前綁定了name,則它所指定的繫結被索引代替。glBindAttribLocation甚至可以在頂點著色器連線到程式物件之前呼叫,因此,這個呼叫可以用於繫結任何屬性名稱。不存在的屬性名稱或者在連線到程式物件的頂點著色器中不活動的屬性將被忽略。

public static int linkProgram(int vertexShaderId, int fragmentShaderId) {
        final int programId = GLES30.glCreateProgram();
        if (programId != 0) {
            //將頂點著色器加入到程式
            GLES30.glAttachShader(programId, vertexShaderId);
            //將片元著色器加入到程式中
            GLES30.glAttachShader(programId, fragmentShaderId);

			//繫結自定義的屬性索引
            GLES30.glBindAttribLocation(programId, 5, "aColor");

            //連結著色器程式
            GLES30.glLinkProgram(programId);
            final int[] linkStatus = new int[1];

            GLES30.glGetProgramiv(programId, GLES30.GL_LINK_STATUS, linkStatus, 0);
            if (linkStatus[0] == 0) {
                String logInfo = GLES30.glGetProgramInfoLog(programId);
                System.err.println(logInfo);
                GLES30.glDeleteProgram(programId);
                return 0;
            }
            return programId;
        } else {
            //建立失敗
            return 0;
        }
    }

在連結階段,OpenGL ES 3.0實現為每個屬性變數執行如下操作:

對於每個屬性變數,檢查是否已經通過glBindAttribLocation指定了繫結。如果指定了一個繫結,則使用指定的對應屬性索引。否則,OpenGL ES實現將分配一個通用頂點屬性索引。

int GLES30.glGetAttribLocation(int program,String name);

glGetAttribLocation根據屬性名稱返回屬性索引

11-08 12:38:49.534 32752-640/com.onzhou.opengles.shader D/EnableVertexRenderer: color location = 5

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

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