最通俗的安卓OpenGL教學07——使用FBO
阿新 • • 發佈:2020-12-22
技術標籤:OpenGL ES
當需要對紋理進行多次渲染時,而這些渲染取樣是不需要展示給使用者看的,就可以用一個單獨的緩衝物件(離屏渲染)
來儲存多次渲染取樣的結果,等處理完後再顯示到視窗上。
FBO的建立有以下步驟:
- 建立FBO
- 繫結FBO
- 設定FBO分配記憶體大小
- 把紋理繫結到FBO
- 檢查FBO繫結是否成功
- 解綁FBO
FBO的使用有以下步驟:
- 繫結FBO
- 獲取需要繪製的圖片紋理,然後繪製渲染
- 解綁FBO
- 把繫結到FBO的紋理繪製渲染出來
1. FBO的建立
在onSurfaceCreated時建立:
//建立 fbo
int[] fbo_s = new int[1];
GLES20. glGenBuffers(1, fbo_s, 0);
fbo = fbo_s[0];
//使 fbo 成為 fbo物件 GL_FRAMEBUFFER
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
//建立 1個 fbo 紋理 fboTextureID
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);//第三個引數是指從哪兒開始取
fboTextureID = textureIds[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureID);// 在沒設定點的情況下預設是繫結 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);
2.使用FBO
在onDrawFrame時使用:
//繫結 fbo 開始使用 FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
//繫結 imgTextureId 開始使用紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureId);
//繪製
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
//解綁 FBO紋理
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
yFboRender.onDraw(fboTextureID);
3. 完整程式碼
package com.york.media.opengl.demo.fbo;
import android.content.Context;
import android.opengl.GLES20;
import com.york.media.opengl.R;
import com.york.media.opengl.egl.TextureUtils;
import com.york.media.opengl.egl.YGLSurfaceView;
import com.york.media.opengl.egl.YShaderUtil;
import com.york.media.opengl.utils.LogUtil;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
/**
* author : York
* date : 2020/12/20 19:42
* desc : FBO 幀緩衝物件
* <p>
* 當需要對紋理進行多次渲染時,而這些渲染取樣是不需要展示給使用者看的,就可以用一個單獨的緩衝物件(離屏渲染)
* 來儲存多次渲染取樣的結果,等處理完後再顯示到視窗上。
*/
public class YUsedFboRender 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 fboTextureID;
private int vbo;
private int fbo;
private int imgTextureId;
private int fboWidth;
private int fboHeight;
private final YFboRender yFboRender;
//頂點座標
float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
//紋理座標
float[] fragmentData = {
0f, 1f,
1f, 1f,
0f, 0f,
1f, 0f
};
public YUsedFboRender(Context context, int width, int height) {
this.mContext = context;
fboWidth = width;
fboHeight = height;
yFboRender = new YFboRender(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() {
yFboRender.onCreate();
//載入頂點著色器 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_s = new int[1];
GLES20.glGenBuffers(1, vbo_s, 0);
vbo = vbo_s[0];
//繫結 VBO
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
//分配 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);
//建立 fbo
int[] fbo_s = new int[1];
GLES20.glGenBuffers(1, fbo_s, 0);
fbo = fbo_s[0];
//使 fbo 成為 fbo物件 GL_FRAMEBUFFER
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
//建立 1個 fbo 紋理 fboTextureID
int[] textureIds = new int[1];
GLES20.glGenTextures(1, textureIds, 0);//第三個引數是指從哪兒開始取
fboTextureID = textureIds[0];
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, fboTextureID);// 在沒設定點的情況下預設是繫結 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);
LogUtil.d("fbo_Width=" + fboWidth + ",fboHeight=" + fboHeight);
//分配FBO記憶體大小
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, fboWidth, fboHeight, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
//把 2D紋理 fboTextureID 繫結到 FBO 物件
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, fboTextureID, 0);
//檢查FBO繫結是否成功
if (GLES20.glCheckFramebufferStatus(GLES20.GL_FRAMEBUFFER) != GLES20.GL_FRAMEBUFFER_COMPLETE) {
LogUtil.e("fbo bind error !");
}
//繫結紋理到 fbo成功後 退出紋理繫結,進入下一環節
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
imgTextureId = TextureUtils.createImageTexture(mContext, R.drawable.nobb);
}
@Override
public void onSurfaceChanged(int width, int height) {
//設定視窗大小
GLES20.glViewport(0, 0, width, height);
fboWidth = width;
fboHeight = height;
yFboRender.onChange(fboWidth, fboHeight);
}
@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, vbo);
//使能頂點屬性陣列,使之有效
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);
//繫結 fbo 開始使用 FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fbo);
//繫結 imgTextureId 開始使用紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, imgTextureId);
//繪製
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
//解綁 FBO紋理
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
yFboRender.onDraw(fboTextureID);
}
}
package com.york.media.opengl.demo.fbo;
import android.content.Context;
import android.opengl.GLES20;
import com.york.media.opengl.R;
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 20:29
* desc :
*/
public class YFboRender {
private final Context context;
private final FloatBuffer vertexBuffer;
private final FloatBuffer fragmentBuffer;
private int vPosition;
private int fPosition;
private int program;
private int vbo;
private final float[] vertexData = {
-1f, -1f,
1f, -1f,
-1f, 1f,
1f, 1f
};
//因為 fbo的片元座標系與安卓的不一樣,所以看上去是倒的
private final float[] fragmentData = {
0f, 0f,
1f, 0f,
0f, 1f,
1f, 1f
};
public YFboRender(Context context) {
this.context = 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);
}
public void onCreate() {
String vertexSource = YShaderUtil.getRawResource(context, R.raw.screen_vert);
String fragmentSource = YShaderUtil.getRawResource(context, R.raw.screen_frag);
program = YShaderUtil.createProgram(vertexSource, fragmentSource);
vPosition = GLES20.glGetAttribLocation(program, "vPosition");
fPosition = GLES20.glGetAttribLocation(program, "fPosition");
int[] vbo_s = new int[1];
GLES20.glGenBuffers(1, vbo_s, 0);
vbo = vbo_s[0];
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
GLES20.glBufferData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4 + fragmentData.length * 4, null, GLES20.GL_STATIC_DRAW);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, 0, vertexData.length * 4, vertexBuffer);
GLES20.glBufferSubData(GLES20.GL_ARRAY_BUFFER, vertexData.length * 4, fragmentData.length * 4, fragmentBuffer);
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
}
public void onChange(int width, int height) {
GLES20.glViewport(0, 0, width, height);
}
public void onDraw(int textureId) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
GLES20.glClearColor(0f, 1f, 0f, 1f);
GLES20.glUseProgram(program);
//使用 vbo
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, vbo);
GLES20.glEnableVertexAttribArray(vPosition);
GLES20.glVertexAttribPointer(vPosition, 2, GLES20.GL_FLOAT, false, 8, 0);
GLES20.glEnableVertexAttribArray(fPosition);
GLES20.glVertexAttribPointer(fPosition, 2, GLES20.GL_FLOAT, false, 8, vertexData.length * 4);
//解綁vbo,退出使用vbo 進入下一環節,繪製紋理
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
//繫結紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
//繪製紋理
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
//解綁紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
}
}
效果依舊槓槓滴: