
兩個簡單濾鏡切換
通過上兩個章節(jié)的實現,我們實現了相機預覽,因為是通過opengl實現,因此,可以通過改變不同的著色器程序,
實現不同的預覽效果,也就是不同的濾鏡效果
正常預覽的片元著色器程序:
uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;
void main()
{
#從對應紋理位置取顏色值
gl_FragColor = texture2D(inputImageTexture, textureCoordinate);
}
黑白濾鏡效果片元著色器程序:
uniform sampler2D inputImageTexture;
varying vec2 textureCoordinate;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
void main() {
//從對應紋理位置取顏色值
lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);
float luminance = dot(textureColor.rgb, W);
gl_FragColor = vec4(vec3(luminance), textureColor.w);
}
因此通過動態(tài)切換著色器程序,我們可以實現不同的濾鏡效果

預覽濾鏡切換核心代碼:
- 首先,基于基類JImageOESFilter,只需要替換成黑白片元著色器(gray_oes_fragment_shader)即可
public class JImageOESGrayFilter extends JImageOESFilter{
public JImageOESGrayFilter(Context context) {
super(context);
vertexShader = Utils.readShaderFromResource(context, R.raw.base_oes_vertex_shader);
fragmentShader = Utils.readShaderFromResource(context, R.raw.gray_oes_fragment_shader);
}
}
- 然后,在點擊事件中,改變Render中filter的對象賦值
findViewById(R.id.switch_filter).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
glSurfaceView.setFilter(new JImageOESGrayFilter(JCamera2OESActivity.this));
}
});
public void setFilter(JImageOESFilter filte){
render.setFilter(filte);
}
public void setFilter(final JImageOESFilter filter) {
runOnDraw(new Runnable() {
@Override
public void run() {
final JImageOESFilter oldFilter = mFilterEngine;
mFilterEngine = filter;
if (oldFilter != null) {
oldFilter.destroy();
}
JLog.d("jiadongfeng4","oldfilter:"+oldFilter+" newFilter:"+mFilterEngine);
mFilterEngine.ifNeedInit();
GLES20.glUseProgram(mFilterEngine.getProgram());
mFilterEngine.onOutputSizeChanged(outputWidth, outputHeight);
}
});
}
以上代碼,創(chuàng)建一個隊列任務,當Render的onDrawFrme被調用時,會poll 出并執(zhí)行這個任務,完成Opengel程序對象,也就是濾鏡對象的切換
@Override
public void onDrawFrame(GL10 gl) {
...
//執(zhí)行濾鏡切換任務
runAll(runOnDraw);
mFilterEngine.onDraw(transformMatrix, mOESTextureId,glCubeBuffer,glTextureBuffer);
}
以上兩個簡單濾鏡切換過程只需要,替換對應的濾鏡對象即可;是簡單的濾鏡A-濾鏡B切換的場景
代碼具體實現參考 QCCamera中的JCamera2OESActivity和JImageOESGrayFilter,JImageOESRender
FBO
以上單個濾鏡場景的效果,渲染操作等都是在默認的幀緩沖進行操作的,這個默認的幀緩沖是在我們創(chuàng)建Surface的時候自動創(chuàng)建和配置好的,這個OpenGL ES默認的幀緩沖是由窗口系統(tǒng)提供的,是默認顯示到屏幕上的,也就是GlSurfaceView中的
實際應用場景中,會將幾個濾鏡效果疊加起來,效果疊加后,然后將最終的結果顯示到屏幕上。疊加處理過程,是放到FrameBuffer中進行處理的,處理完成后,才顯示到屏幕上。
這個場景是需要解決的問題是:濾鏡A+B+C+D的場景,也就是濾鏡組的場景 A處理-->B處理-->C處理-->顯示到屏幕上
首先,定義和創(chuàng)建FBO
- JImageOESFilter濾鏡類
//FBO對象引用
private int[] frameBuffers = new int[1];
//與FBO對象綁定的紋理id
public int[] frameBufferTextures = new int[1];
public void onOutputSizeChanged(final int width, final int height) {
outputWidth = width;
outputHeight = height;
//當surface有變化時,重新生成FBO
if (frameBuffers != null) {
destroyFramebuffers();
}
OpenGlUtils.createFrameBuffer(frameBuffers, frameBufferTextures, width, height);
}
- OpenGlUtils
public static void createFrameBuffer(int[] frameBuffer, int[] frameBufferTexture,
int width, int height) {
//產生FBO ID
GLES20.glGenFramebuffers(frameBuffer.length, frameBuffer, 0);
//產生紋理ID
GLES20.glGenTextures(frameBufferTexture.length, frameBufferTexture, 0);
for (int i = 0; i < frameBufferTexture.length; i++) {
//綁定紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTexture[i]);
//加載圖像數據, 并上傳紋理
//pixels 可能是一個空指針。在這種情況下,會分配紋理內存以適應寬度width和高度的紋理height
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
//綁定
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[i]);
//將texture和level指定的紋理圖像附加為當前綁定的幀緩沖區(qū)對象的邏輯緩沖區(qū)之一
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, frameBufferTexture[i], 0);
//解綁紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
//解綁FBO
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
}
checkGlError("createFrameBuffer");
}
GLES20.glGenFramebuffers(frameBuffer.length, frameBuffer, 0):
第一個是要創(chuàng)建的幀緩存的數目;
第二個是指向存儲一個或者多個ID的變量或數組的指針,它返回未使用的FBO的ID,ID為0表示默認幀緩存,即window系統(tǒng)提供的幀緩存
第三個參數表示framebuffer數組賦值時的偏移量,為0表示,即為frameBuffer分配frameBuffer.length個長度
GLES20.glGenTextures(frameBufferTexture.length, frameBufferTexture, 0):
同上,產生n個紋理id
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, frameBufferTexture[i]):
綁定frameBufferTexture[i]紋理,后續(xù)所有操作都針對此紋理生效,直到調用 * GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0)*解綁
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, GLES20.GL_RGBA, width, height, 0, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE, null);
設置當前對應紋理id的紋理寬高,內容格式等,因為當前還沒有內容,因此最后一個參數為null
glTexParameterf:
設置紋理的采樣參數
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffer[i]):
一旦一個FBO被創(chuàng)建,在使用它之前必須綁定;一旦FBO被綁定,之后的所有的OpenGL操作都會對當前所綁定的FBO造成影響;
ID號為0表示缺省幀緩存,即默認的window提供的幀緩存。因此,在glBindFramebufferEXT()中將ID號設置為0可以解綁定當前FBO;
GLES20.glFramebufferTexture2D(GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, frameBufferTexture[i], 0):
第一個參數一定是GL_FRAMEBUFFER
第二個參數是關聯(lián)紋理圖像的關聯(lián)點
第三個參數textureTarget在圖像情況下是GL_TEXTURE_2D
第四個參數是紋理對象的ID號
最后一個參數是要被關聯(lián)的紋理的mipmap等級
通過以上操作,將把一幅紋理圖像frameBufferTexture[i]關聯(lián)到一個FBO frameBuffer[i]
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0):
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0):
解綁操作,表示接下來的opengl操作,對frameBufferTexture[i]和frameBuffer[i]再無影響
注意:跟FBO綁定的紋理ID類型為GL_TEXTURE_2D,不是GL_TEXTURE_EXTERNAL_OES類型,因此經過FBO緩存處理后的數據紋理id:frameBufferTexture,必須由OPENGL當成GL_TEXTURE_2D類型進行處理
使用FBO
JImageOESFilter
public int onDrawFBO(float[] transformMatrix, int textureId, FloatBuffer cubeBuffer, FloatBuffer textureBuffer) {
//
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, frameBuffers[0]);
//根據紋理textureId類型的不同,執(zhí)行不同的邏輯
onDraw(transformMatrix, textureId, cubeBuffer, textureBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
return frameBufferTextures[0];
}
GLES20.glBindFramebuffer:
綁定FBO,后續(xù)opengl所有的數據操作都在改FBO中進行
onDraw:
這里使用的紋理id類型為GL_TEXTURE_EXTERNAL_OES的例子,GL_TEXTURE_2D的處理方式大同小異,在
Camera2 教程 5:兩種預覽方式比較章節(jié),我們已經講解了兩者的差異
glBindFramebuffer(GL_FRAMEBUFFER, 0):
解綁FBO,后續(xù)opengl的操作,就跟這個沒有關系了
最后返回frameBufferTextures[0],給下一個流程或者濾鏡處理
用一張圖表示下FBO過程:

注意以上流程中,FBO濾鏡處理后的數據是存儲在FBO中的,是顯示不到屏幕上的;最后一個FBO數據處理后將對應的紋理id放到Opengl中渲染才能顯示到默認屏幕上
代碼工程
完整的FBO例子請參考以下工程的JCamera2OESFboActivity