天文常识OpenGL图像的加载和存储
之前提到过OGL中纹理缓存是作为输⼊缓存存在的,这使得输⼊缓存能够读取但是不能够改写。为了突破这⼀限制,在OGL中提出了
Image的概念,这使得程序员能够有效的操作texture缓存——对背后的texture缓存进⾏读写操作。但是,这样的操作打破了原有的
pipeline流⽔线,使得原本应该被OGL⾃⾝进⾏管理的缓存需要程序员⾃⾝来进⾏处理,因此提供了⽅便的同时也增加了程序员的编程负
担。
为了对texture缓存进⾏写操作,需要将对应的纹理数据给绑定到相应的image uint中,需要注意的是image uint和texture uint是两个不
同的区域,因此两者的ID号码计数是分离开的。由于image图像是和texture纹理绑定的,因此image的类型和texture的图像类型类似,其
次每⼀个image⾥都有⼀个对应的存储类型,这个存储类型和image在内存中的分割以及图像像素的格式是相关的。
为了加载image中的数据,需要利⽤imageLoad内置函数,该函数需要两个参数,对应的输⼊图像以及相应的坐标信息,该函数会读取对
应坐标处的图像的像素值。反过来,imageStore函数则会将相应的数据存⼊到对应的图像的对应的坐标位置处。下⾯是⼀个利⽤image进
⾏存储和读取的实例。
⾸先创建两个纹理缓存,其中image_palette_buffer作为背后的缓存提供存储⽀持,该函数会分配相应的内存,但是并不进⾏数据的初始
化,同时也不会将这个纹理缓存绑定到对应的texture uint中,真正被绑定到OGL管理的纹理缓存的数据是image_palette_texture。
glGenBuffers(1, &image_palette_buffer);//创建调⾊板缓存(提供内存)
glBindBuffer(GL_TEXTURE_BUFFER, image_palette_buffer);
glBufferData(GL_TEXTURE_BUFFER, 256 * 4 * sizeof(float), NULL, GL_STATIC_DRAW);
glGenTextures(1, &image_palette_texture);//创建调⾊板纹理,⽤于和⽚元着⾊器交互
glBindTexture(GL_TEXTURE_BUFFER, image_palette_texture);
glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, image_palette_buffer);
vmath::vec4 * data = (vmath::vec4 *)glMapBuffer(GL_TEXTURE_BUFFER, GL_WRITE_ONLY);
for (int i = 0; i < 256; i++)
{
data[i] = vmath::vec4((float)i);
}
glUnmapBuffer(GL_TEXTURE_BUFFER);
⾸先调⽤纹理激活函数切换纹理缓存,同时创建新的纹理对象output_texture,这个对象作为输出图像来接收输出的数据。同时设置对应的
纹理参数,紧接着将其绑定到第0号图像单元上。注意这⾥的第0号图像单元和第0号纹理单元不是同⼀个对象。glBindImageTexture函数
的第⼀个参数⽤于指定image单元的索引号,第⼆个参数⽤于指定纹理的ID号,第三个参数⽤于指定纹理的层次,倒数第⼆个参数⽤于指定
图像的读写属性,最后⼀个则指定图像的存储格式,这⾥是每⼀个像素的颜⾊通道是32位的浮点数,每⼀个像素包括四个颜⾊通道RGB以
及透明度。
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &output_texture);//2D_TEXTURE,⽤作输出缓存
glBindTexture(GL_TEXTURE_2D, output_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, MAX_FRAMEBUFFER_WIDTH, MAX_FRAMEBUFFER_HEIGHT, 0, GL_RGBA, GL_FLOAT, NULL); glBindTexture(GL_TEXTURE_2D, 0);
glBindImageTexture(0, output_texture, 0, GL_TRUE, 0, GL_READ_WRITE, GL_RGBA32F);
为了更⽅便快捷的清除texture缓存中的数据,引⼊⼀个新的概念pixel pack object,该对象管理⼀个像素相关的缓存区域,其解压的端⼝
和纹理缓存或者frame缓存关联,反之当frame缓存和纹理缓存进⾏打包处理时,则会传递数据到pack object。下⾯的例⼦中创建⼀个PIXEL_UNPACK_BUFFER在稍后的处理中将这个缓存中的数据可以上传到纹理缓存中,达到清除纹理缓存中数据的效果。
glGenBuffers(1, &output_texture_clear_buffer);//解压出来的数据会提交给texture buffer
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, output_texture_clear_buffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER, MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT * sizeof(GLuint), NULL, GL_STATIC_DRAW);
data = (vmath::vec4 *)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
memt(data, 0x00, MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT * sizeof(GLuint));
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
利⽤图像进⾏纹理写操作,⾸先将调⾊板纹理绑定点到纹理缓存中,作为输⼊图像颜⾊的初始数据。同时将输出缓存数据进⾏清空,主要是
利⽤之前准备好的pack object实现。
//将调⾊板纹理和图像绑定作为输⼊颜⾊的初始数据有关时间的诗句
glBindImageTexture(0, image_palette_texture, 0, GL_FALSE, 0, GL_READ_ONLY, GL_RGBA32F);
//将输出缓存清零
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, output_texture_clear_buffer);
glBindTexture(GL_TEXTURE_2D, output_texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, current_width, current_height, GL_RGBA, GL_FLOAT, NULL);
glBindTexture(GL_TEXTURE_2D, 0);
//绑定输出缓存作为输出,
glBindImageTexture(1, output_texture, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
在这⾥需要注意的是glBindImageTexture函数的第⼀个参数分别是0和1,和glsl语⾔中对应的图像对应。GLSL语⾔中利⽤binding指定绑
定的点分别是0和1。在GLSL程序中,⾸先从给定的颜⾊纹理中读取颜⾊值,读取的索引根据给定的图元索引计算得到。第⼆步则将读取到
的颜⾊值写到2D纹理中,这个纹理和output_texture绑定。GLSL程序中利⽤imageStore内置函数将颜⾊值写⼊到纹理缓存中。在稍后的
处理中,只需要绑定output_texture就可以将其作为输⼊,同时利⽤output_texture进⾏⽚元。
#version 430 core
layout (binding = 0, rgba32f) uniform imageBuffer colors;//和texture buffer关联
layout (binding = 1, rgba32f) uniform image2D output_buffer;//2D_texture
out vec4 color;
void main(void)
{//从⼀个图像缓存中读取数据,数据的索引根据图元的ID号码决定
vec4 col = imageLoad(colors, gl_PrimitiveID & 255);
棉花糖制作imageStore(output_buffer, ivec2() - ivec2(200, 0), col);
imageStore(output_buffer, ivec2() + ivec2(200, 0), col);
}
由于image的出现打破了pipeline的处理流程,因此需要程序员⾃⼰进⾏内存访问正确性的保证。OGL也提供相应的函数来保证对内存进⾏
互斥访问,从⽽保证访问内存的正确性。主要是利⽤类似于OS层次的原⼦操作了外接的函数对这样的。⽐如利⽤
初一语文课文imageAtomicAdd(output_buffer, ivec2(), 1);函数进⾏原⼦加操作。
image最主要的⼀个应⽤层场景是可以创建OIT(顺序⽆关透明性),其主要实现原理是利⽤image缓存获取texture的中间输出,然后将其
数据暂存起来,并在适当的时候进⾏排序,从⽽保证最终的渲染结果与原始的顺序⽆关。在红宝书上提供了⼀种顺序⽆关透明性的实现,其
主要思想是利⽤链表暂存图像和当前像素相重叠的像素值,然后在需要渲染的时候将其进⾏排序处理从⽽保证每次渲染都与顺序⽆关。
GLSL中第⼀步开启⽚元测试⽤于清除⽆效的⽚元数据,并且初始化原⼦计数器的数值为0。在GLSL中⾸先利⽤原⼦操作累加计数器的数
值,并将其放⼊到链表的头指针中,链表是以数组的形式存在于图像缓存中。利⽤exchange操作返回原来保存的数据,从⽽可以得到上⼀
个保存的索引数值。接下来利⽤光照位置以及顶点位置计算关照对像素颜⾊的影响。同时将计算得到的数据存储在图像list_buffer中,存放
格式是x存放上下⼀个指针的索引,y则存放颜⾊信息,z则存放当前索引的深度数值,⽽w则存放⼀些
封建时代班主任寄语小学layout (early_fragment_tests) in;
layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
layout (binding = 1, rgba32ui) uniform writeonly uimageBuffer list_buffer;
layout (binding = 0, offt = 0) uniform atomic_uint list_counter;
layout (location = 0) out vec4 color;
in vec3 frag_position;
in vec3 frag_normal;
in vec4 surface_color;
uniform vec3 light_position = vec3(40.0, 20.0, 100.0);
void main(void)
{
uint index;
大学生毕业总结uint old_head;
uvec4 item;
vec4 frag_color;
index = atomicCounterIncrement(list_counter);
old_head = imageAtomicExchange(head_pointer_image, ivec2(), uint(index));
vec3 L = normalize(light_position - frag_position);
vec3 V = normalize(-frag_position);
vec3 N = normalize(frag_normal);
vec3 H = normalize(L + V);
float NdotL = dot(N, L);
教育改革心得体会
float NdotH = dot(N, H);
vec4 modulator = vec4(b * abs(NdotL), surface_color.a);
vec4 additive_component = mix(surface_color, vec4(1.0), 0.6) * vec4(pow(clamp(NdotH, 0.0, 1.0), 26.0)) * 0.7;
item.x = old_head;
item.y = packUnorm4x8(modulator);
item.z = floatBitsToUint(gl_FragCoord.z);
item.w = packUnorm4x8(additive_component);
imageStore(list_buffer, int(index), item);
frag_color = modulator;
color = frag_color;
}
在真正渲染的时候根据上⾯得到的链表利⽤存储的深度数值进⾏排序,从⽽有有效的引导颜⾊的组合,保证了最终的渲染结果与混合的结果⽆关。
layout (binding = 0, r32ui) uniform uimage2D head_pointer_image;
layout (binding = 1, rgba32ui) uniform uimageBuffer list_buffer;
layout (location = 0) out vec4 color;
#define MAX_FRAGMENTS 40
uvec4 fragment_list[MAX_FRAGMENTS];
void main(void)
{
uint current_index;
uint fragment_count = 0;
current_index = imageLoad(head_pointer_image, ivec2(gl_FragCoord).xy).x;
while (current_index != 0 && fragment_count < MAX_FRAGMENTS)
{
uvec4 fragment = imageLoad(list_buffer, int(current_index));
fragment_list[fragment_count] = fragment;
current_index = fragment.x;
fragment_count++;
}
uint i, j;
if (fragment_count > 1)
{
for (i = 0; i < fragment_count - 1; i++)
{
for (j = i + 1; j < fragment_count; j++)
{
uvec4 fragment1 = fragment_list[i];
uvec4 fragment2 = fragment_list[j];
float depth1 = uintBitsToFloat(fragment1.z);
float depth2 = uintBitsToFloat(fragment2.z);
if (depth1 < depth2)
{//根据深度从⼤到⼩排序⽚元
fragment_list[i] = fragment2;
fragment_list[j] = fragment1;
}
}
}
}
vec4 final_color = vec4(0.0);
for (i = 0; i < fragment_count; i++)
{
vec4 modulator = unpackUnorm4x8(fragment_list[i].y);
vec4 additive_component = unpackUnorm4x8(fragment_list[i].w);
final_color = mix(final_color, modulator, modulator.a) + additive_component; }
color = final_color;
}