一. 什么是VBO,它是用来做什么的。
VBO: Vertex Buffer Object 【顶点缓冲类】
为什么要用VBO?
不使用VBO时,我们每次绘制( glDrawArrays )图形时都是从本地内存处获取顶点数据然后传输给OpenGL来绘制,这样就会频繁的操作CPU->GPU增大开销,从而降低效率。
使用VBO,我们就能把顶点数据缓存到GPU开辟的一段内存中,然后使用时不必再从本地获取,而是直接从显存中获取,这样就能提升绘制的效率。在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);
- 在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,它是用来做什么的。
FBO: Frame Buffer object 【帧缓冲对象】
为什么要用FBO?
当我们需要对纹理进行多次渲染采样时,而这些渲染采样是不需要展示给用户看的,所以我们就可以用一个单独的缓冲对象(离屏渲染)来存储我们的这几次渲染采样的结果,等处理完后才显示到窗口上。优势
提高渲染效率,避免闪屏,可以很方便的实现纹理共享等。渲染方式
渲染到缓冲区(Render)- 深度测试和模板测试
渲染到纹理(Texture)- 图像渲染-
FBO大体工作过程
FBO工作过程.png -
纹理坐标系与FBO坐标系比较
坐标系比较.jpg 在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);
- 在Android中使用FBO
(1)、绑定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]);
(2)、获取需要绘制的图片纹理,然后绘制渲染
(3)、解绑FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
(4)、再把绑定到FBO的纹理绘制渲染出来
三. 正交投影,它是用来做什么的
上面我们得到的( ?)是不在归一化坐标范围内的,为了能使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