我正在尝试在OpenGL/GLSL中实现平铺延迟渲染,但我被困在光剔除上。
我的 GPU 有点旧(AMD Radeon 6490m),出于奇怪的原因,当在共享变量上调用内部原子操作时,计算着色器以无限循环运行,因此我无法使用计算着色器计算最小和最大深度。无论如何,这不是很耗时的操作,所以我在片段着色器中完成。
然后,对于每个可见点光源(在视图空间中),我计算屏幕空间边界四边形。现在,我想使用单计算着色器进行光照剔除和着色。问题是如上所述,我无法对共享变量使用原子操作,因此我无法构建瓷砖灯光列表并存储瓷砖的光计数。
问题是我找不到任何其他方法可以做到这一点。知道如何使用非原子学剔除和构建平铺灯光列表吗?
这是我的计算着色器的伪代码:
#version 430
#define MAX_LIGHTS 1024
#define TILE_SIZE 32
#define RX 1280
#define RY 720
struct Light {
vec4 position;
vec4 quad;
vec3 color;
float radius;
}
uint getTilesXCount(){
return uint(( RX + TILE_SIZE - 1) / TILE_SIZE);
}
uint getTilesYCount(){
return uint((RY + TILE_SIZE - 1) / TILE_SIZE);
}
layout (binding = 0, rgba16f) uniform readonly image2D minMaxTex;
layout (binding = 1, rgba16f) uniform readonly image2D diffTex;
layout (binding = 2, rgba16f) uniform readonly image2D specTex;
layout (std430, binding = 3) buffer pointLights {
Light Lights[];
};
//tile light list & light count
shared uint lightIDs[MAX_LIGHTS];
shared uint lightCount = 0;
uniform uint totalLightCount;
layout (local_size_x = TILE_SIZE, local_size_y = TILE_SIZE) in;
void main(void){
ivec2 pixel = ivec2(gl_GlobalInvocationID.xy);
vec2 tile = vec2(gl_WorkGroupID.xy * gl_WorkGroupSize.xy) / vec2(1280, 720);
//get minimum & maximum depth for tile
vec2 minMax = imageLoad(minMax, tile).xy;
uint threadCount = TILE_SIZE * TILE_SIZE;
uint passCount = (totalLightCount + threadCount - 1) / threadCount;
for(uint i = 0; i < passCount; i++){
uint lightIndex = passIt * threadCount + gl_LocalInvocationIndex;
// prevent overrun by clamping to a last ”null” light
lightIndex = min(lightIndex, numActiveLights);
Light l = pointLights[lightIndex];
if(testLightBounds(pixel, l.quad)){
if ((minMax.y < (l.position.z + l.radius))
&&
(minMax.x > (l.position.z - l.radius))){
uint index;
index = atomicAdd(lightCount, 1);
pointLightIndex[index] = lightIndex;
}
}
}
barrier();
//do lighting for actual tile
color = doLight();
imageStore(out, pos, color);
}
我还没有真正实现平铺延迟,但我认为你可以用类似于为模拟构建粒子相邻列表的方式来解决这个问题。
- 让计算着色器生成一个包含光源和单元 ID 的元组,并使用当前线程作为索引将其存储在缓冲区中。
- 使用您喜欢的 GPU 算法(基数排序或黑调排序)按单元格 ID 对该缓冲区进行排序。
- 对缓冲区进行排序后,构建直方图并进行前缀和扫描,以查找缓冲区内每个单元格的起始位置。
前任。
(Cell, Light) 1st pass: Cell Buffer -> [ 23, 0 ] [ 7, 1 ] [ 9, 2 ] .... 2nd pass: Cell Buffer -> [ 7, 1 ] [ 9, 2 ] [ 23, 0 ] .... (Start, End) 3rd pass: Index Buffer -> [0 0] [0 0] [0 0] [0 0] [0 0] [0 0] [0 1] [1 1] [1 2] ...
有关更多详细信息,该方法在Simon Green的"使用CUDA进行粒子模拟"中进行了描述: http://idav.ucdavis.edu/~dfalcant/downloads/dissertation.pdf
原始方法假设粒子只能放置在单个单元格中,但您应该能够通过使用更大的工作负载轻松解决此问题。