贴图介绍

基础色图

法线贴图

Mix1图:

  • R:ShadingID
  • G:Metallic
  • B:SpecularMask

 

Mix0图,

  • R: 还没看
  • G: Roughness
  • B: mask # MatcapMask(0~0.2)|EmissiveMask(0.2~1)
    • MatCapMask = mask < 0.2 ? saturate(mask*5.1) : 0
    • EmissiveMask = saturate((mask-0.2)*1.25)

 

MatCpas(Texture2DArray):三张图通常用于皮革、金属、丝袜。

 

Ramp

角色的Ramp

Ramp分为五层过渡,并且每个ID都会有这五层Ramp数据,参数5×5直接来到了25个,计算量会很大。如果我们使用曲线的话则要采样五次消耗也很大。


根据光照角度使用不同的层级

在UE复现时我是用了ColorCurveAtlas,每个材质通过5个Curve来调整不同ShadingID区域的Ramp颜色。

 

BaseColor与MatCap

以丝袜为例,可以看到丝袜主要是各向异性高光以及一个Ramp叠在一起

这个厚度纹理其实就是MatCapMask图,采样Matcap图后就是第三张图的样子,Matcap颜色是一个用户参数。

经查看几乎所有丝袜的材质都使用的第三张Matcap,并且有0.5的缩放和0.5的偏移,也就是只使用了左下角的部分。

BaseColor的计算公式为:
BaseColor = lerp( baseColor,   baseColor   *   matcap   *   matcapTint  ,   matcapMask )

下图第1张图就是混入了Matcap的BaseColor。

然后乘上Ramp可以看到非常接近最终效果的样子(图3)。

 

Specular

公式为

    \[ S=SpecularArea*SepcularMask*UserSpecularColor*F0*Ramp \]

  • SpecularArea的计算中身体部分使用的是UnityURP中的GGX计算,而头发使用的一个自定义的高光计算。
  • SepcularMask通过采样纹理来控制哪部分区域拥有高光。
  • F0是lerp(0.04, baseColor, metallic)计算而来。

在身体部分计算高光区域使用的是GGX后作为D,而G使用:

float G = saturate( NoL * 0.75 + 0.25 );

最后计算高光区域:

(D-(1-rough)) * G / (rough*rough);

 

 

头发高光计算为:

float s = min(1, 0.16666 / NoH2);
NoL = saturate(NoL * 0.75 + 0.25);
return s * NoL;

 

头发部分的高光:

待补充

Emissive

emissive的Mask存在Mix0图B通道的0.2~0.8范围内,需要做一个映射。

saturate((b - 0.2) * 1.25);

然后也是分五个ID做五个用户参数,最后乘上Color就可以了

saturate((b - 0.2) * 1.25) * GetEmissive(id) * color

 

Outline

描边是切线空间平滑法线的数据存放在TexCoord1的两个通道中。

顶点计算部分是在View空间进行法线外扩,并根据深度修改描边粗细。同时顶点色的R通道也会作为描边外扩的Mask来控制粗细。

在着色阶段使用背面渲染。颜色计算使用:

float3 hsv = ToHSV(basecolor - metallic);
float a = (hsv.y - hsv.z) * 0.2 + 0.6;
float3 b = smoothstep(0.28, 0.38, NoL) * 0.5 + 0.5;
hsv.z *= a * b;
return ToRGB(hsv) * UserOutlineTint;

关于S-V的值

颜色类型 V S S-V 基础因子 光照响应
鲜艳深色 0.5 1.0 +0.5 0.7
鲜艳亮色 1.0 1.0 0 0.6
白色/浅灰 0.9 0.1 -0.8 0.44

 

Tonemap/LUT映射

绝区零使用LUT在LogC空间进行调色和tonemap,Unity有LogC的转换函数

在逆向时在UE中写了个

float3 LinearToLogC (float3 linearColor)
{
    const float a = 5.5555558; 
    const float d = 0.047996;
    const float b = 0.0734998;
    const float c = 0.386036;
    
    float3 x = linearColor * a + d;
    float3 logC = c + b * log2(x);
    return saturate(logC);
}