作者:位元組流動
來源:
https://blog.csdn.net/Kennethdroid/article/details/109749018如何傳輸一個超大數組給着色器程式?
在 OpenGL ES 圖形圖像進行中,會經常遇到一種情況:将一個超大的數組傳給着色器程式。
目前常用的有三種方式:
- 使用将數組加載到 2D 紋理的方式,然後使用 texelFetch 取資料;
- 使用 uniform 緩沖區對象,即 UBO ;
- 使用紋理緩沖區對象,即 TBO 。
将數組加載到紋理
使用将數組加載到紋理的方式來傳輸大數組,是最容易想到的一種方式。
要想精确地換取每個像素的值,這個時候就不能使用采樣函數 texture ,因為采樣函數會涉及歸一化、過濾以及插值等複雜操作,基本無法得到某一确切像素的值。
這個時候就需要使用紋素擷取函數 texlFetch ,texlFetch 是 OpenGL ES 3.0 引入的 API ,它将紋理視為圖像,可以精确通路像素的内容,我們可以類比通過索引來擷取數組某個元素的值。
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 = 0) in vec4 a_position;
layout(location = 1) in 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, 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 紋理一樣。
//生成一個 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 = 0) out 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/聯系與交流
技術交流擷取源碼可以添加我的微信:Byte-Flow ,拉你進 OpenGL ES 技術交流群。
「視訊雲技術」你最值得關注的音視訊技術公衆号,每周推送來自阿裡雲一線的實踐技術文章,在這裡與音視訊領域一流工程師交流切磋。