初识:顶点缓冲对象VBO,帧缓冲对象FBO,正交投影【矩阵】在Android中的使用

一. 什么是VBO,它是用来做什么的。

  1. VBO: Vertex Buffer Object 【顶点缓冲类】

  2. 为什么要用VBO?
    不使用VBO时,我们每次绘制( glDrawArrays )图形时都是从本地内存处获取顶点数据然后传输给OpenGL来绘制,这样就会频繁的操作CPU->GPU增大开销,从而降低效率。
    使用VBO,我们就能把顶点数据缓存到GPU开辟的一段内存中,然后使用时不必再从本地获取,而是直接从显存中获取,这样就能提升绘制的效率。

  3. 在Android中创建VBO

(a). 创建VBO: GLES20.glGenBuffers(1, vbos, 0);

(b). 绑定VBO:GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]);

(c). 分配VBO需要的缓存大小:
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertex.length * 4,null, GLES20. GL_STATIC_DRAW);

(d). 为VBO设置顶点数据的值:
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);

(e). 解绑VBO:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

  1. 在Android中使用VBO

(a). 绑定VBO:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbos[0]);

(b). 设置顶点数据:
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);

(c).解绑VBO:
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);

二. 什么是FBO,它是用来做什么的。

  1. FBO: Frame Buffer object 【帧缓冲对象】

  2. 为什么要用FBO?
    当我们需要对纹理进行多次渲染采样时,而这些渲染采样是不需要展示给用户看的,所以我们就可以用一个单独的缓冲对象(离屏渲染)来存储我们的这几次渲染采样的结果,等处理完后才显示到窗口上。

  3. 优势
    提高渲染效率,避免闪屏,可以很方便的实现纹理共享等。

  4. 渲染方式
    渲染到缓冲区(Render)- 深度测试和模板测试
    渲染到纹理(Texture)- 图像渲染

  5. FBO大体工作过程


    FBO工作过程.png
  6. 纹理坐标系与FBO坐标系比较


    坐标系比较.jpg
  7. 在Android中创建FBO

(a).创建FBO
GLES20.glGenBuffers(1, fbos, 0);

(b).绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]);

(c).设置FBO分配内存大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 720, 1280, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);

(d).把纹理绑定到FBO
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureid, 0);

(e).检查FBO绑定是否成功GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE)

(f).解绑FBO GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

  1. 在Android中使用FBO

(1)、绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]);

(2)、获取需要绘制的图片纹理,然后绘制渲染

(3)、解绑FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

(4)、再把绑定到FBO的纹理绘制渲染出来

三. 正交投影,它是用来做什么的

存在问题.png

解决方法.png

上面我们得到的( ?)是不在归一化坐标范围内的,为了能使OpenGL正确的渲染,我们就需要把(?)以及其他边统一转换到归一化坐标内,这个操作就是正交投影

使用正交投影,不管物体多远多近,物体看起来总是形状、大小相同的。

1.Android中使用正交投影矩阵
在OpenGL中就需要用到矩形来改变顶点坐标的范围,最后再归一化就可以了。
(A)、顶点着色器中添加矩阵

attribute vec4 v_Position;
attribute vec2 f_Position;
varying vec2 ft_Position;
uniform mat4 u_Matrix;
void main() {
    ft_Position = f_Position;
    gl_Position = v_Position * u_Matrix;
}

(B)、然后根据图形宽高和屏幕宽高计算(?)的长度
orthoM(float[] m, int mOffset, float left, float right, float bottom, float top, float near, float far)

Matrix.orthoM(matrix, 0, -width / ((height / 702f * 526f)),  width / ((height / 702f * 526f)), -1f, 1f, -1f, 1f);
Matrix.orthoM(matrix, 0, -1, 1, - height / ((width / 526f * 702f)),  height / ((width / 526f * 702f)), -1f, 1f);

(C)、使用
GLES20.glUniformMatrix4fv(umatrix, 1, false, matrix, 0);

四.代码样例如下:

package app.antony.vfmdemo;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.opengl.GLUtils;
import android.opengl.Matrix;
import android.util.Log;

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

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

/**
 * OpenGL 数字: 【使用Opengl的大体步骤】
 * t 数字:  【使用纹理的大体步骤】
 * v 数字: 【使用VBO的大体步骤】
 * f 数字: 【使用FBO的大体步骤】
 * m 数字: 【使用正交投影矩阵大体步骤】
 */
public class OpenGLRender implements GLSurfaceView.Renderer {

    private Context mContext;
    //顶点坐标系(-1, -1) (1, -1)  (-1, 1)  (1, 1)
    private float[] vertexData = {
            -1f, -1f,
            1f, -1f,
            -1f, 1f,
            1f, 1f
    };

    //纹理坐标系
    private float[] fragmentData = {
//            0f, 0f,
//            1f, 0f,
//            0f, 1f,
//            1f, 1f
            0f, 1f,
            1f, 1f,
            0f, 0f,
            1f, 0f
    };
    private FloatBuffer vertexBuffer; //顶点buffer
    private FloatBuffer fragmentBuffer; //纹理buffer

    private int program; //渲染源程序
    private int vPosition; //顶点位置
    private int fPosition; //纹理位置
    private int textureId; //纹理的ID 【FBO会跟它进行绑定】
    private int sampler; //纹理采样

    private int vboId;// 保存VBO
    private int fboId;// 保存FBO

    private int uMatrix; //矩阵
    private float[] matrix = new float[16]; //正交投影使用


    private int imgTextureId; //纹理的ID,这个是我们要渲染的纹理【一张图片】
    private FboRender fboRender;

    OpenGLRender(Context context) {
        this.mContext = context;
        fboRender = new FboRender(context);
        //为顶点坐标 分配本地内存地址
        //为什么要分配本地内存地址呢? 因为Opengl取顶点的时候,每一次都到内存中去取值,
        // 所以这个内存在运行过程中是不允许被java虚拟机GC回收的,我们就要把它搞成本地(底层)不受虚拟机控制的这种顶点
        vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) //分配内存大小(分配了32个字节长度)
                .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(GL10 gl10, EGLConfig eglConfig) {
        fboRender.onFboCreate(); //离屏渲染的Render(自定义的)
        //以字符串的形式 加载出 顶点shader 和 纹理shader
        String vertexSource = ShaderUtil.getRawResource(mContext, R.raw.vertex_shader_matrix);
        String fragmentSource = ShaderUtil.getRawResource(mContext, R.raw.fragment_shader);
        //创建源程序
        program = ShaderUtil.createProgram(vertexSource, fragmentSource);

        // OpenGL 7.得到着色器中的属性  todo:我们就从源程序中获取他的属性了
        vPosition = GLES20.glGetAttribLocation(program, "av_Position"); //顶点的向量坐标 todo:一定要跟vertex_shader.glsl中的变量对应上
        fPosition = GLES20.glGetAttribLocation(program, "af_Position"); //纹理的向量坐标
        sampler = GLES20.glGetUniformLocation(program, "sTexture"); // uniform sampler2D sTexture
        uMatrix = GLES20.glGetUniformLocation(program, "u_Matrix"); // uniform mat4 u_Matrix

        //v1. 创建VBO 【Vertex Buffer Object 顶点缓存类】
        int[] vbos = new int[1];
        GLES20.glGenBuffers(1, vbos, 0);
        //v2. 绑定VBO
        vboId = vbos[0];
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);
        //v3. 分配VBO需要的缓存大小:顶点坐标数据长度 + 纹理坐标数据长度【data=null 表示只分配了空间,并没有放入数据】
        GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
        //v4. 为VBO设置坐标点数据的值
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);//坐标顶点赋值
        GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);//纹理坐标点赋值
        //v5. 解绑VBO
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        //TODO: >>>>>> 到这里 就把坐标系中的本地内存数据 缓存到了  显存中【GPU中】 <<<<<<<


        //t1. 创建纹理
        int[] textureIds = new int[1];
        GLES20.glGenBuffers(1, textureIds, 0);
        //t2. 绑定纹理
        textureId = textureIds[0];
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        //t3. 激活纹理 【激活纹理texture0】
        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
        GLES20.glUniform1i(sampler, 0);
        //t4 .设置纹理 环绕和过滤方式
        //todo: 环绕(超出纹理坐标范围):(s==x  t==y GL_REPEAT重复)
        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);
        //todo: 过滤(纹理像素映射到坐标点):(缩小,放大:GL_LINEAR线性)
        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);
        //使用GLUtils.texImage2D 把bitmap这张图片映射到Opengl上
        //Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), R.drawable.androids);
        //GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);
        //bitmap.recycle();

        //f1. 创建FBO 【Frame Buffer Object 纹理缓存类】
        int[] fbos = new int[1];
        GLES20.glGenBuffers(1, fbos, 0);
        //f2. 绑定FBO
        fboId = fbos[0];
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);
        //f3. 为FBO分配需要的内存大小 (1080 1920 是我手机的宽高,没有ActionBar)
        GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, 1080, 1920, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
        //f4.把纹理绑定到FBO上
        GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, textureId, 0);
        //f5. 检查FBO绑定是否成功
        if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
            Log.e("AnRender", "FBO 绑定 纹理失败! ");
        } else {
            Log.i("AnRender", "FBO 绑定 纹理成功! ");
        }

        //t5. 解绑纹理
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        //f6. 解绑FBO
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);

        //这里我们创建一个要绘制的纹理ID
        imgTextureId = createImgTexrute(R.drawable.androids);
    }

    @Override
    public void onSurfaceChanged(GL10 gl10, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        Log.e("AnRender", "width: " + width + "     height:" + height);
        fboRender.onFboChange(width, height);
        if (width > height) { //横屏
            // m1. 计算横屏坐标占有比例 (702 , 526 是该图片的高 宽)
            Matrix.orthoM(matrix, 0, -width / ((height / 702f * 526f)), width / ((height / 702f * 526f)), -1f, 1f, -1f, 1f);
        } else { //竖屏
            // m2. 计算竖屏坐标占有比例
            Matrix.orthoM(matrix, 0, -1, 1, -height / ((width / 526f * 702f)), height / ((width / 526f * 702f)), -1f, 1f);
        }
    }

    @Override
    public void onDrawFrame(GL10 gl10) {
        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT); //清屏
        GLES20.glClearColor(1.0f, 0.0f, 0.0f, 1.0f);//红色清屏【这样背景就是红色的了】
        //OpenGL 8.使用源程序
        GLES20.glUseProgram(program);
        // m3.使用正交投影
        GLES20.glUniformMatrix4fv(uMatrix, 1, false, matrix, 0);
        //OpenGL 9.绑定纹理, 绑定VBO, 绑定FBO
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId);//绑定FBO
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureId); //GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vboId);//绑定VBO


        //OpenGL 10.使顶点属性数组有效, 使纹理属性数组有效
        GLES20.glEnableVertexAttribArray(vPosition);
        GLES20.glEnableVertexAttribArray(fPosition);

        // OpenGL 11.为顶点坐标属性赋值 todo;就是把 vertexBuffer的数据给到  vPosition
        //GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);//vPosition中的数据就是本地内存中的数据了
        //为片元坐标属性赋值  todo:  把textureBuffer的数据给到 fPosition
        //GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, fragmentBuffer);//fPosition中的数据就是本地内存中的数据了
        //todo; 到这里 vertex_shader.glsl中的  "av_Position", "af_Position"就有数据了
        // OpenGL[11步]. 在这一步,我们就可以使用生成好的VBO 【显存中的缓存值了】
        GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0); //vPosition中的数据就是显存中的数据了
        GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);//fPosition中的数据就是显存中的数据了

        // OpenGL 12.绘制图形
        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
        // OpenGL 13.解绑
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);//这里 textre=0 解绑纹理
        GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);//这里 buffer=0 解绑VBO
        GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);//这里 buffer=0 解绑FBO

        //去离屏渲染
        fboRender.onFboDraw(textureId);
    }


    private int createImgTexrute(int src) {
        int[] imgTextureIds = new int[1];
        GLES20.glGenTextures(1, imgTextureIds, 0);
        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureIds[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);

        //使用GLUtils.texImage2D 把bitmap这张图片映射到Opengl上
        Bitmap bitmap = BitmapFactory.decodeResource(mContext.getResources(), src);
        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bitmap, 0);

        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
        return imgTextureIds[0];
    }
}

代码地址: opengl包下
https://github.com/YuLingRui/AntonyCFAV

你可能感兴趣的:(初识:顶点缓冲对象VBO,帧缓冲对象FBO,正交投影【矩阵】在Android中的使用)