alpha-to-coverage是我个人非常钟爱的一种渲染技术,因为它可以用比较低的编程成本以及GPU成本实现类似顺序无关的透明(OIT)渲染。
最典型的使用alpha-to-coverage的例子就是《最终幻想13》三部曲了。
在PS3/Xbox 360上,由于只有MSAA 2x,它的颗粒感是很明显的,但PC版上,由于有了MSAA 8x,因此有了明显的效果。
具体的实现方式可以从DirectX SDK的例子 TransparencyAA就可以了解一二。(顺便说一下,这个例子本来是为了推广TransparencyAA的,但这种渲染技巧对毛发或者叶子边缘的蓬松感的表现没有什么好处,因为TransparencyAA本质上还是一种alpha test的改进形式。)
不过缺省的alpha-to-coverage是一种完全没有随机性的过程,因此它会在alpha值变化的地方产生类似地形图那样的条纹:
为了解决这个问题,humus.name网站的大牛(点击打开链接)提出了使用伪随机函数在pixel shader里改写SV_Coverage,从而改进alpha-to-coverage效果的尝试。但大牛没有留下参考用的shader代码。
因此我就做了点小尝试,去实现大牛提出的效果。实验结果截图和参考shader会贴在后面。
效果比第一张图的要好多了。这个是MSAA 4x下的截图,所以颗粒感还是有一些,但只要不放大了看还是很可以接受的。
Shader实现如下:
// 根据屏幕坐标得到一个伪随机值。由于屏幕坐标是固定的,因此伪随机值不会导致闪烁
float rand_1_05(in float2 uv)
{
float2 noise = (frac(sin(dot(uv, float2(12.9898, 78.233)*2.0)) * 43758.5453));
return abs(noise.x + noise.y) * 0.5;
}
struct PsOutput
{
float4 v4Color : SV_Target;
uint uCoverageMask : SV_Coverage;
};
PsOutput PsCustomAlphaToCoverage(PsInput I)
{
PsOutput output;
// Lookup alpha texture
float4 v4AlphaSample = g_AlphaTexture.Sample( g_AlphaTextureSampler, I.v2TexCoord );
// Modulate by diffuse lighting value
output.v4Color = v4AlphaSample * I.v4Diffuse;
// 如果是普通的alpha to coverage,在这里就可以返回,也不需要输出SV_Coverage。
// 另外记得定制的alpha to coverage不能打开Blend state里的AlphaToCoverageEnabled选项。
// 根据alpha算出可以写多少个MSAA sample
float alpha = clamp(output.v4Color.a * MSAA_SAMPLES, 0.0f, MSAA_SAMPLES);
// 如果alpha值太低,则直接不输出任何MSAA sample
if (alpha < 0.2f)
output.uCoverageMask = 0;
// 如果alpha值太高,则输出全部MSAA sample
else if (alpha > MSAA_SAMPLES - 0.2f)
{
output.uCoverageMask = (uint(1) << MSAA_SAMPLES) - 1;
}
else
{
// 其余情况则根据alpha的范围决定。
// base和base+1为需要二择一的两组coverage mask。fraction为选择base+1的概率。
float fraction = frac(alpha);
uint base = floor(alpha);
float randValue = rand_1_05(I.v4Pos.xy);
// 根据概率决定该屏幕像素的coverage mask选择的是base还是base+1。
if (randValue < fraction)
base++;
output.uCoverageMask = (uint(1) << base) - 1;
}
return output;
}