android平臺下OpenGL ES 3.0使用GLSurfaceView對相機Camera預覽實時處理
OpenGL ES 3.0學習實踐
- android平臺下OpenGL ES 3.0從零開始
- android平臺下OpenGL ES 3.0繪製純色背景
- android平臺下OpenGL ES 3.0繪製圓點、直線和三角形
- android平臺下OpenGL ES 3.0繪製彩色三角形
- android平臺下OpenGL ES 3.0從矩形中看矩陣和正交投影
- android平臺下OpenGL ES 3.0著色語言基礎知識(上)
- android平臺下OpenGL ES 3.0著色語言基礎知識(下)
- android平臺下OpenGL ES 3.0例項詳解頂點屬性、頂點陣列
- android平臺下OpenGL ES 3.0例項詳解頂點緩衝區物件(VBO)和頂點陣列物件(VAO)
- android平臺下OpenGL ES 3.0繪製立方體的幾種方式
- android平臺下OpenGL ES 3.0實現2D紋理貼圖顯示bitmap
- android平臺下OpenGL ES 3.0基於GLSurfaceView對相機Camera預覽實時處理
相機預覽資料格式
android相機
輸出的原始資料格式一般都是NV21(實際上就是YUV420SP的格式)
或者NV12(實際上就是YUV420P的格式)
,筆者的小米MIX 2S
的預設輸出格式就是NV21
的,關於格式的問題,後續部落格再詳細說明。
前面的部落格以及說明了如何通過OpenGL ES
來渲染影象,一般YUV格式
的資料是無法直接用OpenGL ES
來渲染的,而在OpenGL中使用的絕大部分紋理ID都是RGBA的格式,在OpenGL ES 3.0
的擴充套件#extension GL_OES_EGL_image_external_essl3
定義了一個紋理的擴充套件型別,即GL_TEXTURE_EXTERNAL_OES
,否則整個轉換過程將會非常複雜。同時這種紋理目標對紋理的使用方式也會有一些限制,紋理繫結需要繫結到型別GL_TEXTURE_EXTERNAL_OES
上,而不是型別GL_TEXTURE_2D上,對紋理設定引數也要使用GL_TEXTURE_EXTERNAL_OES
int[] tex = new int[1];
//建立一個紋理
GLES30.glGenTextures(1, tex, 0);
//繫結到外部紋理上
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
//設定紋理過濾引數
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.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
在實際的渲染過程中繫結紋理的程式碼如下:
GLES30.glActiveTexture(GL_TEXTURE0);
GLES30.glBindTexture(GL_TEXTURE_EXTERNAL_OES, textureId);
GLES30.glUniform1i(uniformSamplers, 0);
在OpenGL ES的頂點著色器
中,任何需要從紋理中取樣的OpenGL ES的頂點著色器
都需要宣告其對此擴充套件的使用,使用指令如下:
//這行是OpenGL ES 2.0中的宣告
#extension GL_OES_EGL_image_external : require
//這行是OpenGL ES 3.0中的宣告
#extension GL_OES_EGL_image_external_essl3 : require
上面的過程就是使用這種擴充套件型別的紋理ID從建立到設定引數,再到真正的渲染整個過程,接下來再根據一個完整的示例
看一下具體的旋轉角度問題,因為在使用攝像頭的時候很容易在預覽的時候會出現倒立、映象等問題。
開始專案實踐
先不多說,直接實踐看效果,基於之前的專案工程,新建CameraSurfaceRenderer.java
檔案:
/**
* @anchor: andy
* @date: 2018-11-09
* @description: 基於相機
*/
public class CameraSurfaceRenderer implements GLSurfaceView.Renderer {
private static final String TAG = "TextureRenderer";
private final FloatBuffer vertexBuffer, mTexVertexBuffer;
private final ShortBuffer mVertexIndexBuffer;
private int mProgram;
private int textureId;
/**
* 頂點座標
* (x,y,z)
*/
private float[] POSITION_VERTEX = new float[]{
0f, 0f, 0f, //頂點座標V0
1f, 1f, 0f, //頂點座標V1
-1f, 1f, 0f, //頂點座標V2
-1f, -1f, 0f, //頂點座標V3
1f, -1f, 0f //頂點座標V4
};
/**
* 紋理座標
* (s,t)
*/
private static final float[] TEX_VERTEX = {
0.5f, 0.5f, //紋理座標V0
1f, 1f, //紋理座標V1
0f, 1f, //紋理座標V2
0f, 0.0f, //紋理座標V3
1f, 0.0f //紋理座標V4
};
/**
* 索引
*/
private static final short[] VERTEX_INDEX = {
0, 1, 2, //V0,V1,V2 三個頂點組成一個三角形
0, 2, 3, //V0,V2,V3 三個頂點組成一個三角形
0, 3, 4, //V0,V3,V4 三個頂點組成一個三角形
0, 4, 1 //V0,V4,V1 三個頂點組成一個三角形
};
private float[] transformMatrix = new float[16];
/**
* 渲染容器
*/
private GLSurfaceView mGLSurfaceView;
/**
* 相機ID
*/
private int mCameraId;
/**
* 相機例項
*/
private Camera mCamera;
/**
* Surface
*/
private SurfaceTexture mSurfaceTexture;
/**
* 矩陣索引
*/
private int uTextureMatrixLocation;
private int uTextureSamplerLocation;
public CameraSurfaceRenderer(GLSurfaceView glSurfaceView) {
this.mCameraId = Camera.CameraInfo.CAMERA_FACING_FRONT;
this.mGLSurfaceView = glSurfaceView;
mCamera = Camera.open(mCameraId);
setCameraDisplayOrientation(mCameraId, mCamera);
//分配記憶體空間,每個浮點型佔4位元組空間
vertexBuffer = ByteBuffer.allocateDirect(POSITION_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer();
//傳入指定的座標資料
vertexBuffer.put(POSITION_VERTEX);
vertexBuffer.position(0);
mTexVertexBuffer = ByteBuffer.allocateDirect(TEX_VERTEX.length * 4)
.order(ByteOrder.nativeOrder())
.asFloatBuffer()
.put(TEX_VERTEX);
mTexVertexBuffer.position(0);
mVertexIndexBuffer = ByteBuffer.allocateDirect(VERTEX_INDEX.length * 2)
.order(ByteOrder.nativeOrder())
.asShortBuffer()
.put(VERTEX_INDEX);
mVertexIndexBuffer.position(0);
}
private void setCameraDisplayOrientation(int cameraId, Camera camera) {
Activity targetActivity = (Activity) mGLSurfaceView.getContext();
android.hardware.Camera.CameraInfo info =
new android.hardware.Camera.CameraInfo();
android.hardware.Camera.getCameraInfo(cameraId, info);
int rotation = targetActivity.getWindowManager().getDefaultDisplay()
.getRotation();
int degrees = 0;
switch (rotation) {
case Surface.ROTATION_0: degrees = 0; break;
case Surface.ROTATION_90: degrees = 90; break;
case Surface.ROTATION_180: degrees = 180; break;
case Surface.ROTATION_270: degrees = 270; break;
}
int result;
if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
result = (info.orientation + degrees) % 360;
result = (360 - result) % 360; // compensate the mirror
} else { // back-facing
result = (info.orientation - degrees + 360) % 360;
}
camera.setDisplayOrientation(result);
}
/**
* 載入外部紋理
* @return
*/
public int loadTexture() {
int[] tex = new int[1];
//建立一個紋理
GLES30.glGenTextures(1, tex, 0);
//繫結到外部紋理上
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
//設定紋理過濾引數
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.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_S, GLES30.GL_CLAMP_TO_EDGE);
GLES30.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES30.GL_TEXTURE_WRAP_T, GLES30.GL_CLAMP_TO_EDGE);
//解除紋理繫結
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
return tex[0];
}
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
//設定背景顏色
GLES30.glClearColor(0.5f, 0.5f, 0.5f, 0.5f);
//編譯
final int vertexShaderId = ShaderUtils.compileVertexShader(ResReadUtils.readResource(R.raw.vertex_camera_shader));
final int fragmentShaderId = ShaderUtils.compileFragmentShader(ResReadUtils.readResource(R.raw.fragment_camera_shader));
//連結程式片段
mProgram = ShaderUtils.linkProgram(vertexShaderId, fragmentShaderId);
uTextureMatrixLocation = GLES30.glGetUniformLocation(mProgram, "uTextureMatrix");
//獲取Shader中定義的變數在program中的位置
uTextureSamplerLocation = GLES30.glGetUniformLocation(mProgram, "yuvTexSampler");
//載入紋理
textureId = loadTexture();
//載入SurfaceTexture
loadSurfaceTexture(textureId);
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
GLES30.glViewport(0, 0, width, height);
}
@Override
public void onDrawFrame(GL10 gl) {
GLES30.glClear(GLES30.GL_COLOR_BUFFER_BIT);
//使用程式片段
GLES30.glUseProgram(mProgram);
//更新紋理影象
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(transformMatrix);
//啟用紋理單元0
GLES30.glActiveTexture(GLES30.GL_TEXTURE0);
//繫結外部紋理到紋理單元0
GLES30.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
//將此紋理單元床位片段著色器的uTextureSampler外部紋理取樣器
GLES30.glUniform1i(uTextureSamplerLocation, 0);
//將紋理矩陣傳給片段著色器
GLES30.glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);
GLES30.glEnableVertexAttribArray(0);
GLES30.glVertexAttribPointer(0, 3, GLES30.GL_FLOAT, false, 0, vertexBuffer);
GLES30.glEnableVertexAttribArray(1);
GLES30.glVertexAttribPointer(1, 2, GLES30.GL_FLOAT, false, 0, mTexVertexBuffer);
// 繪製
GLES20.glDrawElements(GLES20.GL_TRIANGLES, VERTEX_INDEX.length, GLES20.GL_UNSIGNED_SHORT, mVertexIndexBuffer);
}
public boolean loadSurfaceTexture(int textureId) {
//根據紋理ID建立SurfaceTexture
mSurfaceTexture = new SurfaceTexture(textureId);
mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
mGLSurfaceView.requestRender();
}
});
//設定SurfaceTexture作為相機預覽輸出
try {
mCamera.setPreviewTexture(mSurfaceTexture);
} catch (IOException e) {
e.printStackTrace();
return false;
}
//開啟相機預覽
mCamera.startPreview();
return true;
}
}
頂點著色器:
#version 300 es
layout (location = 0) in vec4 vPosition;
layout (location = 1) in vec4 aTextureCoord;
//紋理矩陣
uniform mat4 uTextureMatrix;
out vec2 yuvTexCoords;
void main() {
gl_Position = vPosition;
gl_PointSize = 10.0;
//只保留x和y分量
yuvTexCoords = (uTextureMatrix * aTextureCoord).xy;
}
片段著色器:
#version 300 es
//OpenGL ES3.0外部紋理擴充套件
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
vFragColor = texture(yuvTexSampler,yuvTexCoords);
}
注意:
外部紋理擴充套件在OpenGL ES 2.0
和OpenGL ES 3.0
中不太一樣。
//OpenGL ES2.0外部紋理擴充套件
#extension GL_OES_EGL_image_external : require
在啟動的Activity中處理初始化的部分:
private void setupView() {
//例項化一個GLSurfaceView
mGLSurfaceView = new GLSurfaceView(this);
mGLSurfaceView.setEGLContextClientVersion(3);
mGLSurfaceView.setRenderer(new CameraSurfaceRenderer(mGLSurfaceView));
setContentView(mGLSurfaceView);
}
來個剪刀手看看效果:
簡單的濾鏡處理
通過紋理將相機採集的資料渲染到GLSurfaceView
的過程中,我們也可以新增各種濾鏡。
給相機新增黑白濾鏡,修改片段著色器
#version 300 es
//OpenGL ES3.0外部紋理擴充套件
#extension GL_OES_EGL_image_external_essl3 : require
precision mediump float;
uniform samplerExternalOES yuvTexSampler;
in vec2 yuvTexCoords;
out vec4 vFragColor;
void main() {
vec4 vCameraColor = texture(yuvTexSampler,yuvTexCoords);
float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);
vFragColor = vec4(fGrayColor, fGrayColor, fGrayColor, 1.0);
}
可以參照下面的幾個轉換公式:
標清電視使用的標準BT.601