感谢 @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 = 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/
-- END --
进技术交流群,扫码添加个人微信:Byte-Flow
获取视频教程和源码
推荐:
FFmpeg + OpenGL ES 实现 3D 全景播放器
以为不错,点个在看呗~

本文分享自微信公众号 - 字节流动(google_developer)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。