? ? ? ? 最近在做2D手游的时候遇到一个需求:给2D图片识别边缘并且加上一圈描边,再在此基础上再加一圈柔和的外发光。
? ? ? ? 效果实现本身没有什么特别之处,普通的描边shader参考,稍微改改即可以实现能接受的效果。
? ? ? ? 类似的效果可以参考其他大佬的实现方案:
????????[Unity Shader]2D边缘发光效果(学习笔记) - 知乎 (zhihu.com)https://zhuanlan.zhihu.com/p/633616509Unity Shader 极简实践4——图片外发光 - 简书 (jianshu.com)https://www.jianshu.com/p/333917633789
???????shader的问题在于:一方面、2D图片边缘的识别本身就需要取周围像素点通过透明度判断出边缘来,即需要在片元着色器for循环去多重采样;另一方面、对描边和外发光的效果有柔边要求,因此需要实现类似于高斯模糊的边缘效果,本身高斯模糊也需要for循环多重采样。因此本身性能上讲是不高的。而根据需求, 场景内可能会有数量较多的图片需要使用这个shader。
? ? ? ? 实际上其实美术在出图的时候就加上描边也是可以的,但这样美术童鞋就需要大量处理。本着能用代码就不用人力的原则,最后得出一个折衷的方案:
? ? ? ? 实现一个Editor工具,使用相机拍摄使用了对应材质球的图片,相机背景色设置为Color.clear,然后拍摄出的图片自动存储到目录下。
? ? ? ? 方案上本身没有问题,但是这个工具造出来之后,却发现,拍摄下来的图片,透明度变浅,颜色也变得有点奇怪。
? ? ? ? 测试过程中发现,相机的填充色改为其他颜色,但是透明度为0,拍摄出来的图片竟然也跟着变化。
? ? ? ? 此时开始怀疑是混合模式的问题。
? ? ? ? 查看shader,实现的方式是普通的透明度混合,即
? ? ? ? Blend SrcAlpha OneMinusSrcAlpha
? ? ? ? 回忆起基础知识可知
????????FinalColor = SrcColor * SrcAlpha + DstColor * (1 - SrcAlpha)
????????
? ? ? ? 让我们起一个更加直观的变量名字,假定渲染图片之前的渲染Buffer的Color和Alpha分别为BufferColor,BufferAlpha,图片的Color和Alpha分别为ImgColor和ImgAlpha,最终屏幕颜色为ResultColor和ResultAlpha,则有:
????????ResultColor = ImgColor * ImgAlpha + BufferColor * (1 - ImgAlpha)
????????ResultAlpha = ImgAlpha?* ImgAlpha + BufferAlpha * (1 - ImgAlpha)
? ? ? ? 由此可见,实际上输出的图片的Color和Alpha不是(ImgColor, ImgAlpha),Alpha是一个乘方关系,而Color也被乘上了Alpha系数。保存的图片效果不是想象中的效果的原因就是因为保存的结果是(rgb = ImgColor * ImgAlpha,? a = ImgAlpha * ImgAlpha)。
? ? ? ? 但是为什么Scene里边看到的效果又是对的呢,这是因为,Scene界面下即便我们选了相机的颜色为Color.clear,场景依旧是按天空盒等形式展示的,因此看到的效果不是和Color.clear叠加的结果。
? ? ? ?由于此处我们只是想导出正确的图片,最取巧的方式是,修改BlendMode为
? ? ? ? BlendMode One One
? ? ? ? 则对应的
????????ResultColor = ImgColor * 1 + BufferColor * (1 - 1)
????????ResultAlpha = ImgAlpha?* 1 + BufferAlpha * (1 - 1)
? ? ? ? 正好是对应图片的值。这样拍摄出来的效果就是想要的效果了。