前文【让粒子可以在白色背景显示 [BLENDING SHADER 实操]】说到最后的效果 有点BUG, 在一些过渡的地方有些黑边。 这个问题倒是和算法无关,是和图片本身有关系。 下面是效果对比图:
有黑边 | 没黑边(完美) |
其原因是做图的时候,为了方便做图, 开始是不会直接用"透明"这个概念做图,而是将图做成黑色底,然后最后将黑底转为Alpha=0 的颜色,所以 透明图的RGBA是(0,0,0,1)。 而这种做法对 Additive 是没影响的。但是对于我这个Shader 却是有问题了。
在网上也有人遇到过类似的问题:(渲染tga格式的序列帧,这种黑边怎么优化呀)
其根本原因是没有使用 Additive 的叠加方式(使用了就没问题,会有一种光了一点点的感觉,当然在PS上没办法看到Additive效果)
而图片去黑的方法也有很多,使用PS也行,在AE 中也有个叫UnMult的插件。
然而同事反应 用PS 太麻烦了(虽然有Action),还是需要手动做几步操作,而且纯黑白的图效果不太好。
所以我干脆就使用C#写了个插件(仅限PNG格式)
首先得明白去黑底的原理
去黑底的图片分两种
1.纯黑白(没有其他RGB颜色的图)
2.一种色有除黑白外其他颜色的图片
无论是那种图,其去黑底的原理都是 “越黑的像素越透明”。只是实现的细节上有区别。
对于
1.纯黑白的处理就简单多了,所有像素设置成Rgba(1,1,1, (src.r+src.g+src.b)/3) 即可。
2.多彩色的图片,不能使用纯黑白的处理,否则所有图都会变成黑白,其原理是找出原像素点中RGB分量中最高的值 maxV =Max(src.r,src.g,src.b),一般其透明度就是该值maxV。
因为其他值相对maxV比较少,所以rgb 分别加上 255-maxV 的差值,其原理是将该像素点"变亮"了,从而取消了黑底对该像素点的影响。而黑底将转化为透明度
而使用多彩色图片转换的算法对纯黑白的支持也不太好, 有可能会残留灰底,其原因是 有可能在美工做图时 没有真正地使用"纯灰度",也就是说 rgb 的分值不是相等的,这里对2种图片分别做了处理,在工具中使用 /F 参数是针对纯黑白的图处理。
具体代码如下:
using System; using System.Drawing; using System.Drawing.Imaging; using System.Linq; namespace ImageBlackToAlpha { class Program { static byte Max(params byte[] values) { if (values == null || values.Length == 0) return 0; var max = values[0]; for (var i = 1; i < values.Length; ++i) { max = Math.Max(max, values[i]); } return max; } static byte Min(params byte[] values) { if (values == null || values.Length == 0) return 0; var min = values[0]; for (var i = 1; i < values.Length; ++i) { min = Math.Min(min, values[i]); } return min; } static void Main(string[] args) { if (args.Count() < 2) { Console.WriteLine("Usage: "ImageBlackToAlpha" [+ source] [destination [/F]]"); Console.WriteLine(" source InputPngPath "); Console.WriteLine(" destination OutputPngPath"); Console.WriteLine(" /F Forces all RGB to White(254,254,254)"); return; } var toWhite = false; var image = Image.FromFile(args[0]); var bitmap = new Bitmap(image); var save = new Bitmap(bitmap.Width, bitmap.Height); if (args.Count() == 3) { toWhite = args[2] == "/F" || args[2] == "/f"; } for (var x = 0;x< bitmap.Width;++x) { for(var y = 0; y < bitmap.Height; ++y) { var clr = bitmap.GetPixel(x, y); if (toWhite) { // 只支持 纯黑白贴图 var alpha = (0 + clr.R + clr.G + clr.B) / 3; alpha = Math.Min(clr.A, alpha); //因为 纯白(RGB)而Alpha小于255 会出现 全透明的问题 所以不能使用 255//bug? clr = Color.FromArgb(alpha, 254, 254, 254); } else { var max = Max(clr.R, clr.G, clr.B); //因为 纯白(RGB)而Alpha小于255 会出现 全透明的问题 所以不能使用 255//bug? var sub = 254 - max; var alpha = Math.Min(clr.A, max); clr = Color.FromArgb(alpha, clr.R + sub, clr.G + sub, clr.B + sub); } save.SetPixel(x, y, clr); } } save.MakeTransparent(Color.Transparent); save.Save(args[1], ImageFormat.Png); } } }
效果图:
注:在测试过程中发现rgb(1,1,1)[RGB32(255,255,255)] 的时候,无论透明度设置成多少,该像素依然是全透明,不清楚这是PNG的 Feature 还是 System.Drawing.Imaging 处理的问题,这里统一使用了 254 代替255 避开了这个BUG。