OpenGL图像数据操作

  之前的实践中,我们在着色器中的输入输出都是比较固定的。比如在顶点或片元着色器中,顶点属性的输入和帧缓存的颜色值;虽然我们可以通过纹理或者纹理缓存对象(TBO)来读取任意的内存区域,不过总体上来说写入的时机是固定的,也是可以预知的。比如我们在固定的阶段通过transform feedback操作来获取顶点数据并传递到transform feedback缓存中,也可以根据光栅化阶段的标准样式,将片元着色器产生的像素写入帧缓存中。

  在这次实践中我们将使用一种允许用户在指定的位置进行读写操作的机制,着色器可以因此在内存中建立数据结构,然后谨慎地更新同一块内存位置,完成彼此之间的各级通信过程。首先我们介绍使用纹理存储通用数据。

使用纹理存储通用数据

  我们可以使用内存来表示一个缓存对象,或则一个单一层级的纹理对象,并且在着色器中进行通用目的的读写操作。在OpenGL中提供一些特别的图像类型来支持这一需求,它们主要用来表达未编码的图像数据。图像类型和采样器(sampler)非常的类似,比如采样器类型sampler2D,对应的图像类型image2D或iimage2D,在着色器中也是使用uniform定义,但它们并不相同:首先,图像类型表达的是单一层级的纹理,不是完整的mipmap;其次,图像类型不支持滤波等采样操作。注意不支持的操作还包括深度比较(depth comparison),所以阴影类型的采样器并没有对应的图像类型。下面我们列了一些常用的图像类型:

图像类型 意义
image1D 1D浮点数类型
image2D 2D浮点数类型
image3D 3D浮点数类型
imageCube 浮点数Cube map数组类型

imageBuffer

浮点数缓存类型

iimage1D

1D有符号整数类型
iimage2D 2D有符号整数类型
iimage3D 3D有符号整数类型
iimageBuffer 有符号整数缓存类型
uimage1D 1D无符号整数类型
uimage2D 2D无符号整数类型
uimage3D 3D无符号整数类型
uimageCube 无符号整数Cube map数组类型
uimageBuffer 无符号缓存类型

  这些图像类型定义的是通用的数据类型,我们还需要一个format的限定符来设置数据在内存中的图像格式。下表中将列举常用的一些图像格式限定符。

图像类型 OpenGL内部格式
rgba32f GL_RGBA32F
rgba16f GL_RGBA16F
rg32f GL_RG32F
rg16f GL_RG16F
r32f GL_R32F
r16f GL_R16F
rgba32i GL_RGBA32I
rgba16i GL_RGBA16I
rgba8i GL_RGBA8I
rgba32ui GL_RGBA32UI
rgba16ui GL_RGBA16UI
rgba8ui GL_RGBA8UI

  图像的format限定符是作为图像变量声明的一部分提供的,并且必须在声明一个用来读取图像的变量的时候使用。如果图像变量只用来写入,那么我们也可以忽略这个限定符。注意,我们在使用图像限定符的时候一定要和图像本身的基本数据类型相匹配。比如,image2D必须使用浮点类型的限定符,如r32f或者rgba16_unorm,而非浮点型的限定符rg8ui是不行的。图像类型在着色器中的声明示例如下:

layout(binding=,rgba32f) uniform image2D image1;

  其中bingding表示图像单元的索引,类似采样器的位置(如GL_TEXTURE0),也可以使用glUniform1i来设置,默认为0,如果只使用一个图像就不需要显示使用glUniform1i设置。rgba32f即图像的format。

  下面的例子我们将实现在glsl中存取图像数据:

static const GLfloat cData[] = {
1.0,0.0,0.0,1.0, 0.0,1.0,0.0,1.0,
0.0,0.0,1.0,1.0, 1.0,1.0,0.0,1.0
};
glGenTextures(,&tex);
glBindTexture(GL_TEXTURE_2D,tex);
glTexStorage2D(GL_TEXTURE_2D,,GL_RGBA32F,,);
glTexSubImage2D(GL_TEXTURE_2D,
,
,,
,,
GL_RGBA,GL_FLOAT,
cData); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR); //输出图像
glGenTextures(,&otex);
glBindTexture(GL_TEXTURE_2D,otex);
glTexStorage2D(GL_TEXTURE_2D,,GL_RGBA32F,,);
glBindTexture(GL_TEXTURE_2D,); glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_S,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_WRAP_T,GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST); glBindTexture(GL_TEXTURE_2D,);

  首先生成两张图片,一张用于输入,一张用于输出。生成的过程于普通的纹理生成过程一样,需要注意的是纹理的格式,即format,在这里,我们使用的是rgba32f,这个要于着色其中的格式相对应。

  接下来是使用这两张纹理:

         glClear(GL_COLOR_BUFFER_BIT);

         dShader->Use();
glBindImageTexture(,tex,,GL_FALSE,,GL_READ_ONLY,GL_RGBA32F);
glBindImageTexture(,otex,,GL_FALSE,,GL_WRITE_ONLY,GL_RGBA32F);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES,,);
//glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); tShader->Use();
glBindTexture(GL_TEXTURE_2D,otex);
glBindVertexArray(vao);
glDrawArrays(GL_TRIANGLES,,);

  重点是glBindImageTexture这个函数,函数原型:

  void glBindImageTexture(GLuint unit,GLuint texture,GLint level, GLboolear layered,GLint layer, GLenum access, GLenum format);

  函数意义是将level层的纹理绑定到unit的图像单元。如果texture是1D或者2D的纹理数组,且layered为GL_TRUE,则绑定整个数组,而layered为GL_FALSE则只绑定layer层。access可以有三种选择:GL_READ_ONLY,GL_WRITE_ONLY,GL_READ_WRITE,表示的是对图像的访问方式,最后一个format即图像数据在内存中的格式,于texture声明的格式要兼容。

  所以上述代码的表示将tex与0图像单元绑定,用于读取数据,otex与1图像单元绑定,用于写入数据,最后再用otex渲染。

  下面为顶点和片元着色器代码:

#version  core

layout(location=) in vec3 iPos;
layout(location=) in vec2 iTexcoord; uniform mat4 model;
uniform mat4 view;
uniform mat4 proj; out vec2 texcoords; void main()
{
texcoords = iTexcoord;
gl_Position = proj * view * model * vec4(iPos,);
}
#version  core

layout(binding=,rgba32f) uniform image2D colors;
layout(binding=,rgba32f) uniform image2D output_buffer; //uniform sampler2D image; in vec2 texcoords;
out vec4 color; void main()
{
//ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
ivec2 size = imageSize(output_buffer);
vec4 col = imageLoad(colors,ivec2(,));//ivec2(gl_FragCoord.xy));
imageStore(output_buffer,ivec2(size.x * texcoords.x, size.y * texcoords.y),vec4(texcoords,,));
//imageStore(output_buffer,ivec2(texcoords),vec4(1,1,0,1));
color = col;//vec4(1,0,0,1);
//color = texture(image,texcoords);
}

  顶点着色器没什么变化,在片元着色器中需要对于图像单元的进行读取和写入。在着色器中对图像进行操作需要借助着色器的内置函数:imageLoad,imageStore,imageSize。这三个函数有很多重载,分别对应不同的图像,比如我们这次针对image2D使用的:

  gvec4 imageLoad(readonly gimage2D image,ivec2 P);

  gvec4 imageStore(writeonly gimage2D image,ivec2 P, gvec4 data);

  ivec2 imageSize(gimage2D image);

  imageLoad用于从图像中读取数据,imageStore则是写,imageSize则返回图像的大小。需要注意的是P的类型是ivec2,表示坐标位置,是整形的,于我们常用的vec2浮点型不一样,这个是像素的实际位置,没有进行归一化,即表示第P.x行,P.y列的数据。在本例中我们使用imageSize和顶点的纹理坐标(已归一化)来定位。

  效果如图,背后的绿色物体使用从图像的(1,0)位置(整个它图像为2*2的,(1,0)位置为绿色)取的的数据,用于片元着色器输出,中间的矩形是使用着色器写入数据图像的图像渲染的,我们写入的数据是纹理坐标。

  

  

  源代码:https://github.com/xin-lover/opengl-learn/tree/master/chapter-15-memory

Linux OpenGL 实践篇-15-图像数据操作的更多相关文章

  1. Linux OpenGL 实践篇-10-framebuffer

    在之前的实践中我们都是在当前的窗口中渲染,即使用的缓存都是由glutCreateWindow时创建的缓存,我们可称之为默认缓存.它是唯一一个可以被图形服务器的显示系统识别的帧缓存,我们在屏幕上看到的只 ...

  2. Linux OpenGL 实践篇-5 纹理

    纹理 在之前的实践中,我们所渲染的物体的表面颜色都是纯色或者根据顶点位置计算出的一个颜色,这种方式在表现物体细节方面是比较吃资源的,因为我们每增加一个细节,我们就需要定义更多的顶点及其属性.所以美术人 ...

  3. Linux OpenGL 实践篇-3 绘制三角形

    本次实践是绘制两个三角形,重点理解顶点数组对象和OpenGL缓存的使用. 顶点数组对象 顶点数组对象负责管理一组顶点属性,顶点属性包括位置.法线.纹理坐标等. OpenGL缓存 OpenGL缓存实质上 ...

  4. Linux OpenGL 实践篇-16 文本绘制

    文本绘制 本文主要射击Freetype的入门理解和在OpenGL中实现文字的渲染. freetype freetype的官网,本文大部分内容参考https://www.freetype.org/fre ...

  5. Linux OpenGL 实践篇-6 光照

    经典光照模型 经典光照模型通过单独计算光源成分得到综合光照效果,然后添加到物体表面特定点,这些成分包括:环境光.漫反射光.镜面光. 环境光:是指不是来特定方向的光,在经典光照模型中基本是个常量. 漫反 ...

  6. Linux OpenGL 实践篇-14-多实例渲染

    多实例渲染 OpenGL的多实例渲染是一种连续执行多条相同的渲染命令的方法,并且每条命令产生的结果都有轻微的差异,通常用于渲染大量的几何物体. 设想一个场景,比如太空,我们需要渲染数以万记的星球,如果 ...

  7. Linux OpenGL 实践篇-12-procedural-texturing

    程序式纹理 简单的来说程序式纹理就是用数学公式描述物体表面的纹路 .而实现这个过程的着色器我们称之为程序纹理着色器,通常在这类着色器中我们能使用的输入信息也就是顶点坐标和纹理坐标. 程序式纹理的优点 ...

  8. Linux OpenGL 实践篇-11-shadow

    OpenGL 阴影 在三维场景中,为了使场景看起来更加的真实,通常需要为其添加阴影,OpenGL可以使用很多种技术实现阴影,其中有一种非常经典的实现是使用一种叫阴影贴图的实现,在本节中我们将使用阴影贴 ...

  9. Linux OpenGL 实践篇-9 模型

    之前一直渲染箱子,显得有点单调.这一次我们绘制一个用艺术家事先用建模工具创建的模型. 本次实践参考:https://learnopengl-cn.github.io/03%20Model%20Load ...

随机推荐

  1. java集合框架之Collection

    参考http://how2j.cn/k/collection/collection-collection/366.html Collection是 Set List Queue和 Deque的接口Qu ...

  2. sql server 2008 R2 升级与安装遇到的问题

    因工作需要,遂把以前的2008升级到r2,升级失败,具体原因忘了,卸载2008,清了注册表删了文件,结果安装的时候失败了,如下图: 下一步-有错误日志和错误的序列号,错误日志在C:\Program F ...

  3. 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之二 —— 准备

    ==== 目录 ==== 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之一 —— 开篇 跟我学: 使用 fireasy 搭建 asp.net core 项目系列之二 —— ...

  4. MFC中Radio Button使用方法

    先为对话框加上2个radio button,分别是Radio1和Radio2. 问题1:如何让Radio1或者Radio2默认选上?如何知道哪个被选上了? 关键是选上,“默认”只要放在OnInitDi ...

  5. VC.NET 需要注意的一些问题

    String^ abc = gcnew String(); String^ abc = nullptr override .net 类方法: void Reset() override {....} ...

  6. Codevs 1523 地精部落

    1523 地精部落 省队选拔赛  时间限制: 1 s  空间限制: 256000 KB  题目等级 : 大师 Master 题解       题目描述 Description 传说很久以前,大地上居住 ...

  7. Mol Cell Proteomics. |胡丹丹| 雷公藤红素通过SIRT1-FXR 信号通路保护胆汁淤积性肝损伤

    期刊:Mol Cell Proteomics 题目:Celastrol protects from cholestatic liver injury though modulation of SIRT ...

  8. 普通组件定义渲染和render渲染组件的区别(三)

    普通方式定义组件和效果: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> ...

  9. C 语言实例 - 删除字符串中的特殊字符

    C 语言实例 - 删除字符串中的特殊字符 C 语言实例 C 语言实例 删除字符串中的除字母外的字符. 实例 #include<stdio.h> int main() { ]; int i, ...

  10. struts2学习笔记 day02 获取参数 访问ServletAPI 结果类型