Lv.2 Unity主线:添加法线

法线贴图

通常模型都会有法线贴图,里面是关于模型法线的信息,对法线的处理需要在计算光照之前 图片

添加法线贴图

properties

一个是采样器,一个是法线强度的控制

1
2
3
4
5
_NormalMap("NormalMap", 2D) = "bump"{}
_NormalMapScale("NormalMapScale", Range(0,5)) = 1
----------
sampler2D _NormalMap;
half _NormalMapScale;

appdata

appdata需要读取模型原始法线和切线

1
2
3
4
5
6
7
struct appdata
{
    float4 vertex   : POSITION;
    float3 normal   : NORMAL;   //法线
    float4 tangent  : TANGENT;  //切线
    float2 uv       : TEXCOORD0;         
};

v2f

v2f则需要接收处理的模型法线和切线以及副切线

1
2
3
4
5
6
7
8
9
10
struct v2f
{
    float4 pos          : SV_POSITION;
    float4 positionWS   : TEXCOORD0;
    float3 normalWS     : TEXCOORD1; //法线
    float3 tangentWS    : TEXCOORD2; //切线
    float3 bitangentWS  : TEXCOORD3; //副切线
    float2 texcoord     : TEXCOORD4;
    UNITY_SHADOW_COORDS(5)
};

vert 顶点着色器

处理数据参考

1
2
3
4
5
6
7
8
9
10
11
12
13
v2f vert(appdata v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);
    o.positionWS = mul(unity_ObjectToWorld, v.vertex);
    o.normalWS = UnityObjectToWorldNormal(v.normal);
    o.tangentWS = UnityObjectToWorldDir(v.tangent.xyz);
    //* v.tangent.w处理平台差异
    o.bitangentWS = cross(o.normalWS, o.tangentWS) * v.tangent.w;
    o.texcoord = TRANSFORM_TEX(v.uv,_MainTex);
    TRANSFER_SHADOW(o);
    return o;
}

frag片元着色器

法线贴图混合

对于法线贴图的混合,需要先构建TBN矩阵,其实就是把模型顶点法线,切线,副切线写成矩阵形式,注意需要归一化

1
float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS);

采样法线贴图需要额外的UnpackNormal,对于强度控制只需要控制xy分量,也就是切线和副切线,因为切线和副切线决定面的倾斜程度,不对法线操作是因为在归一化后会间接影响,如果同时都乘上这个因子,那么归一化相当于和原法线强度不变

1
2
float3 normalMapData = UnpackNormal(tex2D(_NormalMap, i.texcoord)).xyz;
normalMapData.xy *= _NormalMapScale;

最后就是混合阶段,直接使用矩阵乘法就行

1
2
float3 pixelNormal = normalize(mul(normalMapData, TBN));
float noL = max(0, dot(pixelNormal, lightWS));

当然不构建矩阵乘法其实还有一种写法,实际是一样的

1
2
3
float3 pixelNormal = normalize( normalMapData.x * tangentWS 
        + normalMapData.y * bitangentWS 
        + normalMapData.z * normalWS );

完整代码参考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
float4 frag(v2f i) : SV_Target
{
 //vectors
 float3 positionWS = i.positionWS; 
 float3 normalWS = normalize(i.normalWS);
 float3 tangentWS = normalize(i.tangentWS);
 float3 bitangentWS = normalize(i.bitangentWS);
 float3x3 TBN = float3x3(tangentWS, bitangentWS, normalWS);
 half3 lightWS = normalize(_WorldSpaceLightPos0.xyz);
 float3 viewDir = normalize(_WorldSpaceCameraPos - positionWS);
 //tex
 float3 lightColor = _LightColor0.xyz;
 float4 baseCol = tex2D(_MainTex, i.texcoord);
 float3 normalMapData = UnpackNormal(tex2D(_NormalMap, i.texcoord)).xyz;
 normalMapData.xy *= _NormalMapScale;
 //halfLambet
 float3 pixelNormal = normalize(mul(normalMapData, TBN));
 float noL = max(0, dot(pixelNormal, lightWS));
 float halfLambet = noL*0.5 + 0.5;
    
    //Shadow
    float shadow = SHADOW_ATTENUATION(i);
    //output
    float3 finalColor = baseCol *halfLambet * lightColor *shadow;
    return float4(finalColor, 1); 
}



    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • URP - RendererFeature :ScreenSpaceOutline
  • 平滑法线处理 - 八面体映射
  • Lv.3 Unity主线:一个简单的PBRShader
  • 理论支线:直接光漫反射与GGX高光的混合问题
  • 理论支线:PBR - 基于图像的照明( image based lighting-IBL)
  • # #