法线贴图的作用

174 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,[点击查看活动详情]

前言

在之前的文章中,介绍了一张纹理图片是如何被贴到模型上的,其中纹理的作用是替代物体的漫反射颜色,而纹理的另一种常见应用是凹凸映射。

什么是凹凸映射

通常一个模型的面数会直接影响其渲染时间和效率,因此选用低面数的模型要比高面数的模型在渲染时效率高,但是低面数的模型会失去很多细节,比如光影效果。那么有没有什么办法既可以照顾到渲染效率又不损失很多细节呢?答案是:凹凸映射技术。而本篇要介绍的法线贴图就属于凹凸映射技术的一种,其可以不改变低模的空间结构,但是又可以使低模在一定程度上保留高模的光影效果。

16714340970552.jpg

法线贴图

法线贴图中存储的是物体表面的法线信息,法线的坐标信息取决于我们是基于哪个坐标空间构建的,例如世界空间下还是模型空间下,不过一般我们经常用的是切线空间下的法线贴图,其具有自由度高、可以进行UV动画,可重用,可压缩等优点。需要说明的是对其采样得到的rgb通道的数值是模型的法向量转换到切空间后的坐标经过单位化,再做一个0-1的映射,然后乘以255得出的颜色值。(下图右侧为切线空间下的法线纹理)

16714334094839.jpg

光影计算

上面提到我们经常使用的是切线空间下的法线贴图,所以采样得到的也就是切线空间下的法线信息,要拿来做光影计算就要求我们将光照模型中需要的其他信息:例如光方向、视角方向、反射方向、顶点坐标信息都变换到同一坐标系下,例如可以将法线信息变换到世界空间下,在和世界空间下的其他向量参与运算。也可以将其他向量变换到切线空间下再与法线进行运算。

在Unity中的具体实现(转换到世界空间下进行计算)

  • 1、在Project面板右键创建一个Unlit Shader,将其命名为NormalMap。

16714364513572.jpg

  • 2、选中第一步创建的NormalMap,右键创建一个Material。

16714364719168.jpg

  • 3、在Hierarchy面板创建一个Sphere,并将第2步创建的Material拖拽赋值到Sphere上。

16714367219062.jpg

  • 4、使用脚本IDE打开第一步创建的Shader,添加代码如下:
Shader "Unlit/NormalMap"
{
    Properties {
        _Color ("基础色", color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex("纹理贴图", 2D) = "white" {}
        _NormalMap("法线贴图", 2D) = "bump" {}
        _SpecularPower ("高光次幂", Range(0, 10)) = 5
        _SpecularColor ("高光颜色", color) = (1.0, 1.0, 1.0, 1.0)
    }
    SubShader {
        Tags {
            "RenderType"="Opaque"
        }
        Pass {
            Name "FORWARD"
            Tags {
                "LightMode"="ForwardBase"
            }
            
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"
            #include "AutoLight.cginc"
            #include "Lighting.cginc"
            #pragma multi_compile_fwdbase_fullshadows
            #pragma target 3.0

            float3 _Color;
            float _SpecularPower;
            float3 _SpecularColor;
            sampler2D _MainTex;float4 _MainTex_ST;
            sampler2D _NormalMap; float4 _NormalMap_ST;
            
            struct appdata {
                float4 vertex : POSITION;
                float4 normal : NORMAL;
                float4 tangent : TANGENT;
                float2 uv : TEXCOORD0;
            };
            struct v2f {
                float4 pos : SV_POSITION;
                float4 posWS : TEXCOORD0;
                float2 uv : TEXCOORD1;
                float3 nDirWS : TEXCOORD2;
                float4 tDirWS : TEXCOORD3;
            };
            v2f vert (appdata v) {
                v2f o;
                o.uv = v.uv;
                o.pos = UnityObjectToClipPos( v.vertex );
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                o.nDirWS = normalize(UnityObjectToWorldNormal(v.normal));
                o.tDirWS = float4(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0.0)).xyz, v.tangent.w);
                return o;
            }
            float4 frag(v2f i) : COLOR {
                float3 nDirTS = UnpackNormal(tex2D(_NormalMap, TRANSFORM_TEX(i.uv, _NormalMap)));
                float3 nDir = normalize(i.nDirWS);
                float3 tDir = normalize(i.tDirWS - dot(i.tDirWS, nDir) * nDir);
                float3 bDir = cross(nDir, tDir) * i.tDirWS.w;
                float3x3 TBN = float3x3(tDir, bDir, nDir);
                float3 nDirWS = normalize(mul(nDirTS, TBN));
                float3 lDirWS = _WorldSpaceLightPos0.xyz;
                float3 vrDirWS = reflect(-normalize(_WorldSpaceCameraPos.xyz - i.posWS.xyz), nDirWS);
                float lambert = max(0.0, dot(nDirWS, lDirWS));
                float phong = pow(max(0.0, dot(vrDirWS, lDirWS)), _SpecularPower);
                float3 diffuse = tex2D(_MainTex,TRANSFORM_TEX(i.uv, _MainTex)).rgb * _Color.rgb * lambert;
                float3 specular =  _SpecularColor * phong;
                return float4(diffuse + specular, 1.0);
            }
            ENDCG
        }
    }
    FallBack "Diffuse"
}
  • 5、将准备好的漫反射贴图和法线贴图拖拽赋值到材质面板。

16714373208067.jpg

  • 6、查看效果。

16714375916046.jpg

最后

需要注意的是法线贴图并没有改变模型的顶点位置,而是一种视觉效果,在模型的边缘处就可以看出来。OK,本篇就到这里。