一. 什么是VBO裁厅,它是用來做什么的。
VBO: Vertex Buffer Object 【頂點緩沖類】
為什么要用VBO?
不使用VBO時侨艾,我們每次繪制( glDrawArrays )圖形時都是從本地內(nèi)存處獲取頂點數(shù)據(jù)然后傳輸給OpenGL來繪制执虹,這樣就會頻繁的操作CPU->GPU增大開銷,從而降低效率唠梨。
使用VBO袋励,我們就能把頂點數(shù)據(jù)緩存到GPU開辟的一段內(nèi)存中,然后使用時不必再從本地獲取当叭,而是直接從顯存中獲取茬故,這樣就能提升繪制的效率。在Android中創(chuàng)建VBO
(a). 創(chuàng)建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設(shè)置頂點數(shù)據(jù)的值:
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). 設(shè)置頂點數(shù)據(jù):
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?
當(dāng)我們需要對紋理進行多次渲染采樣時醉箕,而這些渲染采樣是不需要展示給用戶看的钾腺,所以我們就可以用一個單獨的緩沖對象(離屏渲染)來存儲我們的這幾次渲染采樣的結(jié)果徙垫,等處理完后才顯示到窗口上。優(yōu)勢
提高渲染效率放棒,避免閃屏姻报,可以很方便的實現(xiàn)紋理共享等。渲染方式
渲染到緩沖區(qū)(Render)- 深度測試和模板測試
渲染到紋理(Texture)- 圖像渲染-
FBO大體工作過程
FBO工作過程.png -
紋理坐標(biāo)系與FBO坐標(biāo)系比較
坐標(biāo)系比較.jpg 在Android中創(chuàng)建FBO
(a).創(chuàng)建FBO
GLES20.glGenBuffers(1, fbos, 0);
(b).綁定FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbos[0]);
(c).設(shè)置FBO分配內(nèi)存大小
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的紋理繪制渲染出來
三. 正交投影,它是用來做什么的
上面我們得到的( 溉奕?)是不在歸一化坐標(biāo)范圍內(nèi)的,為了能使OpenGL正確的渲染忍啤,我們就需要把(加勤?)以及其他邊統(tǒng)一轉(zhuǎn)換到歸一化坐標(biāo)內(nèi),這個操作就是正交投影同波。
使用正交投影鳄梅,不管物體多遠多近,物體看起來總是形狀未檩、大小相同的戴尸。
1.Android中使用正交投影矩陣
在OpenGL中就需要用到矩形來改變頂點坐標(biāo)的范圍,最后再歸一化就可以了冤狡。
(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)、然后根據(jù)圖形寬高和屏幕寬高計算(悲雳?)的長度
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 數(shù)字: 【使用Opengl的大體步驟】
* t 數(shù)字: 【使用紋理的大體步驟】
* v 數(shù)字: 【使用VBO的大體步驟】
* f 數(shù)字: 【使用FBO的大體步驟】
* m 數(shù)字: 【使用正交投影矩陣大體步驟】
*/
public class OpenGLRender implements GLSurfaceView.Renderer {
private Context mContext;
//頂點坐標(biāo)系(-1, -1) (1, -1) (-1, 1) (1, 1)
private float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
//紋理坐標(biāo)系
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);
//為頂點坐標(biāo) 分配本地內(nèi)存地址
//為什么要分配本地內(nèi)存地址呢合瓢? 因為Opengl取頂點的時候坦胶,每一次都到內(nèi)存中去取值,
// 所以這個內(nèi)存在運行過程中是不允許被java虛擬機GC回收的晴楔,我們就要把它搞成本地(底層)不受虛擬機控制的這種頂點
vertexBuffer = ByteBuffer.allocateDirect(vertexData.length * 4) //分配內(nèi)存大卸傥(分配了32個字節(jié)長度)
.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);
//創(chuàng)建源程序
program = ShaderUtil.createProgram(vertexSource, fragmentSource);
// OpenGL 7.得到著色器中的屬性 todo:我們就從源程序中獲取他的屬性了
vPosition = GLES20.glGetAttribLocation(program, "av_Position"); //頂點的向量坐標(biāo) todo:一定要跟vertex_shader.glsl中的變量對應(yīng)上
fPosition = GLES20.glGetAttribLocation(program, "af_Position"); //紋理的向量坐標(biāo)
sampler = GLES20.glGetUniformLocation(program, "sTexture"); // uniform sampler2D sTexture
uMatrix = GLES20.glGetUniformLocation(program, "u_Matrix"); // uniform mat4 u_Matrix
//v1. 創(chuàng)建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需要的緩存大小:頂點坐標(biāo)數(shù)據(jù)長度 + 紋理坐標(biāo)數(shù)據(jù)長度【data=null 表示只分配了空間,并沒有放入數(shù)據(jù)】
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
//v4. 為VBO設(shè)置坐標(biāo)點數(shù)據(jù)的值
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);//坐標(biāo)頂點賦值
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);//紋理坐標(biāo)點賦值
//v5. 解綁VBO
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
//TODO: >>>>>> 到這里 就把坐標(biāo)系中的本地內(nèi)存數(shù)據(jù) 緩存到了 顯存中【GPU中】 <<<<<<<
//t1. 創(chuàng)建紋理
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 .設(shè)置紋理 環(huán)繞和過濾方式
//todo: 環(huán)繞(超出紋理坐標(biāo)范圍):(s==x t==y GL_REPEAT重復(fù))
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: 過濾(紋理像素映射到坐標(biāo)點):(縮小税弃,放大: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. 創(chuàng)建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分配需要的內(nèi)存大小 (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);
//這里我們創(chuàng)建一個要繪制的紋理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. 計算橫屏坐標(biāo)占有比例 (702 , 526 是該圖片的高 寬)
Matrix.orthoM(matrix, 0, -width / ((height / 702f * 526f)), width / ((height / 702f * 526f)), -1f, 1f, -1f, 1f);
} else { //豎屏
// m2. 計算豎屏坐標(biāo)占有比例
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.使頂點屬性數(shù)組有效, 使紋理屬性數(shù)組有效
GLES20.glEnableVertexAttribArray(vPosition);
GLES20.glEnableVertexAttribArray(fPosition);
// OpenGL 11.為頂點坐標(biāo)屬性賦值 todo;就是把 vertexBuffer的數(shù)據(jù)給到 vPosition
//GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, vertexBuffer);//vPosition中的數(shù)據(jù)就是本地內(nèi)存中的數(shù)據(jù)了
//為片元坐標(biāo)屬性賦值 todo: 把textureBuffer的數(shù)據(jù)給到 fPosition
//GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, fragmentBuffer);//fPosition中的數(shù)據(jù)就是本地內(nèi)存中的數(shù)據(jù)了
//todo导匣; 到這里 vertex_shader.glsl中的 "av_Position"才菠, "af_Position"就有數(shù)據(jù)了
// OpenGL[11步]. 在這一步,我們就可以使用生成好的VBO 【顯存中的緩存值了】
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0); //vPosition中的數(shù)據(jù)就是顯存中的數(shù)據(jù)了
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);//fPosition中的數(shù)據(jù)就是顯存中的數(shù)據(jù)了
// 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