开启HDR时,半透明渲染画面异常的解决方案

开启HDR时,半透明渲染画面异常的解决方案

这是侑虎科技第331原创文章,感谢作者程可汗供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者Github:https://github.com/chengkehan,同时,作者也是U Sparkle活动参与者哦,UWA欢迎更多开发朋友加入 U Sparkle开发者计划,这个舞台有你更精彩!


在我们开启HDR时,Blend可能引起Unity自带材质 “Particles/Alpha Blended” 出现画面显示异常的问题。该问题本质上是Alpha Blend操作的颜色融合问题,其在大家的项目研发中影响范围很广。为此,本文分析了异常的原因并给出了相应的解决方案。

一、举例说明

我们不妨先来看以下代码:

// Blend 状态设置
Blend SrcAlpha OneMinusSrcAlpha

// 颜色输出
fixed4 col = 2.0f * i.color * tex2D(_MainTex, i.texcoord);
return col;

这个代码在非HDR时不会有什么问题,但是当切换到HDR时就会导致渲染画面出现问题。在下面的例子中有两个面片,一个为红色的方形面片,一个为黄色的圆形面片,都使用 Unity自带的“Particles/Alpha Blended”作为材质。当它们分别渲染时,效果似乎是正确的。(注意这里也只是说似乎正确,稍后会解释为什么,为了将注意力放在问题本身,暂时忽略Gamma矫正)。
请输入图片描述

当我们将这两个面片一起渲染时,问题就出现了,如下图所示:
请输入图片描述

很明显上图中的效果是错误的,正确的效果是红色方形面片上叠加一个黄色圆形面片。而出现错误的原因正是由于开启了HDR,但材质中没有考虑到HDR所导致的。下面我们就一步步来分析。


二、分析说明

首先,先渲染红色方形面片,最终写到Color Buffer中的值应该是一个红色,但是有多红呢?由于开启了HDR,RenderTexture的单通道不再是8bit,而是更多的bit,这就意味着单通道的最大值不再是非HDR情况下的1,会比1大很多,具体最大值是多少需要看RenderTexture的格式是什么。明白了这点后再看代码:

fixed4 col = 2.0f * i.color * tex2D(_MainTex, i.texcoord);

这里的_MainTex 赋予的是白色贴图 fixed4(1,1,1,1),i.color 是 fixed4(1,0,0,1) 的红色,因此col 变量的值为fixed4(2, 0, 0, 2),所以最终写到Color Buffer中的红色,不是1,而是 2。开始划重点了,再仔细想想到底是不是 2,这里也是我一开始忽略的地方,而问题的关键就在这,由于设置了Blend方式:

Blend SrcAlpha OneMinusSrcAlpha

这种Blend方式对应的公式是:

final color = SrcAlpha * SrcColor + (1 - SrcAlpha) * DestColor

根据上文中的例子:

SrcAlpha = 2
SrcColor = fixed3(2,0,0)
DestColor = 背景色 <= fixed3(1,1,1)

我们将这些值代入Blend公式后会发现,最终写入Color Buffer是一个R通道介于3到4之间的红色。这也是上文中为什么说是看似正确,因为虽然输出的红色从效果上是正确的,可实际已经不在控制范围之内。而当我们将一个黄色的圆形面片叠加上之后,问题就完全暴露了。这里还有一个注意点,R通道是介于3到4之间的值,那么GB两个通道呢,再次划重点,此时这两个通道中写入了两个负值,值的大小和背景色有关,这也是不可忽略的,会影响到后续的叠加色输出。

在那张错误的效果图中可以看到,原本的圆形黄色面片中间出现了出乎意料的偏绿色。颜色不可能凭空出现,一定是经过了某种操作,所以第一个想到这三个颜色之间的关系是 “绿色=黄色-红色”。顺着这种假设,再参照上文的分析,在渲染这个圆形黄色面片的时候(由于圆形是通过一张Alpha渐变的Mask贴图做出来的,为了将问题简化,先只考虑圆心处 Mask的Alpha接近1的情况):

SrcAlpha = 2
SrcColor = fixed4(2,2,0)
// 上文中说明了 R 通道是一个介于 3 到 4 之间的一个值
// GB 通道是两个负值,值的大小和背景色有关
DestColor = fixed3(3.5,-g,-b) 

三、验证并修复

再次将这些值代入Blend公式,就会得到一个偏绿色的值了,这也就解释了为什么会出现上文图中的错误。同理,圆中间的绿色是错误的,而黄色的渐变的过度似乎是正确的,其实也是错误的,只是巧合下的看似正确。

最后,针对这个问题修复,可以将a通道控制在0到1的范围内,将代码修改为:

fixed4 col = i.color * tex2D(_MainTex, i.texcoord);
col.rgb *= 2.0f;
return col;

补充:当两个颜色叠加时,有时会混出另一个不想要的颜色。比如 fixed3(1,0,0) 和 fixed3(0,1,0) 以0.5的比例进行混合,最终是一个 fixed3(0.5,0.5,0) 黄色。这种情况在 LDR时视觉上或许并不明显,但是在HDR时效果会被成倍的放大。对于这种情况,可以挑选一个合适的Tonemapping来减轻问题。或者对于Blend时出现这种情况的颜色,将其rgba四个通道都限制在0到1的范围内,这就和LDR没区别了。


文末,再次感谢程可汗的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)。
也欢迎大家来积极参与U Sparkle开发者计划,简称"US",代表你和我,代表UWA和开发者在一起!