筆者以前寫了三篇Android中使用OpenGL ES入門級的文章,從OpenGL ES的相關概念出發,分析了利用OpenGL ES實現3D繪圖的重要的兩個步驟:定義形狀和繪製形狀,簡單的繪製了一個三角形。
這裏再簡單回顧下:Android中使用OpenGL一共會涉及到四個類:android
1)Activity——自不用說,Android界面展現的類;
2)GLSurfaceView——SurfaceView(API Level 1)的子類,View的孿生兄弟-SurfaceView專用於要求頻繁刷新的界面中,好比相機預覽界面;而GLSurfaceVIew(API Level 3)是繼承於SurfaceView,且實現了SurfaceHolder.Callback2接口,專門用於OpenGL 中界面的展現;
3)GLSurfaceView.Renderer——這是一個接口,咱們會自定義一個渲染器類來講實現這個接口。GLSurfaceView自己並不處理不少圖像展現的相關任務,更多的實現是放在了該渲染器類中進行實現;該接口的實現會要求實現三個方法,每一個方法會最終對應Camera的一種狀態;
4)圖像類——具體的類,具體實現看需求,好比筆者前面的文章中,該圖像類就是Triangle,三角形類,關鍵的類,最終圖像預覽時呈現何種狀態是在這個類中被定義的。ide
前期基礎知識儲備
SurfaceTexture瞭解
SurfaceTexture(API Level 11),掌握這個類是利用OpenGL實現相機預覽的關鍵所在。等會看完代碼,你就會發現,利用OpenGL畫一個簡單的形狀和利用OpenGL實現相機預覽兩個看起來難度相差很大的任務實際上用到的OpenGL的代碼倒是十分類似的,惟一的區別就是在於相機預覽時會用到SurfaceTexture進行紋理數據的處理。首先看下開發者文檔中的描述:ui
Captures frames from an image stream as an OpenGL ES texture.
The image stream may come from either camera preview or video decode. A Surface created from a SurfaceTexture can be used as an output destination for the android.hardware.camera2, MediaCodec, MediaPlayer, and Allocation APIs. When updateTexImage() is called, the contents of the texture object specified when the SurfaceTexture was created are updated to contain the most recent image from the image stream. This may cause some frames of the stream to be skipped.this
從文檔中的描述,咱們能夠知道:SurfaceTexture的做用就是從Image Stream中捕獲幀數據,用做OpenGL的紋理(紋理用於填充片元着色器,即「上色」),其中Image Stream來自相機預覽或視頻解碼。 因此咱們可使用SurfaceTexture來和Camera進行鏈接(調用camera.setPreviewTexture(mSurfaceTexture)方法)從而獲取相機傳過來的預覽圖像流數據,而SurfaceTexture類獲取到圖像流數據以後並不會進行顯示,而是對其處理後傳給GLSurfaceView或者TextureView進行顯示,所以SurfaceTexture和GLSurfaceView的搭配使用十分合適。code
上代碼,具體實現
1)自定義相機預覽類,實現渲染器接口和SurfaceTexture的接口;orm
public class CameraGLSurfaceView extends GLSurfaceView implements Renderer, SurfaceTexture.OnFrameAvailableListener
1
2)寫入詳解預覽類CameraGLSurfaceView的構造方法;視頻
public CameraGLSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
mContext = context;
setEGLContextClientVersion(2);
setRenderer(this);
setRenderMode(RENDERMODE_WHEN_DIRTY);
}
看過筆者以前OpenGL文章的朋友理解這段代碼是沒有困難的,這裏首先指定使用的OpenGL ES的版本爲2.0,而後調用setRenderer()方法綁定適渲染器接口,最後調用setRendererMode()的方法指定模式爲RENDERMODE_WHEN_DIRTY-即有新數據時進行刷新,不然等待。對象
3)關鍵-實現渲染器接口的三個方法繼承
@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
// TODO Auto-generated method stub
Log.i(TAG, "onSurfaceCreated...");
mTextureID = createTextureID();
mSurface = new SurfaceTexture(mTextureID);
mSurface.setOnFrameAvailableListener(this);
mDirectDrawer = new DirectDrawer(mTextureID);
CameraInterface.getInstance().doOpenCamera(null); //①在建立surface時打開相機
}
@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
// TODO Auto-generated method stub
Log.i(TAG, "onSurfaceChanged...");
GLES20.glViewport(0, 0, width, height);
if(!CameraInterface.getInstance().isPreviewing()){
CameraInterface.getInstance().doStartPreview(mSurface, 1.33f); //②預覽
}
}
@Override
public void onDrawFrame(GL10 gl) {
// TODO Auto-generated method stub
Log.i(TAG, "onDrawFrame...");
GLES20.glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
mSurface.updateTexImage(); //SurfaceTexture的關鍵方法
float[] mtx = new float[16];
mSurface.getTransformMatrix(mtx); //SurfaceTexture的關鍵方法
mDirectDrawer.draw(mtx); //③調用圖形類的draw()方法
}
分析:這三個方法裏面的代碼很是關鍵,(1)onSurfaceCreated()方法中實例化SurfaceTexture對象mSurface和圖形類對象mDirectDrawer,而且在這裏調用了相機的開啓方法doOpenCamera(),這是使用OpenGL實現相機預覽時OpenGL代碼對相機的第一次控制;
(2)onSurfaceChanged()方法中調用相機的doStartPreview()方法,這個方法調用在相機界面發生變化時,會關閉相機預覽而後從新打開相機預覽,即doStartPreview()方法內部實現實際是連續調用了mCamera.stopPreview(); mCamera.setPreviewDisplay(mHolder); mCamera.startPreview();三個方法;
(3)onDrawFrame()方法最爲關鍵,首先是兩行OpenGL的常規設置代碼(glClearColor()、glClear()),用於設置背景;接着是連續調用了SurfaceTexture的兩個關鍵的方法:①updateTexImage(), 當updateTexImage()方法被調用時,SurfaceTexture對象所關聯的OpenGLES中紋理對象的內容將被更新爲相機預覽傳出的圖像流中最新的圖片;②getTransformMatrix(),該方法也是必須調用的,用以轉換已經發生變換的紋理矩陣的座標;最後是調用了圖像類的draw()方法用以繪製預覽的界面。接口
4)剩餘的代碼段,輔助做用
@Override
public void onPause() {
// TODO Auto-generated method stub
super.onPause();
CameraInterface.getInstance().doStopCamera(); //④暫停預覽時調用的相機方法
}
private int createTextureID()
{
int[] texture = new int[1];
GLES20.glGenTextures(1, texture, 0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture[0]);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
return texture[0];
}
public SurfaceTexture _getSurfaceTexture(){
return mSurface;
}
//該方法是實現SurfaceTexture.OnFrameAvailableListener接口時實現的方法
//用於提示新的數據流的到來
@Override
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
// TODO Auto-generated method stub
this.requestRender();
}
以上的代碼就是自定義的相機預覽類的中的關鍵代碼,接下來,咱們再看看另外的一個關鍵類——圖像類
1)自定義一個圖像類 DirectDrawer
public class DirectDrawer
1
2)相關變量的聲明:頂點着色器的聲明,片元着色器的聲明,緩衝區變量的聲明,程式的聲明,頂點座標的聲明,紋理座標的聲明
private final String vertexShaderCode =
"attribute vec4 vPosition;" +
"attribute vec2 inputTextureCoordinate;" +
"varying vec2 textureCoordinate;" + //傳給片元着色器的變量
"void main()" +
"{"+
"gl_Position = vPosition;"+
"textureCoordinate = inputTextureCoordinate;" +
"}";
private final String fragmentShaderCode =
"#extension GL_OES_EGL_image_external : require\n"+ //固定指令
"precision mediump float;" + //固定指令
"varying vec2 textureCoordinate;\n" + //頂點着色器中傳遞過來的變量
"uniform samplerExternalOES s_texture;\n" + //固定指定
"void main() {" +
" gl_FragColor = texture2D( s_texture, textureCoordinate );\n" +
"}";
private FloatBuffer vertexBuffer, textureVerticesBuffer;
private ShortBuffer drawListBuffer;
private final int mProgram;
private int mPositionHandle;
private int mTextureCoordHandle;
private short drawOrder[] = { 0, 1, 2, 0, 2, 3 }; //繪製頂點的順序
// number of coordinates per vertex in this array
private static final int COORDS_PER_VERTEX = 2;
private final int vertexStride = COORDS_PER_VERTEX * 4; // 4 bytes per vertex
//頂點座標-座標值是固定的搭配,不一樣的順序會出現不一樣的預覽效果
static float squareCoords[] = {
-1.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f,
};
//紋理座標-相機預覽時對片元着色器使用的是紋理texture而不是顏色color
static float textureVertices[] = {
0.0f, 1.0f,
1.0f, 1.0f,
1.0f, 0.0f,
0.0f, 0.0f,
};
2)圖像類的構造方法-相機預覽的圖像類和畫圖時的圖像類的構造方法極爲類似
private int texture;
public DirectDrawer(int texture)
{
this.texture = texture;
// initialize vertex byte buffer for shape coordinates
ByteBuffer bb = ByteBuffer.allocateDirect(squareCoords.length * 4);
bb.order(ByteOrder.nativeOrder());
vertexBuffer = bb.asFloatBuffer();
vertexBuffer.put(squareCoords); //第一個裝載的數據是頂點座標
vertexBuffer.position(0);
// initialize byte buffer for the draw list
ByteBuffer dlb = ByteBuffer.allocateDirect(drawOrder.length * 2);
dlb.order(ByteOrder.nativeOrder());
drawListBuffer = dlb.asShortBuffer();
drawListBuffer.put(drawOrder); //第二個裝載的數據是繪製頂點的順序
drawListBuffer.position(0);
ByteBuffer bb2 = ByteBuffer.allocateDirect(textureVertices.length * 4);
bb2.order(ByteOrder.nativeOrder());
textureVerticesBuffer = bb2.asFloatBuffer();
textureVerticesBuffer.put(textureVertices); //第三個裝載的數據是紋理座標
textureVerticesBuffer.position(0);
//解析以前變量中聲明的兩個着色器
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexShaderCode);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentShaderCode);
mProgram = GLES20.glCreateProgram(); // 建立空程式
GLES20.glAttachShader(mProgram, vertexShader); // 添加頂點着色器到程式中
GLES20.glAttachShader(mProgram, fragmentShader); // 添加片元着色器到程式中
GLES20.glLinkProgram(mProgram); // 連接程式
}
}
3)千呼萬喚始出來-繪製方法draw()
public void draw(float[] mtx)
{
GLES20.glUseProgram(mProgram);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texture);
// get handle to vertex shader's vPosition member
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
// Enable a handle to the triangle vertices
GLES20.glEnableVertexAttribArray(mPositionHandle);
// Prepare the <insert shape here> coordinate data
GLES20.glVertexAttribPointer(mPositionHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, vertexBuffer);
//使用一次glEnableVertexAttribArray方法就要使用一次glVertexAttribPointer方法
mTextureCoordHandle = GLES20.glGetAttribLocation(mProgram, "inputTextureCoordinate");
GLES20.glEnableVertexAttribArray(mTextureCoordHandle);
GLES20.glVertexAttribPointer(mTextureCoordHandle, COORDS_PER_VERTEX, GLES20.GL_FLOAT, false, vertexStride, textureVerticesBuffer);
GLES20.glDrawElements(GLES20.GL_TRIANGLES, drawOrder.length, GLES20.GL_UNSIGNED_SHORT, drawListBuffer);
// 使用了glEnableVertexAttribArray方法就必須使用glDisableVertexAttribArray方法
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glDisableVertexAttribArray(mTextureCoordHandle);
}
代碼看上去有點複雜,可是結合筆者以前利用OpenGL實現畫圖的draw()方法的代碼和整理使用OpenGL實現相機預覽的draw()方法來看,就會發現這段代碼看上去複雜,實際是幾乎固定的代碼,每一個方法調用都是有規律可循。
程式相關方法
glUseProgram()
頂點着色器相關方法
glGetAttribLocation()
glEnableVertexAttribArray()
glVertexAttribPointer()
片元着色器相關方法
glGetUniformLocation()
glUniform4fv()
繪製相關方法
glDrawArrays-繪製方法
收尾相關方法
glDisableVertexAttribArray()
前面OpenGL相關文章的讀者朋友會發現使用OpenGL畫圖和使用OpenGL實現相機預覽時,圖像類的變量聲明、構造方法和繪製方法draw()都是十分的類似的,換言之,這裏的使用OpenGL的代碼幾乎是能夠當作是模板代碼,而實現兩種功能的不一樣就在於相機預覽類中使用了SurfaceTexture進行相關數據傳輸和控制。