专栏首页字节流动OpenGL ES 如何传输一个大数组到着色器程序?

OpenGL ES 如何传输一个大数组到着色器程序?

如何传输一个超大数组给着色器程序?

在 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/

-- END --

文章分享自微信公众号:
字节流动

本文参与 腾讯云自媒体分享计划 ,欢迎热爱写作的你一起参与!

作者:字节流动
原始发表时间:2022-05-06
如有侵权,请联系 cloudcommunity@tencent.com 删除。
登录 后参与评论
0 条评论

相关文章

  • OpenGL ES 传输一个大数组给着色器有哪些方式?

    在 OpenGL ES 图形图像处理中,会经常遇到一种情况:如何将一个超大的数组传给着色器程序?

    字节流动
  • 熟悉 OpenGL VAO、VBO、FBO、PBO 等对象,看这一篇就够了

    VBO(Vertex Buffer Object)是指顶点缓冲区对象,而 EBO(Element Buffer Object)是指图元索引缓冲区对象,VAO 和...

    字节流动
  • 面试中经常被问到的 OpenGL ES 对象,你知道的有哪些?

    VBO(Vertex Buffer Object)是指顶点缓冲区对象,而 EBO(Element Buffer Object)是指图元索引缓冲区对象,VBO 和...

    字节流动
  • OpenGL ES 对象

    VBO(Vertex Buffer Object)是指顶点缓冲区对象,而 EBO(Element Buffer Object)是指图元索引缓冲区对象,VAO

    ruochen
  • OpenGL ES编程指南(四)

    用于可视化OpenGL ES设计的两个方面:作为客户端 - 服务器体系结构和作为管道。 这两种观点都可以用于规划和评估应用程序的体系结构。

    Helloted
  • OpenGL ES _ 着色器_语法

    OpenGL ES _ 入门_01 OpenGL ES _ 入门_02 OpenGL ES _ 入门_03 OpenGL ES _ 入门_04 Open...

    酷走天涯
  • Android openGl 绘制简单图形的实现示例

    学习五部曲,弄清楚5个W一个H(when(什么时候使用)、where(在哪个地方使用?)、who(对谁使用)、what(是个什么东西)、why(为什么要这么用?...

    砸漏
  • OpenGL ES初探:渲染流程及GLKit简介

    OpenGL是一套多功能开放标准库,用于处理可视化2D和3D数据。OpenGL可以将调用函数转换成图形处理命令并传送给底层图形硬件,因此OpenGL的绘制效率非...

    CC老师
  • OpenGL ES 3.0 | 围绕HelloTriangle实战案例 展开 渲染流程分析

    凌川江雪
  • OpenGL ES for Android 世界

    大家好,本文是 iOS/Android 音视频专题的第五篇,该专题中 AVPlayer 项目代码将在 Github 进行托管,你可在微信公众号(GeekDev)...

    100001509164
  • OpenGL入门

    笔者最近在写安卓端OpenGL ES采集渲染摄像头的功能,恶补了一下OpenGL的相关知识,本篇权当记录。

    ppchao
  • NDK OpenGLES 3.0 开发(一):绘制一个三角形

    OpenGLES 全称 OpenGL for Embedded Systems ,是三维图形应用程序接口 OpenGL 的子集,本质上是一个跨编程语言、跨平台的...

    字节流动
  • OpenGL ES _ 着色器_程序

    OpenGL ES _ 入门_01 OpenGL ES _ 入门_02 OpenGL ES _ 入门_03 OpenGL ES _ 入门_04 Open...

    酷走天涯
  • OpenGL ES 3.0 | 统一变量和属性的概念与(在程序中的)获取流程、统一变量缓冲区对象详解、std140块规范、用 命名统一变量块 建立 统一变量缓冲区对象 的流程 和 相关API 和...

    程序示例, 说明如何用前面描述的命名统一变量块LightTransform【std140例程处】 建立一个统一变量缓冲区对象: 【思路: 块与自定义绑定...

    凌川江雪
  • 【前端可视化】 OpenGL / WebGL 入门和实践

    OpenGL 是一套规范,不是接口,学习这套规范,就可以在支持 OpenGL 的机器上正常使用这些规范,在显示器上看到绘制的结果。

    ConardLi
  • OpenGL ES 2.0 (iOS)[01]: 一步从一个小三角开始

    1). 三个什么端点(屏幕坐标点)? 要回答这个问题要先了解 OpenGL ES 的坐标系在屏幕上是怎样分布的:

    半纸渊
  • 用OpenGL构建粒子喷泉

    felix
  • OpenGL入门

    笔者最近在写安卓端OpenGL ES采集渲染摄像头的功能,恶补了一下OpenGL的相关知识,本篇权当记录。

    ppchao
  • 【iOS】OpenGL入门资料整理

    在应用程序调用任何OpenGL执行之前,首先需要创建一个OpenGL的上下文。这个上下文是一个非常庞大的状态机,保存了OpenGL中的各种状态,这也是OpenG...

    MapleYe

扫码关注腾讯云开发者

领取腾讯云代金券