角色妆容的实现

前段时间做了下角色妆容的实现,想写个文章记录一下这个事情。妆容看起来很复杂,实际上整理实现思路很简单,主要是两个方面的内容,改变基础色和改变高光(金粉效果)。
先贴一个妆容效果:

没有开启妆容的情况下,基础色就是从颜色贴图和基础颜色中获得;开启妆容后,要根据各个状态模块的模板和比例来插值妆容颜色和基本颜色;金粉则是改变特定区域的高光,金粉的浓淡可以用滑块控制,最好同时结合妆容色的浓淡;额外可能需要改变特定妆容区域的光滑度,比如唇彩。

妆容界面


妆容开关

最上面有一个Toggle来开关妆容模块,可以使用shader_feature_local的关键字,比如_MAKEUP来区分。

妆容金粉

最上面的界面是指定金粉贴图贴图和相应的缩放,使用同样的UV缩放和位移的好处是避免重复读取金粉贴图,提高性能,避免每个妆容模块都要去读一次贴图。毕竟贴图是存储在内存中,要读取到GPU内的话,如果没有Cache中,则速度相比一个计算来说要慢一个数量级的可能。

妆容模块

接下来是具体的妆容模块,虽然模块比较多,实际上大同小异。功能都是通过通道贴图去改变指定位置的基础颜色,有些通道贴图还有图案的作用。有一些模块有额外的功能,比如唇彩的光滑度滑块、面纹的UV变化。

妆容的实现原理

下面介绍妆容的具体实现原理。

妆容颜色

这里的妆容颜色实际上对应的就是界面上具体的妆容模块。以第一个腮红为例子来说明,参考如下代码:

1
2
3
half3 makeup = baseColor;
half4 blush = SAMPLE_TEXTURE2D(_BlushMap, sampler_BlushMap, uv0);
makeup = lerp(makeup, blush.rgb * _BlushColor, blush.a * _BlusIntensity);

从代码可以看到妆容色makeup是基础颜色和妆容颜色的插值结果。妆容颜色是从通道贴图读取的rgb和腮红颜色的结合,同样插值比例是通道贴图的a和腮红比例的结合。不过大部分妆容模块的妆容颜色不需要通道贴图的rgb,这种通道贴图实际上可以做合并处理。

妆容模块的结合

妆容模块有一定的叠加顺序,最底部的是基础色,然后是按照顺序叠加的妆容模块,比如界面上的妆容模块顺序。那么,计算的时候,首先也是一个个按照顺序插值过来,比如先插值基础颜色和腮红,然后用插值结果继续和下一个妆容模块做插值,这样得到的最终妆容颜色就是多个妆容模块的结合。

妆容金粉

金粉实际上改变的是高光。没有金粉的话,高光就是默认的情况,比如pbr的金属流高光或者Bling-Phong的高光。有金粉的话,根据金粉计算出一个高光,同时与默认高光进行插值,插值的因子同具体妆容模块的颜色计算。
可以参考以下代码实现:

1
2
3
half3 golddustUV0 = SAMPLE_TEXTURE2D(_GolddustMap, sampler_GolddustMap, _GolddustUVTile * uv0 + _GolddustUVSpeed * _Time.x).rgb;
half4 blush = SAMPLE_TEXTURE2D(_BlushMap, sampler_BlushMap, uv0);
half3 specular = lerp((half3)0, _BlusGolddustColor * golddustUV0 * _BlusGolddustIntensity, blush.a * _BlusIntensity);

对于Pbr的金属流,默认的高光是0,所以金粉是为了增加额外的高光。关键的一句是在默认的高光和金粉高光之间做插值,插值比例是金粉浓度和妆容的比例。金粉高光是从金粉贴图读取出来同时应用金粉颜色和强度。

其它功能

比如唇彩模块可以改变光滑度,这个改变的前提是唇彩的通道贴图a通道是大于0的;另外还有面纹的一些UV变化,实际上这个是简单模仿贴花的功能。

性能优化

下面介绍一些妆容性能相关的优化策略。

妆容通道贴图采样器的合并

上述界面的妆容模块过多,如果每个妆容通道贴图一个采样器,肯定会超过限制。方式是所有妆容模块共用一个或者几个采样器。不过,理解上来说,一个贴图采样器对应一个贴图设置,所以去改变贴图的设置会不会有一些影响这个待验证。

妆容通道贴图合并

其实根本没必要一个妆容模块一个贴图,完全可以做贴图合并,比如不需要使用rgb的妆容模块,那么一个贴图可以对应四个妆容模块了。实际上,跟美术沟通后发现,妆容的效果主要是依赖妆容的通道掩码和妆容颜色,所以基本上不需要使用妆容贴图的rgb。

妆容颜色渲染到基础贴图

这个理论上来说算是终极优化吧。妆容会暴露很多参数给美术或者用户,用户调整这些参数后会得到一个化妆后的效果。关键的地方是,调整完成之后,可以理解为妹子化妆完成后,效果已经固定了。那么实际上,我们不需要每次再去计算妆容颜色,而是可以将妆容颜色渲染到一张单独的贴图上或者直接覆盖原本的BaseMap。以后的渲染,就不需要使用妆容模块了。

具体实现思路

可以新建一个Pass,将妆容颜色的计算结果单独走一遍Pass,同时结合原本的BaseMap作为基础颜色,渲染目标是一个RT,比如是BaseMap。这个Pass的开关可以提供接口供业务代码控制,在化妆完成后调用来覆盖原本的BaseMap。

妆容效果

最后上一点效果图吧,从美术大佬那边要来的图,凑合看看吧。