项目中,用一个相机将图像渲染到Render Texture上面,然后将这个Render Texture给UI中的Raw Image显示出来,发现粒子效果没有出现。
如图:
实际的样子:
Raw Image中显示的样子:
其中,粒子使用的Shader是Legacy Shaders/Particles/Alpha Blended。在其中可以看到如下代码:
Tags { "QUEUE"="Transparent" "IGNOREPROJECTOR"="true" "RenderType"="Transparent" "PreviewType"="Plane" }
Pass {
Tags { "QUEUE"="Transparent" "IGNOREPROJECTOR"="true" "RenderType"="Transparent" "PreviewType"="Plane" }
ZWrite Off
Cull Off
Blend SrcAlpha OneMinusSrcAlpha
ColorMask RGB
这里要了解一个叫做Premultiplied Alpha的概念。也就是预先将Alpha计算进RGB的值中。比如原来是(r,g,b,a), 通过Premultiplied Alpha变为(ar,ag,ab,a)。Premultiplied Alpha的作用一个是减少后期计算,还有一个更重要的作用是进行Texture Filtering:
Premultiplied Alpha 后的像素格式变得不直观,因为在画图的时候都是先从调色板中选出一个RGB颜色,再单独设置透明度,如果RGB乘以透明度就搞不清楚原色是什么了。从前面的 Alpha Blending 公式可以看出,Premultiplied Alpha 之后,混合的时候可以少一次乘法,这可以提高一些效率,但这并不是最主要的原因。最主要的原因是:
没有 Premultiplied Alpha 的纹理无法进行 Texture Filtering(除非使用最近邻插值)。
以最常见的filtering方式线性插值为例,一个宽2px高1px的图片,左边的像素是红色,右边是绿色10%透明度,如果把这个图片缩放到1x1的大小,那么缩放后1像素的颜色就是左右两个像素线性插值的结果,也就是把两个像素各个通道加起来除以2。如果使用没有Premultiplied Alpha的颜色进行插值,那么结果就是:
((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)((255,0,0,1)+(0,255,0,0.1))⋅0.5=(127,127,0,0.55)
如果绿色Premultiplied Alpha,也就是 (0, 255 * 0.1, 0, 0.1),和红色混合后:
((255,0,0,1)+(0,25,0,0.1))⋅0.5=(127,25,0,0.55)((255,0,0,1)+(0,25,0,0.1))⋅0.5=(127,25,0,0.55)
从上面的图里第三个颜色是没有Premultiplied Alpha的混合结果,对比第四个Premultiplied Alpha后颜色的结果,显然第四个颜色更符合直觉,第三个颜色太绿了,因为绿色通道没有乘以透明度,所以在线性插值的时候占了过大的权重。
所以Premultiplied Alpha最重要的意义是使得带透明度图片纹理可以正常的进行线性插值。这样旋转、缩放或者非整数的纹理坐标才能正常显示,否则就会像上面的例子一样,在透明像素边缘附近产生奇怪的颜色。
GPU专用的纹理格式,比如 PVR、ETC 一般在生成纹理都是默认Premultiplied Alpha的,这些格式一般是GPU硬解码,引擎用CPU处理会很慢。
原因在于默认的粒子效果使用到的shader中使用了ColorMask RGB
,所以只有RGB三个通道的值被存入了缓冲,而没有写入A通道的值, 所以我们得到的texture其实没有粒子的alpha信息,由於使用了ZWrite Off
,所以也没有粒子的深度信息,当我们把这张纹理拿出来显示的时候,由于某些粒子所在位置alpha值为0,所以通过alpha预存得到的RGBA值是(0, 0, 0, 0),所以最后也就看不到颜色了。
对用的解决办法是将shader中的ColorMask RGB
改为ColorMask RGBA
,写入粒子的alpha信息就行了。
如图:
参考链接: