LearnOpenGL - IntroductionLearn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners.https://learnopengl.com/Guest-Articles/2020/OIT/Introduction
这里主要是介绍了两类实现透明物体混合的方法。
顾名思义,这种混合方法就以物体为单位,先利用CPU对画面中的目标进行排序,然后从远往近逐个渲染目标,早期的3D游戏很多都是使用这种方式来实现混合。但如果画面中的物体数量非常庞大的时候,在每一帧都用CPU在渲染前先对所有对象做一次排序,渲染性能下降会很明显。同时,在自遮挡、互相遮挡等情况下,混合依然会出现问题。
下面这种情况就是以立方体为单位排序后按序渲染,当发生互相遮挡的情况时,结果就出错了。
为了提升混合速度和效果,就不能再用CPU排序的简单方法实现混合,这类不使用CPU排序混合的方法可以称作为OIT,顺序无关只是说不需要在渲染前用CPU排序了,倒不是意味着真的不考虑渲染的顺序了。又根据具体实现的方法可以分成两个大类,精准OIT和近似OIT。这里只实现以下精准OIT。
exact OIT通常实现是通过在GPU中对片元进行排序来准确计算正确的混合结果。排序阶段需要在着色器中使用相对较大的临时内存,而这些内存通常会被保守地分配到最大值。经典的exact OIT方法有Depth Peeling,Per Pixel Linked List。
Depth Peeling的介绍在这:OpenGL GLFW 深度/模版测试 面剔除 混合与帧缓冲-CSDN博客,效果如下。
参考:vulkan_顺序无关的半透明混合(OIT)_adaptive transparency opengl-CSDN博客
Depth Peeling中剥离几次就需要几个PASS,如果能够在一个PASS中直接准备好排序需要的所有内容,就可以节省掉后续几个PASS的性能消耗。Per Pixel Linked List通过静态链表实现在一个PASS中,为每个像素维护一个链表,再对每个像素的链表排序,以此正确混合颜色。
生成像素链表的片元着色器:
#version 430 core
layout(pixel_center_integer) in vec4 gl_FragCoord;
struct PixelNode {
vec4 rgba;
float depth;
int next;
float padding[2];
};
layout(std430, binding = 0) buffer PixelBuffer{
PixelNode node[];
};
layout(std430, binding = 1) buffer PixelCount{
int curCount;
int maxCount;
};
layout(std430, binding = 2) buffer StartIndexTexture{
int startIndexTexture[];
};
out vec4 FragColor;
uniform vec4 Color;
uniform int width;
uniform int height;
void main(){
int curIndex = atomicAdd(curCount, 1);
if(curIndex < maxCount){
int oldind = atomicExchange(startIndexTexture[int(gl_FragCoord.x) * height + int(gl_FragCoord.y)], curIndex);
node[curIndex].rgba = Color;
node[curIndex].depth = gl_FragCoord.z;
node[curIndex].next = oldind;
}
FragColor = vec4(1.0, 1.0, 1.0, 1.0);
}
生成像素链表的顶点着色器:
#version 430 core
layout(location = 0) in vec3 aPos;
layout(location = 1) in vec2 aUV;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main(){
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
排序像素链表并混合颜色输出的片元着色器:
#version 430 core
#define MAX_TREANSPARNTS_LAYER 128
layout(pixel_center_integer) in vec4 gl_FragCoord;
struct PixelNode {
vec4 rgba;
float depth;
int next;
float padding[2];
};
in vec2 oTexture;
out vec4 FragColor;
uniform sampler2D colorTexture;
uniform sampler2D depthTexture;
uniform int width;
uniform int height;
layout(std430, binding = 0) buffer PixelBuffer{
PixelNode node[];
};
layout(std430, binding = 2) buffer StartIndexTexture{
int startIndexTexture[];
};
void main(){
vec4 color = texture(colorTexture, oTexture);
float depth = texture(depthTexture, oTexture).x;
int ind = startIndexTexture[int(gl_FragCoord.x) * height + int(gl_FragCoord.y)];
int plength = 0;
if(ind != -1){
PixelNode fragment[MAX_TREANSPARNTS_LAYER];
while(ind != -1){
PixelNode cNode = node[ind];
ind = cNode.next;
plength += 1;
for(int i = 0; i < plength; ++i){
if(i == plength - 1) fragment[i] = cNode;
else if(fragment[i].depth < cNode.depth){
for(int j = plength - 1; j > i; --j){
fragment[j] = fragment[j - 1];
}
fragment[i] = cNode;
break;
}
}
}
for(int i = 0; i < plength; ++i){
if(fragment[i].depth >= depth) continue;
color = mix(color, fragment[i].rgba, fragment[i].rgba.a);
}
}
FragColor = vec4(color.rgb, 1.0);
}
排序像素链表并混合颜色输出的顶点着色器:
#version 430 core
layout(location = 0) in vec2 aPos;
layout(location = 1) in vec2 aTexture;
out vec2 oTexture;
void main(){
oTexture = aTexture;
gl_Position = vec4(aPos, 0.0, 1.0);
}
附上Per-Pixel Linked List的渲染效果:
教训:因为一个uniform值没传进去,导致结果出大问题,还误以为是自己的程序处理的不对。