1. 程式人生 > 其它 >最通俗的安卓OpenGL教學06——使用VBO

最通俗的安卓OpenGL教學06——使用VBO

技術標籤:OpenGL ES

OpenGL VBO 即頂點緩衝物件 ,目的是提高頂點座標獲取的效率。使用 VBO時,能把頂點資料快取到GPU開闢的一段記憶體中,然後使用時不必再從本地獲取,而是直接從視訊記憶體中獲取,這樣就能提升繪製的效率,不使用 VBO,每次繪製( glDrawArrays )圖形時都是從本地記憶體處獲取頂點資料然後傳輸給 OpenGL來繪製,這樣就會頻繁的操作 CPU->GPU增大開銷,從而降低效率。

OpenGL VBO的建立大概有以下步驟:

  1. 建立VBO
  2. 繫結VBO
  3. 分配VBO需要的快取大小
  4. 為VBO設定頂點資料的值
  5. 解綁VBO

OpenGL VBO的使用大概有以下步驟:

  1. 繫結VBO
  2. 從VBO中獲取頂點,設定頂點資料
  3. 解綁VBO

1. VBO建立

在onSurfaceCreated時建立VBO:


        //建立 VBO
        int[] vbo = new int[1];
        GLES20.glGenBuffers(1, vbo, 0);
        vboID = vbo[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID);

        //分配 VBO需要的快取大小
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER,
vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW); //設定頂點座標資料的值到 VBO GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer); //設定紋理座標資料的值到 VBO GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.
length * 4, fragmentBuffer); //解綁 VBO,指的是離開對 VBO的配置,進入下一個狀態 GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

2. VBO使用

在onDrawFrame時使用VBO:

  		//開始使用 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID);
        //使能頂點屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(vPosition);
        //使能之後,為頂點屬性賦值,從VBO裡獲取 繫結頂點座標; 注意:最後一個引數如果是 vertexBuffer,那麼就沒有用到 VBO,那就還是從CPU裡取頂點
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
        //使能片元屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(fPosition);
        //使能之後,為片元屬性賦值,從VBO裡獲取 繫結紋理座標; 注意:最後一個引數為 VBO裡的偏移量
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);

        //退出 VBO的使用 進入紋理繫結狀態
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

3.完整程式碼

package com.york.media.opengl.demo.vbo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLUtils;

import com.york.media.opengl.R;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

/**
 * author : York
 * date   : 2020/12/20 18:01
 * desc   : OpenGL VBO 即頂點緩衝物件 ,目的是提高頂點座標獲取的效率
 * <p>
 * 不使用 VBO時,每次繪製( glDrawArrays )圖形時都是從本地記憶體處獲取頂點資料然後傳輸給 OpenGL來繪製,這樣就會頻繁的操作 CPU->GPU增大開銷,從而降低效率。
 * 使用 VBO時,能把頂點資料快取到GPU開闢的一段記憶體中,然後使用時不必再從本地獲取,而是直接從視訊記憶體中獲取,這樣就能提升繪製的效率。
 */
public class YVboRender implements YGLSurfaceView.YGLRender {

    private final Context mContext;
    private final FloatBuffer vertexBuffer;
    private final FloatBuffer fragmentBuffer;
    private int program;
    private int vPosition;
    private int fPosition;

    private int bitmapTexture;
    private int vboID;
    //頂點座標
    float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };

    //紋理座標
    float[] fragmentData = {
            0f, 1f,
            1f, 1f,
            0f, 0f,
            1f, 0f
    };


    public YVboRender(Context context) {
        this.mContext = context;

        //讀取頂點座標
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(vertexData);
        vertexBuffer.position(0);

        //讀取紋理座標
        fragmentBuffer = ByteBuffer.allocateDirect(fragmentData.length * 4).order(ByteOrder.nativeOrder()).asFloatBuffer().put(fragmentData);
        fragmentBuffer.position(0);

    }

    @Override
    public void onSurfaceCreated() {
        //載入頂點著色器 shader
        String vertexSource = YShaderUtil.getRawResource(mContext, R.raw.screen_vert);
        //載入片元著色器 shader
        String fragmentSource = YShaderUtil.getRawResource(mContext, R.raw.screen_frag);
        //獲取源程式
        program = YShaderUtil.createProgram(vertexSource, fragmentSource);
        //從渲染程式中得到著頂點色器中的屬性
        vPosition = GLES20.glGetAttribLocation(program, "vPosition");
        //從渲染程式中得到片元著色器中的屬性
        fPosition = GLES20.glGetAttribLocation(program, "fPosition");

        //建立 VBO
        int[] vbo = new int[1];
        GLES20.glGenBuffers(1, vbo, 0);
        vboID = vbo[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID);

        //分配 VBO需要的快取大小
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
        //設定頂點座標資料的值到 VBO
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
        //設定紋理座標資料的值到 VBO
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
        //解綁 VBO,指的是離開對 VBO的配置,進入下一個狀態
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        //建立 1個紋理,放入到 int [] textureIds
        int[] textureIds = new int[1];
        GLES20.glGenTextures(1, textureIds, 0);//第三個引數是指從哪兒開始取
        bitmapTexture = textureIds[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexture);//在沒設定點的情況下預設是繫結 0號紋理

        //設定紋理的環繞方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_REPEAT);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_REPEAT);
        //設定紋理的過濾方式
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);

        //繫結bitmap 到  textureIds[0] 2D紋理
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.nobb);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        bitmap.recycle();

        //解綁紋理 指的是離開對 紋理的配置,進入下一個狀態
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

    @Override
    public void onSurfaceChanged(int width, int height) {
        //設定視窗大小
        GLES20.glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame() {
        //清除螢幕,此處用的是紅色
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
        GLES20.glClearColor(1f, 0f, 0f, 1f);
        //使用著色器源程式
        GLES20.glUseProgram(program);

        //開始使用 VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboID);
        //使能頂點屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(vPosition);
        //使能之後,為頂點屬性賦值,從VBO裡獲取 繫結頂點座標; 注意:最後一個引數如果是 vertexBuffer,那麼就沒有用到 VBO,那就還是從CPU裡取頂點
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
        //使能片元屬性陣列,使之有效
        GLES20.glEnableVertexAttribArray(fPosition);
        //使能之後,為片元屬性賦值,從VBO裡獲取 繫結紋理座標; 注意:最後一個引數為 VBO裡的偏移量
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);

        //退出 VBO的使用
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

        //要開始繪製紋理了,啟用紋理 0號, 之所以啟用 0號,是因為在沒設定點的情況下預設是 0號
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        //繫結 bitmapTexture 到紋理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexture);
        //繪製
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        //解綁 2D紋理,退出對紋理的使用
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
    }

}

效果其實一樣,看不太出效果,哈哈哈!