OpenGL ES 如何傳輸一個超大數組給着色器程序?

感謝 @ShaderJoy 大佬對本文提供的支持web


如何傳輸一個超大數組給着色器程序?
數組


在 OpenGL ES 圖形圖像處理中,會常常遇到一種狀況:如何將一個超大的數組傳給着色器程序?微信


目前經常使用的有三種方式:app


  • 使用將數組加載到 2D 紋理的方式,而後使用 texelFetch 取數據;編輯器

  • 使用 uniform 緩衝區對象,即 UBO ;函數

  • 使用紋理緩衝區對象,即 TBO 。學習


將數組加載到紋理


使用將數組加載到紋理的方式來傳輸大數組,是最容易想到的一種方式。flex


要想精確地換取每一個像素的值,這個時候就不能使用採樣函數 texture ,由於採樣函數會涉及歸一化、過濾以及插值等複雜操做,基本沒法獲得某一確切像素的值ui


這個時候就須要使用紋素獲取函數 texlFetch ,texlFetch 是 OpenGL ES 3.0 引入的 API ,它將紋理視爲圖像,能夠精確訪問像素的內容,咱們能夠類比經過索引來獲取數組某個元素的值。google


vec4 texelFetch(sampler2D sampler, ivec2 P, int lod);

vec4 texelFetch(sampler3D sampler, ivec3 P, int lod);

vec4 texelFetch(samplerBuffer sampler, int P);


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 類型變量的最大支持數量。


int maxVertexUniform, 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 個矩陣。


#version 310 es
layout(location = 0in vec4 a_position;
layout(location = 1in vec2 a_texCoord;

layout (std140) uniform MVPMatrix
{
    mat4 projection;
    mat4 view;
    mat4 model;
};

out vec2 v_texCoord;
void main()
{
    gl_Position = projection * view * model * a_position;
    v_texCoord = a_texCoord;
}


設置 uniform 塊的綁定點爲 0 ,生成一個 UBO 。


GLuint uniformBlockIndex = 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);

//定義綁定點爲 0 buffer 的範圍
glBindBufferRange(GL_UNIFORM_BUFFER, 0, m_UboId, 03 * 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 紋理同樣。


//生成一個 Buffer Texture 
glGenTextures(1, &m_TboTexId);

float *bigData = new float[BIG_DATA_SIZE];
for (int i = 0; i < BIG_DATA_SIZE; ++i) {
    bigData[i] = i * 1.0f;
}

//生成一個 TBO ,並將一個大的數組上傳至 TBO 
glGenBuffers(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 。


#version 320 es
#extension GL_EXT_texture_buffer : require
in mediump vec2 v_texCoord;
layout(location = 0out mediump  vec4 outColor;
uniform mediump samplerBuffer u_buffer_tex;
uniform mediump sampler2D u_2d_texture;
uniform mediump int u_BufferSize;
void main()
{
    mediump int index = int((v_texCoord.x +v_texCoord.y) /2.0 * float(u_BufferSize - 1));
    mediump float value = texelFetch(u_buffer_tex, index).x;
    mediump vec4 lightColor = 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_Texture
https://www.khronos.org/registry/OpenGL-Refpages/es3/

-- END --


進技術交流羣,掃碼添加個人微信:Byte-Flow



獲取視頻教程和源碼



推薦:

字節流動 OpenGL ES 技術交流羣來啦

Android OpenGL 渲染圖像讀取哪家強?

FFmpeg + OpenGL ES 實現 3D 全景播放器

一文掌握 YUV 圖像的基本處理

Android OpenGL ES 從入門到精通系統性學習教程

OpenGL ES 實現動態(水波紋)漣漪效果


以爲不錯,點個在看唄~

本文分享自微信公衆號 - 字節流動(google_developer)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索