崩坏3角色渲染分析

https://blog.csdn.net/liumazi/article/details/78858811

 

渲染逆向方法:

用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)

posted @ 2018-07-16 16:22  alps_01  阅读(1913)  评论(0编辑  收藏  举报