在使用OpenGL ES進行圖形影像開發時,我們常使用
GL_TEXTURE_2D
紋理型別,它提供了對標準2D影像的處理能力。這種紋理型別適用於大多數場景,可以用於展示靜態貼圖、渲染2D圖形和進行影像處理等操作。
另外,有時我們需要從Camera或外部影片源
讀取資料幀並進行處理。這時,我們會使用GL_TEXTURE_EXTERNAL_OES
紋理型別。其專門用於對外部影像或實時影片流進行處理,可以直接從 BufferQueue 中接收的資料渲染紋理多邊形
,從而提供更高效的影片處理和渲染效能。
在實際應用中,我們通常將GL_TEXTURE_2D
和GL_TEXTURE_EXTERNAL_OES
這兩種紋理型別分開使用,並且它們互不干擾。實際上,這種情況佔據了80%的使用場景。我們可以根據具體需求選擇合適的紋理型別進行處理和渲染。
然而,有時候我們也會遇到一些特殊情況,需要將GL_TEXTURE_EXTERNAL_OES
紋理轉化為GL_TEXTURE_2D
紋理進行影片處理或計算。這種情況可能出現在需要對影片資料進行特殊的影像處理或者與GL_TEXTURE_2D
紋理型別的其他渲染操作進行互動時。
當以上情況出現時,我們該如何處理呢?難道是直接將GL_TEXTURE_EXTERNAL_OES紋理賦值給GL_TEXTURE_2D紋理使用(經過實驗這種方式是不可用的)?
這裡對此情況,先給出解決方案,一般我們可以透過一些技術手段,如離屏渲染或FrameBuffer幀緩衝區物件
,將GL_TEXTURE_EXTERNAL_OES
紋理轉換為GL_TEXTURE_2D
紋理,並進行後續的處理和計算。
而此篇文章主要記錄,我是如何透過FrameBuffer幀緩衝區物件
,將GL_TEXTURE_EXTERNAL_OES紋理資料轉化為GL_TEXTURE_2D紋理資料的!
- 首先 回顧一下GL_TEXTURE_2D紋理與GL_TEXTURE_EXTERNAL_OES紋理;
GL_TEXTURE_EXTERNAL_OES紋理資料
透過FrameBuffer
轉化為GL_TEXTURE_2D紋理資料
;
一、TEXTURE_2D
與 EXTERNAL_OES
在正式研究 “GL_TEXTURE_EXTERNAL_OES紋理資料轉化為GL_TEXTURE_2D紋理資料” 之前,先要搞清楚:
什麼是GL_TEXTURE_2D紋理?
什麼又是GL_TEXTURE_EXTERNAL_OES紋理?
GL_TEXTURE_2D紋理與GL_TEXTURE_EXTERNAL_OES紋理有什麼樣的區別?
1.1 GL_TEXTURE_2D紋理
GL_TEXTURE_2D 提供了對標準2D影像的處理能力,可以儲存靜態的貼圖
、影像
或者幀緩衝區的渲染結果
。
其使用二維的紋理座標系,透過將紋理座標對映到紋理影像上的對應位置
,可以實現紋理貼圖、紋理過濾、紋理環繞等操作
。
GL_TEXTURE_2D
紋理的特點
包括:
- 使用
二維紋理座標系
進行操作; - 使用
glTexImage2D
函式載入紋理
資料; - 透過紋理過濾和紋理環繞等方式進行紋理的取樣和處理;
GL_TEXTURE_2D
紋理:建立、繫結、取樣、載入紋理
影像
public static int createDrawableTexture2D(Context context, int drawableId) {
// 生成紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 紋理取樣方式
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// texImage2D載入影像資料
InputStream is = context.getResources().openRawResource(drawableId);
Bitmap bitmapTmp;
try {
bitmapTmp = BitmapFactory.decodeStream(is);
} finally {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
GLUtils.texImage2D(GLES30.GL_TEXTURE_2D,0, GLUtils.getInternalFormat(bitmapTmp), bitmapTmp, GLUtils.getType(bitmapTmp), 0 );
bitmapTmp.recycle();
return textures[0];
}
GL_TEXTURE_2D
紋理:Shader處理階段
(片元著色器)
precision mediump float;
varying vec2 v_texture_coord;
uniform sampler2D MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_2D
紋理:紋理渲染
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
1.2 GL_TEXTURE_EXTERNAL_OES紋理型別
根據AOSP: SurfaceTexture 文件描述,GL_TEXTURE_EXTERNAL_OES
是一種特殊的紋理型別,主要用於處理外部影像或影片資料
,如從攝像頭捕捉的實時影像和外部影片流。
GL_TEXTURE_EXTERNAL_OES 相對於 GL_TEXTURE_2D 最大的特點
就是 GL_TEXTURE_EXTERNAL_OES可直接從 BufferQueue 中接收的資料渲染紋理多邊形
。
GL_TEXTURE_EXTERNAL_OES紋理型別的特點
包括:
- 需採用
特殊的取樣器型別
和紋理著色器擴充套件
。 - 使用二維紋理座標系進行操作,與GL_TEXTURE_2D相似。
- 專門用於
處理外部影像或影片資料
,可直接從 BufferQueue 中接收的資料渲染紋理多邊形
,從而提供更高效的影片處理和渲染效能。
對於此,官方文件中提供了一個 Grafika 的連續拍攝案例工程,並給出瞭如下參考流程圖。
透過閱讀 Grafika 的連續拍攝案例,我們得知:
- 首先,需建立一個
OES紋理ID
,用於接收Camera影像資料
;
// GL_TEXTURE_EXTERNAL_OES: 紋理建立、繫結、取樣
public static int createTextureOES() {
// 建立OES紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
TextureUtil.checkGlError("glGenTextures");
// 繫結OES紋理ID
int texId = textures[0];
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
TextureUtil.checkGlError("glBindTexture " + texId);
// OES紋理取樣
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
TextureUtil.checkGlError("glTexParameter");
return texId;
}
- 完成
OES紋理ID
建立後,透過oesTextureId
建立一個影像消費者SurfaceTexture
,將SurfaceTexture設定為預覽的PreviewTexture;
// 傳入一個OES紋理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 將 SurfaceTexture 設定為預覽的 PreviewTexture
Camera.setPreviewTexture(mSurfaceTexture);
- 或者透過
SurfaceTexture
建立Surface
,將Surface物件傳遞給MediaPlayer
或MediaCodec
進行影片幀資料獲取;
// 傳入一個OES紋理ID
SurfaceTexture mSurfaceTexture = new SurfaceTexture(oesTextureId);
// 建立 Surface
Surface mSurface = new Surface(mSurfaceTexture);
// 將 Surface 設定給 MediaPlayer 外部影片播放器,獲取影片幀資料
MediaPlayer.setSurface(surface);
- 前文已經提到
GL_TEXTURE_EXTERNAL_OES
紋理型別 可直接從Surface
對應的BufferQueue
中獲取影片流資料; - 在獲取到影片幀資料後:
一方面,可透過OpenGL的渲染管線,將GL_TEXTURE_EXTERNAL_OES紋理渲染到GLSurfaceView上
,完成影像資料的預覽
;
另一方面,可將GL_TEXTURE_EXTERNAL_OES紋理,透過離屏渲染的形式,寫入到 MediaCodeC,硬編碼生成MP4影片。
// GL_TEXTURE_EXTERNAL_OES紋理:Shader處理階段(片元著色器)
#extension GL_OES_EGL_image_external : require
precision mediump float;
varying vec2 v_texture_coord;
uniform samplerExternalOES MAIN;
void main() {
vec4 color=texture2D(MAIN, v_texture_coord);
gl_FragColor=color;
}
GL_TEXTURE_EXTERNAL_OES
紋理:紋理渲染
// 紋理渲染階段:GL_TEXTURE_EXTERNAL_OES紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_EXTERNAL_OES, texId);
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
1.3 關於兩者的區別 我的個人理解
關於兩者的區別,我的個人理解:
GL_TEXTURE_2D
紋理型別與GL_TEXTURE_EXTERNAL_OES
紋理型別,在資料來源
與紋理資料的儲存格式
上存在差異。
- 資料來源方面:
一個來源於glTexImage2D載入的二維影像資料
;
一個來源與影像消費者Surface對應的BufferQueue
; - 紋理儲存格式:
GL_TEXTURE_EXTERNAL_OES資料來源於外部影片源或Camera,其資料格式可能為YUV或RGB;
GL_TEXTURE_2D的資料格式則依賴於開發中setEGLConfigChooser(int redSize, int greenSize, int blueSize, int alphaSize, int depthSize, int stencilSize)
的配置,可能是RGBA8888,也可能是RGBA4444等等。
由於兩者資料來源和紋理儲存格式的差異,兩種紋理型別是不能直接進行轉化
的。
- 首先,其在
紋理取樣階段
、Shader處理階段
和紋理渲染階段
均不同程度的存在差異(這一點在上一節的兩者對比的程式碼舉例中可以證明)。 - 其次,如果需要在處理和計算階段將
GL_TEXTURE_EXTERNAL_OES
紋理轉換
為GL_TEXTURE_2D
紋理,通常需要使用離屏渲染或幀緩衝區物件
等技術手段。
二、EXTERNAL_OES
轉化為TEXTURE_2D
紋理資料
這裡直接介紹轉化過程
:
- 首先,需建立一個
OES紋理ID
(相關程式碼舉例在前文已經給出); - 完成
OES紋理ID
建立後,透過oesTextureId
建立一個影像消費者SurfaceTexture
(相關程式碼舉例在前文已經給出); - 透過
SurfaceTexture
建立Surface
,將Surface物件傳遞給MediaPlayer
,獲取Sdcard中對應路徑的影片幀資料獲取(相關程式碼舉例在前文已經給出); - 建立
FRAMEBUFFER
幀緩衝區,並繫結GL_TEXTURE_2D
空白紋理物件;
public static int createEmptyTexture2DBindFrameBuffer(int[] frameBuffer, int texPixWidth, int texPixHeight) {
// 建立紋理ID
int[] textures = new int[1];
GLES30.glGenTextures(1, textures, 0);
// 繫結GL_TEXTURE_2D紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, textures[0]);
// 紋理取樣
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MIN_FILTER, GLES30.GL_NEAREST);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_MAG_FILTER, GLES30.GL_LINEAR);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES30.GL_TEXTURE_2D, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
// 建立一個空的2D紋理物件,指定其基本引數,並繫結到對應的紋理ID上
GLES30.glTexImage2D(GLES30.GL_TEXTURE_2D, 0, GLES30.GL_RGBA, texPixWidth, texPixHeight,0, GLES30.GL_RGBA, GLES30.GL_UNSIGNED_BYTE, null);
// 取消繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, GLES30.GL_NONE);
/**
* 幀緩衝區
*/
// 建立幀緩衝區
GLES30.glGenFramebuffers(1, frameBuffer, 0);
// 將幀緩衝物件繫結到OpenGL ES上下文的幀緩衝目標上
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, frameBuffer[0]);
// 使用GLES30.GL_COLOR_ATTACHMENT0將紋理作為顏色附著點附加到幀緩衝物件上
GLES30.glFramebufferTexture2D(GLES30.GL_FRAMEBUFFER, GLES30.GL_COLOR_ATTACHMENT0, GLES30.GL_TEXTURE_2D, textures[0], 0);
// 取消繫結緩衝區
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
return textures[0];
}
- 將
GL_TEXTURE_EXTERNAL_OES
紋理渲染
到FRAMEBUFFER
幀緩衝區中;
// 啟用紋理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 將所需的紋理物件繫結到Shader中紋理單元0上
GLES30.glUniform1i(mOesTextureIdHandle, 0);
// 繫結紋理
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
// 繫結FRAMEBUFFER緩衝區
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, tex2DFrameBufferId);
// 繪製矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
// 取消FRAMEBUFFER的繫結
GLES30.glBindFramebuffer(GLES30.GL_FRAMEBUFFER, GLES30.GL_NONE);
- 最後,繪製
渲染GL_TEXTURE_2D紋理
,完成紋理影像的顯示。
// 啟用紋理
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
// 將所需的紋理物件繫結到Shader中紋理單元0上
GLES30.glUniform1i(mTex2DIdHandle, 0);
// 繫結紋理
GLES30.glBindTexture(GLES30.GL_TEXTURE_2D, tex2DId);
// 繪製矩形
GLES30.glDrawArrays(GLES30.GL_TRIANGLE_STRIP, 0, mVertexCount);
三、原始碼下載
ExternalOES紋理資料 轉換為 TEXTURE-2D紋理資料:
https://download.csdn.net/download/aiwusheng/88650498
參考
AOSP:SurfaceTexture
https://source.android.google.cn/docs/core/graphics/arch-st?hl=zh-c
Github:Google Grafika
https://github.com/google/grafika/blob/master/app/src/main/java/com/android/grafika/ContinuousCaptureActivity.java
OpenGL渲染管線:
https://xiaxl.blog.csdn.net/article/details/121467207
紋理ID 離屏渲染 寫入到Surface中:
https://xiaxl.blog.csdn.net/article/details/131682521
MediaCodeC與OpenGL硬編碼錄製mp4:
https://xiaxl.blog.csdn.net/article/details/72530314