如何傳輸一個超大數組給着色器程序?
在 OpenGL ES 圖形圖像處理中,會經常遇到一種情況:如何將一個超大的數組傳給着色器程序?
目前常用的有三種方式:
使用將數組加載到 2D 紋理的方式,然後使用 texelFetch 取數據;
使用 uniform 緩衝區對象,即 UBO ;
使用紋理緩衝區對象,即 TBO 。
將數組加載到紋理使用將數組加載到紋理的方式來傳輸大數組,是最容易想到的一種方式。
要想精確地換取每個像素的值,這個時候就不能使用採樣函數 texture ,因為採樣函數會涉及歸一化、過濾以及插值等複雜操作,基本無法得到某一確切像素的值。
這個時候就需要使用紋素獲取函數 texlFetch ,texlFetch 是 OpenGL ES 3.0 引入的 API ,它將紋理視為圖像,可以精確訪問像素的內容,我們可以類比通過索引來獲取數組某個元素的值。
vec4texelFetch(sampler2Dsampler,ivec2P,intlod);vec4texelFetch(sampler3Dsampler,ivec3P,intlod);vec4texelFetch(samplerBuffersampler,intP);texelFetch 使用的是未歸一化的坐標直接訪問紋理中的紋素,不執行任何形式的過濾和插值操作,坐標範圍為實際載入紋理圖像的寬和高。
texelFetch 使用起來比較方便,在片段着色器中,下面 2 行代碼可以互換,但是最終的渲染結果會有細微差異,至於為什麼會有細微差異?你品,你細品!
gl_FragColor=texture(s_Texture,v_texCoord);gl_FragColor=texelFetch(s_Texture,ivec2(int(v_texCoord.x*imgWidth),int(v_texCoord.y*imgHeight)),0);使用 uniform 緩衝區對象我們經常使用 uniform 類型的變量,向着色器程序傳遞一些向量參與渲染運算。
但是 OpenGL ES 有一個對可使用 uniform 變量數量的限制,我們可以用 glGetIntegerv 函數來獲取 uniform 類型變量的最大支持數量。
intmaxVertexUniform,maxFragmentUniform;glGetIntegerv(GL_MAX_VERTEX_UNIFORM_COMPONENTS,&maxVertexUniform);glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_COMPONENTS,&maxFragmentUniform);目前主流的手機一般支持 1024 個 uniform 類型的變量(vector),使用大的數組時很容易突破這個限制,並且 uniform 變量也不好管理,需要你一次次地設置 uniform 變量。
那麼怎麼才能突破 uniform 變量數量的限制呢?
答案是使用 UBO (Uniform Buffer Object)。
UBO,顧名思義,就是一個裝載 uniform 變量數據的緩衝區對象,本質上跟 OpenGL ES 的其他緩衝區對象沒有區別,創建方式也大致一致,都是顯存上一塊用於儲存特定數據的區域。
當數據加載到 UBO ,那麼這些數據將存儲在 UBO 上,而不再交給着色器程序,所以它們不會占用着色器程序自身的 uniform 存儲空間,UBO 是一種新的從內存到顯存的數據傳遞方式,另外 UBO 一般需要與 uniform 塊配合使用。
本例將 MVP 變換矩陣設置為一個 uniform 塊,即我們後面創建的 UBO 中將保存 3 個矩陣。
#version310eslayout(location=0)invec4a_position;layout(location=1)invec2a_texCoord;layout(std140)uniformMVPMatrix{mat4projection;mat4view;mat4model;};outvec2v_texCoord;voidmain(){gl_Position=projection*view*model*a_position;v_texCoord=a_texCoord;}設置 uniform 塊的綁定點為 0 ,生成一個 UBO 。
GLuintuniformBlockIndex=glGetUniformBlockIndex(m_ProgramObj,"MVPMatrix");glUniformBlockBinding(m_ProgramObj,uniformBlockIndex,0);glGenBuffers(1,&m_UboId);glBindBuffer(GL_UNIFORM_BUFFER,m_UboId);glBufferData(GL_UNIFORM_BUFFER,3*sizeof(glm::mat4),nullptr,GL_STATIC_DRAW);glBindBuffer(GL_UNIFORM_BUFFER,0);//定義綁定點為0buffer的範圍glBindBufferRange(GL_UNIFORM_BUFFER,0,m_UboId,0,3*sizeof(glm::mat4));繪製的時候更新 Uniform Buffer 的數據,更新三個矩陣的數據,注意偏移量。
glBindBuffer(GL_UNIFORM_BUFFER,m_UboId);glBufferSubData(GL_UNIFORM_BUFFER,0,sizeof(glm::mat4),&m_ProjectionMatrix[0][0]);glBufferSubData(GL_UNIFORM_BUFFER,sizeof(glm::mat4),sizeof(glm::mat4),&m_ViewMatrix[0][0]);glBufferSubData(GL_UNIFORM_BUFFER,2*sizeof(glm::mat4),sizeof(glm::mat4),&m_ModelMatrix[0][0]);glBindBuffer(GL_UNIFORM_BUFFER,0);使用紋理緩衝區對象紋理緩衝區對象,即 TBO(Texture Buffer Object),是 OpenGL ES 3.2 引入的概念,因此在使用時首先要檢查 OpenGL ES 的版本,Android 方面需要保證 API >= 24 。
TBO 需要配合緩衝區紋理(Buffer Texture)一起使用,Buffer Texture 是一種一維紋理,其存儲數據來自紋理緩衝區對象(TBO),用於允許着色器訪問由緩衝區對象管理的大型內存表。
在 GLSL 中,只能使用 texelFetch 函數訪問緩衝區紋理,緩衝區紋理的採樣器類型為 samplerBuffer 。
生成一個 TBO 的方式跟 VBO 類似,只需要綁定到 GL_TEXTURE_BUFFER ,而生成緩衝區紋理的方式與普通的 2D 紋理一樣。
//生成一個BufferTextureglGenTextures(1,&m_TboTexId);float*bigData=newfloat[BIG_DATA_SIZE];for(inti=0;i<BIG_DATA_SIZE;++i){bigData[i]=i*1.0f;}//生成一個TBO,並將一個大的數組上傳至TBOglGenBuffers(1,&m_TboId);glBindBuffer(GL_TEXTURE_BUFFER,m_TboId);glBufferData(GL_TEXTURE_BUFFER,sizeof(float)*BIG_DATA_SIZE,bigData,GL_STATIC_DRAW);delete[]bigData;使用紋理緩衝區的片段着色器,需要引入擴展 texture buffer ,注意版本聲明為 #version 320 es 。
#version320es#extensionGL_EXT_texture_buffer:requireinmediumpvec2v_texCoord;layout(location=0)outmediumpvec4outColor;uniformmediumpsamplerBufferu_buffer_tex;uniformmediumpsampler2Du_2d_texture;uniformmediumpintu_BufferSize;voidmain(){mediumpintindex=int((v_texCoord.x+v_texCoord.y)/2.0*float(u_BufferSize-1));mediumpfloatvalue=texelFetch(u_buffer_tex,index).x;mediumpvec4lightColor=vec4(vec3(vec2(value/float(u_BufferSize-1)),0.0),1.0);outColor=texture(u_2d_texture,v_texCoord)*lightColor;}繪製時如何使用緩衝區紋理和 TBO ?
glActiveTexture(GL_TEXTURE0);glBindTexture(GL_TEXTURE_BUFFER,m_TboTexId);glTexBuffer(GL_TEXTURE_BUFFER,GL_R32F,m_TboId);GLUtils::setInt(m_ProgramObj,"u_buffer_tex",0);跟普通紋理的使用方式大致一樣,只不過需要使用 glTexBuffer 綁定 TBO 到緩衝區紋理。
本例,我們通過對緩衝區紋理進行取值,取值範圍是 [0~size-1] ,將取值結果進行歸一化,作為光照顏色疊加到 2D 紋理的採樣結果。
如上圖所示,這樣呈現出來的效果是,紋理坐標從左上角到右下角,色彩強度依次增強。
參考https://www.khronos.org/opengl/wiki/Buffer_Texturehttps://www.khronos.org/registry/OpenGL-Refpages/es3/
-- END --
進技術交流群,掃碼添加我的微信:Byte-Flow
獲取視頻教程和源碼
推薦:
字節流動 OpenGL ES 技術交流群來啦
Android OpenGL 渲染圖像讀取哪家強?
FFmpeg + OpenGL ES 實現 3D 全景播放器
一文掌握 YUV 圖像的基本處理
Android OpenGL ES 從入門到精通系統性學習教程OpenGL ES 實現動態(水波紋)漣漪效果