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. python之文件的读写(2)

    小R昨天因为在研究weblogic的漏洞就没来得及学习python(好吧,这都是借口,懒了,大家可不能像我这样.要坚持每天都学).   这个进度是有点慢呀.哎呀,没事没事,我还年轻,才20岁.  哈哈 ...

  2. Filezilla配置FTP中的坑以及出坑办法

    做本科生助教,老板让配置一个FTP传资料交作业,找了一台Windows服务器捣鼓,开始按网上教程自己配置特别麻烦,何西西说用Filezilla比较方便,就去Filezilla官网下载了Filezill ...

  3. 201621123016 《Java程序设计》第6周学习总结

    1. 本周学习总结 1.1 面向对象学习暂告一段落,请使用思维导图,以封装.继承.多态为核心概念画一张思维导图或相关笔记,对面向对象思想进行一个总结. 注1:关键词与内容不求多,但概念之间的联系要清晰 ...

  4. Maven使用阿里云公共仓库

    https://help.aliyun.com/document_detail/102512.html?spm=a2c40.aliyun_maven_repo.0.0.3618305449xZaK

  5. JavaScript代码风格和分号使用问题

    1.推荐代码风格 JavaScript Standard Style  规定相对松散更多人使用此规范 Airbnb JavaScript Style  规定更严格但也没毛病 2.JavaScript代 ...

  6. NEERC2017:L - Laminar Family

    传送门 很容易想到,离线按路径长度从大到小排个序,用树链剖分加颗支持区间cover的线段树就好了 代码: #include<cstdio> #include<iostream> ...

  7. hibernate添加帮助文档和源码

  8. 51Nod 1126 求递推序列的第N项(矩阵快速幂)

    #include <iostream> #include <algorithm> #include <cmath> #define MOD 7 #define N ...

  9. iOS开发 - RunLoop理解

    RunLoop概念 运行循环,一个 run loop 就是一个事件处理的循环,用来不停的调度工作以及处理事件 作用 保持程序的持续运行 监听处理App中的各种事件(触摸事件,定时器事件,selecto ...

  10. SpringBoot | 集成Redis

    Windows下安装: https://github.com/MicrosoftArchive/redis/releases zip下就解包到自定义目录下,msi就跟着步骤安装 进入安装目录下运行命令 ...