androidopengles教程
A. Android OpenGLES3繪圖 - 音頻可視化(模仿MIUI系統效果)
小米手機播放音樂時鎖屏頁面可以設置音頻可視化效果,這是用OpenGL繪制出來的,我們來實現一下。
首先簡單分析一下原理:
圖形的每一行代表一個聲音片段,它就是一個一維數組,按照數值大小繪制不同的高度,就形成了一條「山脈」;獲取到下一個聲音片段後,將它繪制到下面一行,然後畫面整體向上滾動就可以了。整體類似於繪制一張游戲里常見的3D地形圖。
創建一個MediaPlayer,它可以直接讀取res/raw裡面的音頻文件,start()開始播放
Visualizer是Android SDK裡面提供的音頻分析工具,它可以直接獲取播放的音頻的波形和頻譜。onWaveFormDataCapture回調方法里返回的是原始的PCM波形數組,onFftDataCapture回調方法里返回的是經過快速傅里葉方法轉換後的聲音頻譜數組,數組的第一位是直流分量,後面是不同頻率的數值。
每次獲取到的是一組聲音數據,將它傳給Render繪制。
首先確定圖形的長寬,寬度w其實是由每組音頻的數組長度決定,可以由Visualizer.getCaptureSizeRange()[0]獲取,這里獲取的是最小的數組,也可以用Visualizer.getCaptureSizeRange()[1]獲取最大的數組;長度h可以自己設置想展示多長。
繪制地形圖也就是繪制w * h * 2個三角形,創建vao、vbo和ebo,由於頂點的位置都是固定的,可以在頂點著色器中用gl_VertexID獲取,所以vbo裡面不用傳頂點數據,直接傳聲音數組。
由於圖形是不斷刷新最後一行並向上滾動的,那麼需要使用一個隊列,為了每一幀數據改變最小,不至於進行大量的數組復制和移動。我們 用ByteBuffer vertexBuffer模擬一個循環隊列,使用一個行號int lineNum來標記隊列的頭部。每添加一行數據後,lineNum會加上w,這樣ByteBuffer分成了兩部分:lineNum * w之後的是新舊數據,之前的是舊數據 。
現在我們需要將數據從主內存(vertexBuffer)復制到GPU顯存(vbo)。vertexBuffer里是一個循環隊列,而vbo裡面只能順序保存(因為ebo序號是順序的,vbo不是順序圖形就會錯亂),更新vbo數據緩存的glBufferSubData方法支持設置偏移位置部分更新。那麼我們 先將vertexBuffer定位到lineNum * w,將它後面的舊數據復制到vbo的前面;然後將vertexBuffer定位到0,將剩下的新數據復制到vbo的後面 。這樣就保證了繪制時從上到下,從舊到新。
為了讓顏色更豐富,這里用了地形圖中常用的熱度漸變色數組。
理論上音頻數值是unsigned byte格式的,但是著色器不支持byte格式,我直接用int vPosition接收數據,然而數值范圍不再是0~255了,這有點奇怪,我沒有深入研究。簡單測試了一下,發現取int的前8位,再進行一點比例縮放,用它去漸變色數組里取顏色,會取得較好的顯示效果。
頂點著色器
shader_audio_v.glsl
將顏色傳給片段著色器顯示
shader_audio_f.glsl
最終效果如下圖,錄屏設置的碼率比較低,實際上是很清晰的。
完整項目在 SurfacePaint 項目下的 opengles3 模塊里的audio。
B. 如何在Android上使用OpenGL ES 2.0繪制點
OpenGLES是一個讓人崩潰的東西。在Andorid手機上做3D還就得用它。把我記的一些筆記分享在這里吧:AndroidOpenGLES簡介20011-6-3Android系統使用OpenGL的標准介面來支持3D圖形功能,android3D圖形系統也分為java框架和本地代碼兩部分。本地代碼主要實現的OpenGL介面的庫,在Java框架層,javax.microedition.khronos.opengles是java標準的OpenGL包,android.opengl包提供了OpenGL系統和AndroidGUI系統之間的聯系。Android的本地代碼位於frameworks/base/opengl下,JNI代碼位於frameworks/base/core/com_google_android_gles_jni_GLImpl.cpp和frameworks/base/core/com_google_android_gles_jni_EGLImpl.cpp,java類位於opengl/java/javax/microedition/khronos下本地測試代碼位於frameworks/base/opengl/tests。包括angeles、fillrate等14個測試代碼,這些代碼都可以通過終端進行本地調用測試(模擬器中使用adbshell)。OpenGLES1.x固定管線操作,支持glVertexPointer()等函數,不支持GLSL。頭文件在ndk的GLES目錄下,庫文件是libGLESv1_CM.so。OpenGLES2.x可編程管線操作,不兼容1.x,不支持固定管線操作,例如glVertexPointer()等函數。支持GLSL(還必須用這個來編程)。頭文件在ndk的GLES2目錄下,庫文件是libGLESv2.so。OpenGLES學習2011-6-30OpenGL定義了自己的數據類型。應該堅持使用這些OpenGL的數據類型,從而保證可移植性和效率。OpenGLES目前不支持64位數據類型。OpenGLES只支持三邊形。OpenGLES只支持gl開頭的函數,glu庫都不支持。OpenGLES從OpenGL中刪除的功能:1.glBegin/glEnd2.glArrayElement3.顯示列表4.求值器5.索引色模式6.自定義裁剪平面7.glRect8.圖像處理(這個一般顯卡也沒有,FireGL/Quadro顯卡有)9.反饋緩沖10.選擇緩沖11.累積緩沖12.邊界標志13.glPolygonMode14.GL_QUADS,GL_QUAD_STRIP,GL_POLYGON15.glPushAttrib,glPopAttrib,glPushClientAttrib,glPopClientAttrib16.TEXTURE_1D、TEXTURE_3D、TEXTURE_RECT、TEXTURE_CUBE_MAP17.GL_COMBINE18.自動紋理坐標生成19.紋理邊界20.GL_CLAMP、GL_CLAMP_TO_BORDER21.消失紋理代表22.紋理LOD限定23.紋理偏好限定24.紋理自動壓縮、解壓縮25.glDrawPixels,glPixelTransfer,glPixelZoom26.glReadBuffer,glDrawBuffer,glCopyPixelsOpenGLES2.02011-10-92.0和1.1不兼容。-2.0使用的頭文件是ndk的include目錄下的GLES2目錄,有gl2.h,gl2ext.h,gl2platform.h,而1.1使用的是GLES目錄。-2.0使用的庫文件是ndk的lib目錄下的libGLESv2.so,而1.1使用的是libGLESv1_CM.so。-2.0中取消了很多1.1函數,例如glMatrixModel和glLoadIdentity等。OpenGL著色語言(GLSL――OpenGLShadingLanguage)-使用2.0,必須學此語言。因為很多1.1的函數都被取消了。san-angelesNDKOpenGLES1.1的例子程序2012-3-8SanAngeles,查維基網路,是一個虛構的未來概念城市,位於南加州。常在電影中出現,來源自LosAngeles和SanDiego.該程序的演示效果是,觀察一個宏偉的城市,地面是鏡面有建築倒影,城市中有飛船飛過。前後有5、6個觀察點,而且鏡頭在每個觀察點不停的移動。這個例子,用NDK(C++)調用OpenGLES1.1來繪制了SanAngeles這個城市。基本上全部使用了NDK,Java程序只有1個。用vc2005演示一下,目的通過跟蹤代碼了解一些細節。方法是:-將jni下所有的.h文件,以及demo.c,app-win32.c復制出來,放在一個專門的目錄下,然後改造成用OpenGL的而不是ES的。(或者乾脆刪除大段的繪制代碼,保證編譯通過)首先分析Java代碼-DemoActivity.java,這是唯一的Java文件,它主要需要下列4個jni的介面:-();//初始化-(intw,inth);-();//繪制1幀-();其次分析C++代碼app-android.c-首先,調用了importGLInit(),動態導入OpenGL的庫。-其次,調用了appInit(),在內存中建立了平台無關的3D對象集合。建立方法是用一個數組,用類似畫圓拔高的方式產生諸多三角形。-然後,在每個時鍾周期中調用appRender(),細節是:-prepareFrame(width,height);//准備OpenGLES繪制框架。其實就是清空顏色和深度緩沖,重置投影和模型矩陣。-camTrack();//算好在當前時鍾周期,鏡頭的位置、朝向及焦距等。然後調用gluLookAt來實現。-configureLightAndMaterial();//設置光源和材質-drawModels(-1);//先繪制倒影(其實就是將所有模型z軸倒過來畫)-第一個循環,是畫精緻的物體-第二個循環,是畫運動的物體-drawGroundPlane();//再繪制鏡子一般的地面。在繪制前取消光照,打開混合,然後繪制。繪制後還原狀態。-drawModels(1);//再繪制所有模型-drawFadeQuad();//最後繪制淡出框,用融合的方式畫一個遮住整個視口的2D框,融合系數和時間相關。
C. [OpengGL]渲染流程和程序流程
這節要說的是Opengles的渲染流程和程序流程,都是一些非常基礎的東西,覺得已經熟悉的同學可以自行忽略。
以下是一幅經典的Opengl渲染管線流程圖
Opengles中包含三種圖元方式,點,線,三角形
包含對頂點數據的處理和轉換
把所有輸入的頂點數據作為輸入,將所有點配裝成指定圖元的形狀
(Opengl特有) 把基本圖元形式的頂點的幾何作為輸入,可以通過產生新頂點構造出新的基本圖元來生成其它形狀
(Opengl特有)可以把基本圖元細分為更多的基本圖形,創建出更加平滑的視覺效果
像素化,圖形映射到屏幕上網路的像素點,生成提供片段給片元著色器處理,光柵化包含一個剪裁處理,會計算舍棄超出定義視窗外的像素
為每一個像素點提供最終的顏色,這里會可以提供紋理題圖,如果是3D場景其可以包含3D場景的一些額外數據,例如光線,陰影
對每個像素點進行深度測試,Alpha測試並進行顏色混合操作,進一步合成整個視窗每一個像素點最終要顯示的顏色和透明度
如果從API的角度來分析,你會發現有更多的操作。
在輸入頂點數據的時候需要線做頂點緩沖,這里可以使用頂點緩沖去對象(VBO),頂點數組對象(VAO)。VBO可以減少IO開銷,並使用數組記錄每一個快數據對應綁定的類型(頂點位置、法向量),這些數據會存放在GPU上。VAO是使用一個數組來存每一個VBO儲存的數據、類型,每次回執時就不需要一個一個傳遞了。
經過片元著色之後,測試和混合也是分很多種
每一個片元在幀緩沖中對應的位置,若干對應的位置裁剪窗口中則將此片元送入下一個階段,否則會丟棄此片元,可以在屏幕上指定區域繪制,不在這片區域不進行繪制
深度測試是用片元的深度值和幀緩沖中儲存的對應位置的片元的深度值進行比較,深度值小的片元會覆蓋或混合深度值大得片元。
模板測試 講回執區域限定在一定的范圍內,一般用於湖面倒影,鏡像等場合
如果程序開啟了Alpha混合,則可以根據上一階段對應的片元和幀緩沖的位置片元進行alpha混合
抖動可以模擬更大的色深,需要自己編寫演算法實現,通過GL_DITHER來控制
opengles並不是直接在屏幕上進行繪制,是預先在幀緩沖區進行繪制,當繪制完之後再下將繪制的結果交換到屏幕上,因此每繪制新的一幀是都需要清除緩沖區的相關數據,否則會產生不正確的繪制效果。
這些都是基本的渲染流程,接下來說一下程序流程,以Android程序為例
這個之前需要了解一下Android中屏幕顯示對Opengles的承載,
SurfaceTexture,TextureView, SurfaceView和GLSurfaceView
值得注意的是Android直接內置了Opengles,並內置了GL10,GL20,GL30的類,封裝了Opengles的Android API,當然其中也屏蔽了一些細節,對於真正去理解opengles實現有一定的差距。
初學者很多會選用GLSurfaceView來做實現,例如簡單繪制圖形是沒問題的。但是我們如果深入一點學習,例如濾鏡,例如錄制播放,還是需要使用SurfaceView來做的,因為SurfaceView可以控制繪制的線程,需要自己定義EGL環境,還有SurfaceTexture綁定,同樣這也是初學者使用時的難點。
使用一個GLSurfaceView來顯示一個三角形為例,這里就屏蔽了EGL、GLThread和SurfaceTexture使用的細節,重點關注在Opengles中的實現。
GLSurfaceView.Render提供三個回調介面
onSurfaceCreated 紋理窗口創建
onSurfaceChanged 視口大小更改
onDrawFrame 繪制
初始化的時候,准備好頂點著色器和片元著色器內容,這裡面頂點做色器和片元著色器可以使用字元串讀取,也可以使用glsl的shader文件來讀取。
載入紋理
創建紋理空間->載入紋理資源->編譯紋理
載入紋理綁定到程序
載入紋理->綁定程序
在GLsurfaceView繪制的時候,調用繪制渲染
清屏->指定使用程序->傳入參數到著色器->允許GPU讀取->繪制圖形
日誌列印
Opengles2.0對應的是Android 2.2(API 8)
Opengles3.0對應的是Android 4.3(API 18)
Opengles3.1對應的是Android 5.0(API 21)
先將頂點著色器和片段著色器文件貼出來(這是用來渲染普通視頻用的),這是使用的OpenGLES3.0版本。(存在兼容性問題),下面只是一部分問題,且這里就不將bug的log寫出來了,這是提示大家正確的寫法。
頂點著色器
片段著色器
1.沒有在著色器文件中標明使用版本的時候默認使用2.0版本。
在上面的著色器文件中添加#version 300 es即表明使用3.0版本,如果不添加則使用默認2.0版本(注意此行必須放在第一行)。同時注意使用3.0的api的時候必須添加此行。
2.3.0中attribute變成了in和out
OpenGL ES 3.0中將2.0的attribute改成了in,頂點著色器的varying改成out,片段著色器的varying改成了in,也就是說頂點著色器的輸出就是片段著色器的輸入,另外uniform跟2.0用法一樣。
3.3.0中使用內置參數gl_FragColor會出問題
這里我們只能自定義顏色向量out vec4 vFragColor;
4.3.0中將GL_OES_EGL_image_external變為了GL_OES_EGL_image_external_essl3
在使用紋理擴展的時候,也就是uniform samplerExternalOES sTexture的時候。在3.0中我們使用GL_OES_EGL_image_external_essl3而不是GL_OES_EGL_image_external。使用相機採集紋理的時候就知道了
5.3.0中將紋理的處理方法統一為texture
在2.0中2D紋理和3D紋理處理分別使用texture2D和texture3D方法,而在3.0後使用texture統一處理。
6.in或者out變數等不能在函數內(如main函數內)聲明
OpenglES3.0新特性
D. Android OpenGLES2.0(十六)——3D模型貼圖及光照處理(obj+mtl)
在Android OpenGLES2.0(十四)——Obj格式3D模型載入中實現了Obj格式的3D模型的載入,載入的是一個沒有貼圖,沒有光照處理的帽子,為了呈現出立體效果,「手動」加了光照,擁有貼圖的紋理及光照又該怎麼載入呢?
本篇博客例子中載入的是一個卡通形象皮卡丘,資源是在網上隨便找的一個。載入出來如圖所示:
obj內容格式如下:
mtl文件內容格式如下:
關於Obj的內容格式,在上篇博客中已經做了總結,本篇博客中使用的obj,可以看到f後面的不再跟的是4個數字,而是 f 2/58/58 3/59/59 17/60/60 這種樣子的三組數,每一組都表示為頂點坐標索引/貼圖坐標點索引/頂點法線索引,三個頂點組成一個三角形。而頭部的 mtllib pikachu.mtl 則指明使用的材質庫。
而mtl格式文件中,主要數據類型為:
模型載入和之前的模型載入大同小異,不同的是,這次我們需要將模型的貼圖坐標、頂點法線也一起載入,並傳入到shader中。其他參數,有的自然也要取到。
模型載入以obj文件為入口,解析obj文件,從中獲取到mtl文件相對路徑,然後解析mtl文件。將材質庫拆分為諸多的單一材質。obj對象的 載入,根據具使用材質不同來分解為多個3D模型。具體載入過程如下:
頂點著色器
片元著色器
完成了以上准備工作,就可以調用 readMultiObj 方法,將obj文件讀成一個或多個帶有各項參數的3D模型類,然後將每一個3D模型的參數傳入shader中,進而進行渲染:
E. Android OpenGLES2.0(十八)——輕松搞定Blend顏色混合
Blend是OpenGL中的一個非常重要的部分,它可以讓每個輸出的源和目的顏色以多種方式組合在一起,以呈現出不同的效果,滿足不同的需求。
在OpenGLES1.0中,Blend在OpenGLES固定的管線中,OpenGLES2.0相對1.0來說,更為靈活。在OpenGLES2.0中,與Blend相關的函數及功能主要有:
Blend的使用比較簡單,但是如果不理解Blend的這些函數及參數的意義,使用了錯誤的參數,就難以獲得我們所期望的混合結果了。
想要使用Blend, glEnable(GL_BLEND) 當然是必須的。與之對應的,不需要Blend的時候,我們需要調用 glDisable(GL_BLEND) 來關閉混合。
另外的四個方法,看名字差不多就能知道他們的意義了。
glBlendFunc 和 glBlendFuncSeparate 都是設置混合因子,反正就是這么個意思了。區別在於glBlendFunc是設置RGBA的混合因子,而glBlendFuncSeparate是分別設置RGB和Alpha的混合因子。設置混合因子是做什麼的呢?繼續看。
glBlendEquation 和 glBlendEquationSeparate 都是設置Blend的方程式,也就是設置混合的計算方式了,具體參數後面說。他們的區別在同 glBlendFunc 和 glBlendFuncSeparate 的區別一樣。
顏色、因子、方程式,組合起來就是:最終顏色=(目標顏色 目標因子)@(源顏色 源因子),其中@表示一種運算符。
至於glBlendColor則是在glBlendFunc和glBlendFuncSeparate的設置中,因子可以設置和常量相關的,這個常量就是由glBlendColor設置進去的。
glBlendFuncSeparate設置混合因子,參數及它們表示的主要如下,而glBlendFunc的參數也是這些,表示的意義就是RGB和A合並為RGBA就是了。在下表中,s0表示源,d表示目的,c表示有glBlendColor設置進來的常量。
glBlendEquationSeparate的設置混合操作,參數及其意義如下表所示。通過glBlendEquationSeparate或者glBlendEquation設置的方程中,源和目的顏色分別為(Rs,Gs,Bs,As)
和(Rd,Gd,Bd,Ad)。最終混合的顏色結果為(Rr,Gr,Br,Ar)。源和目的的混合因子分別為(sR,sG,sB,sA)和(dR,dG,dB,dA)
。其中,所有的顏色分量的取值范圍都為[ 0, 1 ]。 GL_MIN和GL_MAX是在OpenGLES3.0才有的 。
Mode RGB Components Alpha Component
GL_FUNC_ADD RrGrBr=sRRs+dRRd=sGGs+dGGd=sBBs+dBBd
Ar=sAAs+dAAd
GL_FUNC_SUBTRACT RrGrBr=sRRs−dRRd=sGGs−dGGd=sBBs−dBBd
Ar=sAAs−dAAd
GL_FUNC_REVERSE_SUBTRACT RrGrBr=dRRd−sRRs=dGGd−sGGs=dBBd−sBBs
Ar=dAAd−sAAs
GL_MIN RrGrBr=min(Rs,Rd)=min(Gs,Gd)=min(Bs,Bd)
Ar=min(As,Ad)
GL_MAX RrGrBr=max(Rs,Rd)=max(Gs,Gd)=max(Bs,Bd)
Ar=max(As,Ad)
目的紋理和源紋理使用的圖片分別如下所示(作為源的圖片為了表現混合效果,上中下三部分用了不一樣的透明度,最下面部分不透明):
根據公式推敲下渲染的結果:
F. 如何使用Android中的OpenGL ES媒體效果
設置OpenGLES環境創建GLSurfaceView為了顯示OpenGL的圖形,需要使用GLSurfaceView類,就像其他任何的View子類意義,可以將它添加到自己的Activity或Fragment之上,通過在布局xml文件中定義或者在代碼中創建實例。在這里,咱們使用GLSurfaceView作為唯一的View在咋們的Activity中,因此,為了簡便,咱在代碼中創建GLSurfaceView的實例並將其傳入setContentView中,這樣它將會填充自己的整個手機屏幕。Activity中的onCreate方法如下:protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);GLSurfaceViewview=newGLSurfaceView(this);setContentView(view);}因為媒體效果的框架僅僅支持OpenGLES2.0及以上的版本,所以在setEGLContextClientVersion方法中傳入2;view.setEGLContextClientVersion(2);為了確保GLSurfaceView僅僅在必要的時候進行渲染,咱們在setRenderMode方法中進行設置:view.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);創建RendererRenderer負責渲染GLSurfaceView中的內容。創建類實現介面GLSurfaceView.Renderer,在這里咱打算將這個類命名為EffectsRenderer,添加構造函數並覆寫介面中的抽象方法,如下:.Renderer{publicEffectsRenderer(Contextcontext){super();}@(GL10gl,EGLConfigconfig){}@(GL10gl,intwidth,intheight){}@OverridepublicvoidonDrawFrame(GL10gl){}}