喷涂中遇到的光栅化问题

Author Avatar
Kanglai Qian 4月 22, 2022

最近看的一个问题,最后想通很简单但一开始自己狠坑了自己一把,简单记录一下。

为了实现运行时喷涂,直接动态RT解决问题:

  1. 喷涂时利用2uv直接绘制到一张RenderTexture
  2. 模型正常渲染的时候采样这张RenderTexture。

但是第二步模型采样的时候始终边缘处有黑色像素,就算提高RenderTexture精度到4096也只能说是缓解。下图中相当于第一次利用2uv绘制的像素全是红色,那么理论上采样的时候就应该全红。

当然其实很常见的一个思路是直接对这张RenderTexture做一次膨胀,相当于红色区域往外扩张几像素肯定能解决问题(之前试验BentNormal确实也是这么干的);但是这回想找到这些黑色来源的根本原因并处理掉。

Round 1 Filter

第一个问题其实一眼就看出来了: 采样的时候默认走了bilinear,导致边缘像素采样的时候混入了外侧的顶点。直接在脚本里把生成的RenderTexture设置下就行:

RenderTexture MaskTex = new RenderTexture(sizeX, sizeY, 0, RenderTextureFormat.ARGB32, RenderTextureReadWrite.Linear);
MaskTex.anisoLevel = 0;
MaskTex.filterMode = FilterMode.Point;

可以看到原来只是很淡黑色区域变得清晰起来(ps. 这里其实出现黑色的地方都是2uv突变的地方),然后让我们进入下一个环节。

Round 2.1 Texture Coordinate Precision?

这里我的思路拐入坑中,第一反应是类似Texture Gathers and Coordinate Precision中提到的问题: 光栅化的规则和Sample的规则可能有所不同(脑海中飘过A Pixel Is Not a Little Square, a Pixel Is Not a Little Square, a Pixel Is Not a Little Square!)。手动试了下如果采样的地方全部偏移一下效果如何:

fixed4 mask = tex2D(_MaskTex, i.uv.zw + float2(1/1024.0, 1/1024.0));

发现有点意思蛤~效果确实提升了不少!

然而后来就陷入了僵局——沿着这个路子继续依然无法根治问题,而且我翻了下DX Spec也没有对应说明(我试了下博客里使用的1/512.0,黑块反而会扩大)。

Round 2.2 Conservative Rasterzation!

后来被曹哥哥一语点醒梦中人——也许是光栅化策略导致的? 火速试了下保守光栅化,反正Unity上已经有现成的ShaderLab command: Conservative,直接打开发现完美:

这么一想雀实…其实问题不是出在第二步使用2uv采样RenderTexture上,而是第一步绘制到RenderTexture上就有问题。我下意识觉得不应该无脑膨胀解决问题,但是问题恰好处于我思维盲区。

之后为了兼容性(Conservative需要DX11.3或者扩展来支持),参考GPU Gems Conservative RasterizationExtension free, non-manufacturer dependent Conservative Rasterization重新用GS实现了一下,结果依然稳定。

最后总结下保守光栅化其实做的就是类似膨胀操作,但是好处是它是“精准”的——在喷涂这个使用场景下如果直接使用膨胀,会直接导致喷涂区域变大(因为膨胀的时候其实要识别是2uv边缘还是喷涂本身的边缘有点麻烦)。当然后来梁老师也提了一个釜底抽薪的思路——直接笔刷做成周围一圈带点渐变,也能换个姿势解决问题orz。