前言
在上一篇博客中医吊,簡單介紹了一下有關(guān)于OpenGL的基礎(chǔ)內(nèi)容,沒看過的,可以看一下OpenGL ES基礎(chǔ)缘眶,如果對(duì)里面有很多內(nèi)容還是不懂的話嘱根,就百度一下吧,里面我都是簡單說了一下大概內(nèi)容巷懈,從這一篇開始该抒,用仿抖音的項(xiàng)目來一步步具體介紹怎么在Android中使用OpenGL。
首先抖音其實(shí)就是錄制前處理和錄制后特效的處理砸喻,今天先來第一步使用OpenGL顯示攝像頭柔逼,為后面的工作做準(zhǔn)備。
需求
使用OpenGL顯示攝像頭割岛,分析一下需求愉适,其實(shí)也就是兩步,第一步采集攝像頭數(shù)據(jù)癣漆,第二步將攝像頭數(shù)據(jù)顯示到屏幕上
采集攝像頭數(shù)據(jù)
使用Android的Camera就可以實(shí)現(xiàn)采集
將攝像頭數(shù)據(jù)顯示到屏幕上
這里顯示到屏幕上维咸,其實(shí)有很多方法,比如ANative_window等等惠爽,但是這個(gè)是使用OpenGL實(shí)現(xiàn)癌蓖,使用OpenGL怎么實(shí)現(xiàn)呢,在上一篇博客中說到了OpenGL的繪制流程婚肆,基本就是按照那個(gè)流程進(jìn)行實(shí)現(xiàn)的租副。
下面是項(xiàng)目結(jié)構(gòu)中一個(gè)不太規(guī)范的類圖,下面根據(jù)這種圖來實(shí)現(xiàn)具體的代碼
簡單點(diǎn)說较性,我們都知道SurfaceView實(shí)質(zhì)是將底層顯存Surface顯示到界面上用僧,而GLSurfaceView實(shí)際就是在這個(gè)基礎(chǔ)之上增加了OpenGL環(huán)境,DouyinView實(shí)際就相當(dāng)于一塊畫布赞咙,而ScreenFilter中是封裝了如何使用畫筆去畫當(dāng)前攝像頭采集到的內(nèi)容责循,而DouyinRender渲染器,就是將ScreenFilter中的畫渲染到畫布上攀操。
創(chuàng)建工程
創(chuàng)建一個(gè)JNI工程院仿,就是在一開始創(chuàng)建項(xiàng)目的時(shí)候,勾選上 Include C++ support,這樣就創(chuàng)建好了
配置文件 AndroidManifest.xml
OpenGL的使用還需要有設(shè)備制造商提供支持速和,以下是設(shè)備的支持情況歹垫,項(xiàng)目里都是用來2.0
OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持這個(gè)API規(guī)范。
OpenGL ES 2.0 :Android 2.2(API 8)和更高的版本支持這個(gè)API規(guī)范颠放。
OpenGL ES 3.0 :Android 4.3(API 18)和更高的版本支持這個(gè)API規(guī)范县钥。
OpenGL ES 3.1 : Android 5.0(API 21)和更高的版本支持這個(gè)API規(guī)范。
DouyinView.java
DouyinRender.java
這里使用的Render,是實(shí)現(xiàn)了OpenGL配置好EGL的渲染器慈迈,后面會(huì)自己來配置,這里先使用這個(gè),上面也有提到過痒留,OpenGL的操作都是在GLThread線程中完成谴麦,所以這里的onSurfaceCreated,onSurfaceChanged伸头,onDrawFrame都是在GLThread中實(shí)現(xiàn)的匾效,下面單獨(dú)看看每個(gè)方法的使用
CameraHelper是封裝了對(duì)Camera的一系列操作
創(chuàng)建著色器
AS里面有個(gè)插件可以支持GLSL的高亮顯示,在plugin里面搜索 GLSL Support
頂點(diǎn)著色器
片元著色器
這里我們需要畫的是矩形恤磷,也就是兩個(gè)三角形面哼,給了4個(gè)點(diǎn)的坐標(biāo),這4個(gè)點(diǎn)扫步,會(huì)執(zhí)行4次頂點(diǎn)著色器魔策,依次將頂點(diǎn)傳遞給gl_Position,當(dāng)4個(gè)點(diǎn)都傳遞完成之后河胎,會(huì)進(jìn)行光柵化闯袒,將一個(gè)矩形變換成一個(gè)個(gè)的片元,然后再去使用gl_FragColor去著色
因?yàn)镾urfaceTexture是Android中的游岳,并不是OpenGL的政敢,所以需要使用額外擴(kuò)展的采樣器samplerExternalOES,而不是普通的sample2D采樣器胚迫,在使用samplerExternalOES時(shí)候喷户,需要再添加一句:#extension GL_OES_EGL_image_external : require
ScreenFilter.java
public ScreenFilter(Context context) {
? ? ? ? //把camera_vertext內(nèi)容讀出來
? ? ? ? String vertexSource= OpenUtils.readRawTextFile(context, R.raw.camera_vertex);
? ? ? ? String fragSource = OpenUtils.readRawTextFile(context, R.raw.camera_frag);
? ? ? ? //通過字符串創(chuàng)建著色器程序
? ? ? ? //使用opengl
? ? ? ? //一.頂點(diǎn)著色器
? ? ? ? //1.1創(chuàng)建頂點(diǎn)著色器
? ? ? ? int vSharderId = GLES20.glCreateShader(GLES20.GL_VERTEX_SHADER);
? ? ? ? //1.2綁定代碼到著色器中
? ? ? ? GLES20.glShaderSource(vSharderId,vertexSource);
? ? ? ? //1.3編譯著色器
? ? ? ? GLES20.glCompileShader(vSharderId);
? ? ? ? //1.4主動(dòng)獲取成功失敗
? ? ? ? int[] status=new int[1];
? ? ? ? GLES20.glGetShaderiv(vSharderId,GLES20.GL_COMPILE_STATUS,status,0);
? ? ? ? if (status[0] != GLES20.GL_TRUE){
? ? ? ? ? ? throw new IllegalStateException("ScreenFitler 頂點(diǎn)著色器配置失敗访锻!");
? ? ? ? }
? ? ? ? //二.創(chuàng)建片元著色器
? ? ? ? int fShaderId = GLES20.glCreateShader(GLES20.GL_FRAGMENT_SHADER);
? ? ? ? //綁定代碼到著色器中
? ? ? ? GLES20.glShaderSource(fShaderId,fragSource);
? ? ? ? //編譯
? ? ? ? GLES20.glCompileShader(fShaderId);
? ? ? ? GLES20.glGetShaderiv(fShaderId,GLES20.GL_COMPILE_STATUS,status,0);
? ? ? ? if (status[0] != GLES20.GL_TRUE){
? ? ? ? ? ? throw new IllegalStateException("ScreenFilter 片元著色器配置失敗");
? ? ? ? }
? ? ? ? //三褪尝、把著色器塞到程序里面去,GPU
? ? ? ? mProgram = GLES20.glCreateProgram();
? ? ? ? GLES20.glAttachShader(mProgram,vSharderId);
? ? ? ? GLES20.glAttachShader(mProgram,fShaderId);
? ? ? ? //連接著色器
? ? ? ? GLES20.glLinkProgram(mProgram);
? ? ? ? //獲取程序是否配置成功
? GLES20.glGetProgramiv(mProgram,GLES20.GL_LINK_STATUS,status,0);
? ? ? ? if (status[0] != GLES20.GL_TRUE){
? ? ? ? ? ? throw? new IllegalStateException("Screen Filter 著色器程序連接失敗");
? ? ? ? }
? ? ? ? //因?yàn)橹饕呀?jīng)塞到了GPU程序中朗若,所以可以刪除了
? ? ? ? GLES20.glDeleteShader(vSharderId);
? ? ? ? GLES20.glDeleteShader(fShaderId);
? ? ? ? //獲得著色器程序中的變量的索引恼五,通過這個(gè)索引對(duì)其進(jìn)行賦值
? ? ? ? vPosition = GLES20.glGetAttribLocation(mProgram,"vPosition");
? ? ? ? vCoord = GLES20.glGetAttribLocation(mProgram,"vCoord");
? ? ? ? vMatrix = GLES20.glGetUniformLocation(mProgram,"vMatrix");
? ? ? ? vTexture =? GLES20.glGetUniformLocation(mProgram,"vTexture");
? ? ? ? //創(chuàng)建一個(gè)數(shù)據(jù)緩沖區(qū)
? ? ? ? //OpenGL的中頂點(diǎn)的位置坐標(biāo)
? ? ? ? mVertextBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
? ? ? ? mVertextBuffer.clear();
? ? ? ? float[] v = {-1.0f,-1.0f,
? ? ? ? ? ? ? ? 1.0f,-1.0f,
? ? ? ? ? ? ? ? -1.0f,1.0f,
? ? ? ? ? ? ? ? 1.0f,1.0f
? ? ? ? };
? ? ? ? mVertextBuffer.put(v);
? ? ? ? //采樣器采樣圖片的坐標(biāo)
? ? ? ? mTextureBuffer = ByteBuffer.allocateDirect(4 * 2 * 4).order(ByteOrder.nativeOrder()).asFloatBuffer();
? ? ? ? mTextureBuffer.clear();
//? ? ? ? float[] f={0.0f,1.0f,
//? ? ? ? ? ? ? ? 1.0f,1.0f,
//? ? ? ? ? ? ? ? 0.0f,0.0f,
//? ? ? ? ? ? ? ? 1.0f,0.0f
//? ? ? ? };
//
? ? ? ? //順時(shí)針旋轉(zhuǎn)90度
//? ? ? ? float[] f={1.0f,1.0f,
//? ? ? ? ? ? ? ? 1,0,
//? ? ? ? ? ? ? ? 0,1,
//? ? ? ? ? ? ? ? 0,0
//
//
//? ? ? ? };
//? ? ? ? 鏡像
? ? ? ? float[] f={1.0f,0.0f,
? ? ? ? ? ? ? ? 1.0f,1.0f,
? ? ? ? ? ? ? ? 0.0f,0.0f,
? ? ? ? ? ? ? ? 0.0f,1.0f
? ? ? ? };
? ? ? ? mTextureBuffer.put(f);
? ? }
? ? /**
? ? * 使用著色器程序開始畫畫
? ? * @param texture
? ? * @param mtx
? ? */
? ? public void onDrawFrame(int texture,float[] mtx){
? ? ? ? //1.設(shè)置窗口大小
? ? ? ? GLES20.glViewport(0,0,mWidth,mHeight);
? ? ? ? //2.使用著色器程序
? ? ? ? GLES20.glUseProgram(mProgram);
? ? ? ? /**
? ? ? ? * 3. 畫頂點(diǎn)
? ? ? ? */
? ? ? ? //3.1傳入頂點(diǎn)數(shù)據(jù),確定形狀
? ? ? ? mVertextBuffer.position(0);
? ? ? ? //size:2表示xy兩個(gè)數(shù)據(jù)
? ? ? ? GLES20.glVertexAttribPointer(vPosition,2,GLES20.GL_FLOAT,false,0,mVertextBuffer);
? ? ? ? //3.2激活
? ? ? ? GLES20.glEnableVertexAttribArray(vPosition);
? ? ? ? //4哭懈,片元灾馒,紋理
? ? ? ? mTextureBuffer.position(0);
? ? ? ? GLES20.glVertexAttribPointer(vCoord,2,GLES20.GL_FLOAT,false,0,mTextureBuffer);
? ? ? ? GLES20.glEnableVertexAttribArray(vCoord);
? ? ? ? //5.變換矩陣
? ? ? ? GLES20.glUniformMatrix4fv(vMatrix,1,false,mtx,0);
? ? ? ? //片元
? ? ? ? //激活圖層
? ? ? ? GLES20.glActiveTexture(GLES20.GL_TEXTURE);
? ? ? ? //圖像數(shù)據(jù)
? ? ? ? GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,texture);
? ? ? ? //傳遞參數(shù)
? ? ? ? GLES20.glUniform1i(vTexture,0);
? ? ? ? //參數(shù)傳完了,通知OpenGL畫畫遣总,從第0個(gè)點(diǎn)開始睬罗,共4個(gè)點(diǎn)
? ? ? ? GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,0,4);
? ? }
Tips
? ? 因?yàn)镺penGL中如果顯示不正確,錯(cuò)誤一般都比較難找旭斥,所以在代碼里有在一些地方去獲取是否配置正確容达,來方便找錯(cuò)
? ? 記錄我遇到的兩個(gè)bug,如果遇到問題可做參考垂券,第一個(gè)是在獲取索引的時(shí)候花盐,類型弄錯(cuò)了羡滑。第二個(gè)是在頂點(diǎn)著色器中,獲取像素點(diǎn)aCoord進(jìn)行矩陣變換的時(shí)候算芯,寫成了紋理坐標(biāo) * 矩陣柒昏,這樣出來的結(jié)果是錯(cuò)的,一定要是 矩陣 * 紋理坐標(biāo)熙揍。