今天遇到一个神奇的现象,我设置了一个100 * 100的 texture,然后使用 ComputeShader 让它填充一些颜色,结果却有一个黑边。

代码如下所示:

_renderTexture = new RenderTexture(100, 100, 24);
        _renderTexture.enableRandomWrite = true;

_renderTexture.Create();
        
shader.Dispatch(0, _renderTexture.width / 8, _renderTexture.height / 8, 1);
[numthreads(8,8,1)]
void CSMain (uint3 id : SV_DispatchThreadID)
{
    Result[id.xy] = float4(float3(id) / 100, 1);
}

最终我们得到的图像是这样的,可以看到有个黑边。

我尝试了一下,如果我将 shader.Dispatch(0, _renderTexture.width / 8, _renderTexture.height / 8, 1); 中的 8 改为 9。

shader.Dispatch(0, _renderTexture.width / 9, _renderTexture.height / 9, 1);

就会发现,这条黑边,变得更加宽了。

经过我查阅相关文档后,我理解了一下 Dispatch 方法的三个参数是说在XYZ三个方向上划分多少个线程组。回到之前的 8 。也就是划分了 100 / 8 = 12.5 -> 12 个线程组。

也就是说100*100的图像被横竖各划分了12次,有12*12个小格子,每个格子由一个线程组来执行。

但是 100 除以 8 不是整除,因此会多出一些像素未能分配到线程组中。因此出现了黑边的情况。

如下图所示,这个图像是100x100,每个白色的小格子是8x8,横竖各有12个小格子,在最右边和最下边有一些蓝色的区域,无法被线程组覆盖,这就是黑边产生的原因。

在 computeShader 代码中 [numthreads(8,8,1)] 相当于规定了每个线程组有多少个线程。如果我们将 numthreads 中的 8 改为 9 ,黑边即可解决。

再来想一想,为什么 Dispatch 时,如果填入 9 黑边会变得更加大呢?

如下图所示

白色的格子是线程组,大小是 9*9,整个宽度是 100*100,因此横竖各有11个白色的格子,最后空蓝色的一条边。这条蓝色的边是线程组未指派的地带。

红色的格子是线程组里的线程数量是 8*8 它不能覆盖整个线程组的工作区域,因此它只能完成该区域的部分工作,而剩下的工作会由第二个线程组里的线程帮忙完成,这里是很奇怪,但是可以解释黑边变宽的原因。

红色的格子水平方向是11个,可以看到右边空出一块区域和蓝色的区域一起构成了黑边(无法处理的区域)。

我将图画完,似乎会形成这样的状况。

剩下白色的区域和蓝色的区域则是无法处理的黑边。

这里就发现了它的一个特性:某个线程组完成不了的工作,会由其它的线程组里的线程顶替完成。 我不确定我这个理解是否正确,但事实看起来是如此。

如果我将 numthread 设置为 9*9,按照现在的理解,应该会产生一条蓝色区域这样的黑边。

03-05 20:31