渲染逆向方法:
用Adreno Profiler抓帧, 并且分析其中的OpenGL调用及相关资源(顶点数据、纹理、Shader代码等)
其中Shader代码和在Unity里选中Shader并点击Compile and show code看到的gles3部分类似, 建议转换成更易于阅读的形式
卡通着色思路:
光照计算仍然是漫反射+高光反射, 其中漫反射是HalfLambert, 高光反射公式来自于Blinn-Phong模型
比较关键的点是, 漫反射部分不是渐变的, 而是分了3档, 以实现卡通风格的层次感, 即所谓的梯度漫反射(Ramped Diffuse)
上图是还原出来的Unity Shader及渲染结果, 下面简要介绍一下该Shader的实现细节..
Main Color:
主纹理, rgb为基础颜色, a的作用见下文
Bloom Mask:
未使用
Light Map:
光照纹理, rgb作用见下文
mainColor.a:
主纹理alpha通道, 表示不受光照影响的程度, 为1.0时无论是否有光照影响都为原颜色, 为0.0时仅包含光照计算结果,
如身体和头发主纹理中, 一些黄色的部分, alpha值较高, 用来保持高亮
从主纹理中分离出来的alpha通道, 见下图
Avatar_Kiana_C2_Texture_Body_Color_RGB2048_A.png和Avatar_Kiana_C2_Texture_Hair_Color_Common_A.png
以及 输出-主纹理-A通道.png
UsingBloomMask:
材质属性, 是否使用BloomMask纹理调整不受光照影响的程度, 默认关闭
公式为 mainColor.a *= tex2D(_BloomMaskTex, i.texcoord5.xy).x
注意: 只能减弱或保持原状, 因为纹理采样结果在0.0~1.0之间
UsingDitherAlpha:
材质属性, 是否应用alpha抖动, 默认关闭
FirstShadowMultColor:
材质属性, 暗面亮度一, xyz分量分别对应主纹理rgb通道, 值越小越暗, 此处为(0.72941, 0.6, 0.65098)
公式为fristShadowColor = mainColor.rgb * _FirstShadowMultColor.xyz
SecondShadowMultColor:
材质属性, 暗面亮度二, xyz分量分别对应主纹理rgb通道, 值越小越暗, 此处为(0.65098, 0.45098, 0.549019)
公式为secondShadowColor = mainColor.rgb * _SecondShadowMultColor.xyz
i.color.r和lightMapColor.g和LightArea和SecondShadow:
顶点颜色r通道和光照纹理g通道, 两者乘积rgProduct用来做暗面颜色选择, 公式如下
diffuseColor = rgProduct >= 0.090000033 ? otherColor : shadowColor
otherColor = ((rgProductFix + i.halfLambert) * 0.5 >= _LightArea) ? mainColor.rgb : fristShadowColor
shadowColor = ((rgProduct + i.halfLambert) * 0.5 >= _SecondShadow) ? fristShadowColor : secondShadowColor
其中,
rgProductFix是rgProduct经过微调的一个值;
i.halfLambert是半兰伯特值, 即法线与光线夹角的余弦值映射到0.0~1.0范围内, 夹角越小值越接近1, 用于表示漫反射部分的强度;
_LightArea和_SecondShadow是材质属性, 分别为用于选择 原颜色和暗面一、暗面一和暗面二的阈值;
上述公式可理解为,
如果rgProduct非常小的话, 在暗面一和暗面二中选一个; 否则, 在原颜色和暗面一中选一个,
相当于漫反射率不再是根据法线渐变, 而是分了3档(按阈值选择), 以此实现卡通风格的层次感,
更进一步地, 当rgProduct非常小的时候, 除非i.halfLambert比较大, 否则选的往往是暗面二
下图1 输出-明暗分布.png, 其中不同颜色代表了不同的区域, 绿色和黑色是暗面一和暗面二, 白色和红色是原颜色和暗面一
下图2 输出-顶点R通道×光照纹理G通道.png, 其中比较黑的区域正对应了上图的绿色和黑色区域
Shininess:
材质属性, 光泽度, Blinn-Phong光照模型中用于计算高光反射的指数, 用于控制高光区域的亮点大小, 值越大亮点半径越小
lightMapColor.b:
光照纹理b通道, 用于使某些区域更容易出现高光, 因为出现高光的条件是 lightMapColor.b + blinnSpec > 1.0
其中blinnSpec是按照Blinn-Phong光照模型计算出来的高光度, 越大表示高光反射越强,
下图 ID_118_B.png, 这是头发的光照纹理b通道, 比较明显; 以及 ID_113_B.png和 输出-光照纹理-B通道.png
lightMapColor.r和LightSpecColor和SpecMulti:
lightMapColor.r为光照纹理r通道, LightSpecColor和SpecMulti为材质属性, 共同控制当存在高光时高光的颜色,
公式为 _LightSpecColor.xyz * _SpecMulti * lightMapColor.r
见下图 ID_113_R.png、ID_118_R.png、输出-光照纹理-R通道.png
最终产生的高光分布: (比想象中弱很多)
Color:
材质属性, 用于调整光照计算结果, 只能调的更弱或者保持原状,
公式为litColor.xyz = (diffuseColor + specColor) * _Color.xyz;
其他输出图:
输出-主纹理-RGB通道.png 和 输出-顶点色-RGB通道.png
皮肤shader:
与上述shader类似, 但是没有BloomMask, 增加了ProbToggle用于调节肤色(未见实际使用), 并且mainColor.a不再有上述作用
描边的实现:
将模型在观察空间中, 按法线往外扩张, 以剔除正面的方式绘制 (Shader中声明的顶点属性是切线, 但实际提交的数据应该还是法线)
下图 输出-膨胀方向.png, 其中红色表示右下, 绿色表示左上, 黄色表示右上, 黑色表示左下, 注意: 只是大概的方向不是绝对的
最终描边结果 输出-最终结果.png, 为了更明显, 描边颜色设置为了黑色, 游戏中实际的值为(0.4117647, 0.3112941, 0.3768184)