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. CodeForces 1110G. Tree-Tac-Toe

    题目简述:给定$n$个节点的树,其中一些节点被染成了白色(其余节点未染色).黑白双方博弈,白先动.轮到黑(白)方时,选择树上的一个未染色的节点并将其染成黑(白)色.率先达成三连色(即存在三个节点$a, ...

  2. msql 初识数据库

    一 数据库管理软件的由来 基于我们之前所学,数据要想永久保存,都是保存于文件中, 毫无疑问, 一个文件仅仅只能存在于某一台机器上. 如果我们暂且忽略直接基于文件来存取数据的效率问题, 并且假设程序所有 ...

  3. HDU - 4535 ZZULI 1867: 礼上往来【错位排序】

    1867: 礼上往来 Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 216  Solved: 65 SubmitStatusWeb Board Desc ...

  4. 洛谷 - P1072 Hankson - 的趣味题 - 质因数分解

    https://www.luogu.org/problemnew/show/P1072 一开始看了一看居然还想放弃了的. 把 \(x,a_0,a_1,b_0,b_1\) 质因数分解. 例如 \(x=p ...

  5. HTTP请求信息和响应信息的格式

    请求: (1)请求行信息 Request URL: 发起请求的路径 Request Method:请求的方式(get  post  head put  delete  trace  options等) ...

  6. UIWebView 使用

    UIWebView是iOS sdk中一个最常用的控件.是内置的浏览器控件,我们可以用它来浏览网页.打开文档等等.这篇文章我将使用这个控件,做一个简易的浏览器.如下图: 我们创建一个Window-bas ...

  7. .netcore--Controller后台实现企业微信发送消息

    一.获得企业微信管理端权限,登录企业企业微信管理端界面,并创建应用,如下图中的[网站消息推送] 二.参见企业微信API文献,根据corpid=ID&corpsecret=SECRET(其中企业 ...

  8. 转 Vlan

    1.支持VLAN的交换机一定是三层交换机吗?2.Trunk配置了就可以VLAN间通信吗?3.Trunk具体怎么工作的?4.VLAN间的通信到底是怎么执行的?如果说给若干个纯二层环境加上若干个路由器,我 ...

  9. C# 获取当前ip

    1.获取局域网ip IPAddress ipAddr = Dns.Resolve(Dns.GetHostName()).AddressList[0];//获得当前IP地址 string ip=ipAd ...

  10. Codeforces Round #542(Div. 2) A.Be Positive

    链接:https://codeforces.com/contest/1130/problem/A 题意: 给n个数,找出一个非0整数d,使所有n个数除以整数d后,数组中正数的数量>= n/2. ...