Contents

计算法线贴图

法线贴图的计算方法

  1. 在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
  2. 在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算

各自的优缺点-性能

在切线空间中计算,效率更高,因为可以在顶点着色器中就完成对光照、视角方向的矩 阵变换,计算量相对较小。( 矩阵变换在顶点着色器中计算)

在世界空间中计算,效率较低,由于需要对法线贴图进行采样,所以变换过程必须在片 元着色器中实现,我们需要在片元着色器中对法线进行矩阵变换。( 矩阵变换在片元着色器中计算)

各自的优缺点-效果

在切线空间中计算,对全局效果的表现可能会不够准确(在处理一些列如镜面反射、环境映射效果时表现效果可能不够准确)

在世界空间中计算,对全局效果的表现更准确(可以更容易的应用于全局效果的计算)

选择

若没有全局效果要求,我们优先使用在切线空间下进行光照计算,因为它效率较高

反之,我们选择在世界空间下计算


在切线空间下计算

在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算

关键点:

计算模型空间到切线空间的变换矩阵

计算方式:

由于变换矩阵进行变换为矢量而非点的变换,因此可以变为3x3矩阵。

变换矩阵为子到父的逆矩阵

$$ \begin{bmatrix} |& |& |\\ Xs& Ys& Zs\\ |& |&| \end{bmatrix} $$

x、y、z轴分别为切线空间中顶点的切线、副切线、法线

切线和法线 可以从模型空间中获取

副切线为 切线和法线叉乘的结果

而3个轴为相互垂直的单位向量,因此可以推出 该矩阵为 正交矩阵

$$ \begin{bmatrix} -& Xs& -\\ -& Ys& -\\ -& Zs&- \end{bmatrix} $$

他就是 模型空间到切线空间的变换矩阵


需要使用的内置函数

//得到模型空间光的方向
ObjSpaceLightDir(模型空间顶点坐标);
//得到模型空间视角方向
ObjSpaceViewDir(模型空间顶点坐标);

实现

Shader "Unlit/NormalMapLearn"
{
    Properties
    {
        //主贴图
        _MainTex("MainTex",2D) = ""{}

        //法线
        _BumpMap("BumpMap",2D) = ""{}
        _BumpScale("BumpScale",Range(0,1)) = 1

        //漫反射
        _MainColor("MainColor",Color) = (1,1,1,1)

        //高光反射
        _SpecularColor("SpecularColor",Color) = (1,1,1,1)
        _SpecNum("SpecNum",Range(0.1,20)) = 10
    }
    SubShader
    {
        Pass
        {
            Tags { "LightMode"="ForwardBase"}

            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;
                float3 lightDir:TEXCOORD1;//切线空间光照向量
                float3 viewDir:TEXCOORD2;//切线空间视口向量
            };

            sampler2D _MainTex;//uv贴图
            float4 _MainTex_ST;

            sampler2D _BumpMap;//法线贴图
            float4 _BumpMap_ST;
            float _BumpScale;//凹凸度

            fixed4 _MainColor;//漫反射颜色
            fixed4 _SpecularColor;//高光反射颜色
            float _SpecNum;//光泽度

            v2f vert (appdata_full v)
            {
                v2f data;

                //将模型坐标系下的顶点坐标 转换到裁剪空间下
                data.pos = UnityObjectToClipPos(v.vertex);

                //设置UV坐标(xy存贴图,zw存法线)
                data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //计算模型空间到切线空间的变换矩阵
                float3 binormal = cross(normalize(v.tangent),normalize(v.normal)) * v.tangent.w;//计算副切线向量
                float3x3 rotation = {v.tangent.xyz , binormal, v.normal};

                //计算切线空间下灯光和视角向量
                data.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));
                data.viewDir = mul(rotation,ObjSpaceLightDir(v.vertex));

                return data;
            }

            fixed4 frag (v2f i) : SV_Target
            {

                //法线贴图取样
                float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
                float3 tangentNormal = UnpackNormal(packedNormal);//切线空间下的法线信息
                //凹凸度计算
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                //计算光照(切线空间下) 
                //Lambert漫反射
                fixed3 uvColor = tex2D(_MainTex,i.uv.xy) *  _MainColor;//uv取样 乘漫反射颜色
                fixed3 lambertColor = _LightColor0.rgb * uvColor * max(0,dot(tangentNormal,normalize(i.lightDir)));
                
                //Blinn-Phong高光
                float3 halfA = normalize(i.lightDir + i.viewDir); 
                fixed3 specularColor =_LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(tangentNormal,halfA)),_SpecNum);

                //Blinn-Phong光照模型
                fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor + lambertColor + specularColor;
                return fixed4(color,1);

            }
            ENDCG
        }
    }
}

在世界空间下计算

在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算

关键点:

计算切线空间到世界空间的变换矩阵

计算方式:

$$ \begin{bmatrix} |& |& |\\ Xs& Ys& Zs\\ |& |&| \end{bmatrix} $$

而x、y、z轴分别为切线空间中顶点的切线、副切线、法线

我们只需要得到3个轴相对于世界空间的向量表达,即可得到该变换矩阵


需要使用的内置函数

//法线从模型空间到世界空间:
UnityObjectToWorldNormal(模型空间法线数据);
//切线从模型空间到世界空间: 
UnityObjectToWorldDir(模型空间切线数据)

实现

Shader "Unlit/WNormalMapLearn"
{
    Properties
    {
        //主贴图
        _MainTex("MainTex",2D) = ""{}

        //法线
        _BumpMap("BumpMap",2D) = ""{}
        _BumpScale("BumpScale",Range(0,1)) = 1

        //漫反射
        _MainColor("MainColor",Color) = (1,1,1,1)

        //高光反射
        _SpecularColor("SpecularColor",Color) = (1,1,1,1)
        _SpecNum("SpecNum",Range(0.1,20)) = 10
    }
    SubShader
    {

        Pass
        {
            Tags { "LightMode"="ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "UnityCG.cginc"
            #include "Lighting.cginc"

            struct v2f
            {
                float4 pos:SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 noraml:NORMAL;
                float3 wpos:TEXCOORD1;
                float3x3 rotation:TEXCOORD2;
            };

            sampler2D _MainTex;//uv贴图
            float4 _MainTex_ST;

            sampler2D _BumpMap;//法线贴图
            float4 _BumpMap_ST;
            float _BumpScale;//凹凸度

            fixed4 _MainColor;//漫反射颜色
            fixed4 _SpecularColor;//高光反射颜色
            float _SpecNum;//光泽度

            v2f vert (appdata_full v)
            {
                v2f data;
                data.pos = UnityObjectToClipPos(v.vertex);
                data.noraml = UnityObjectToWorldNormal(v.normal);
                data.wpos = mul(unity_ObjectToWorld,v.vertex);

                data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //计算 切线空间 到切线空间的变换矩阵
                float3 wtangent = UnityObjectToWorldDir(v.tangent);
                float3 wbinormal = cross(normalize(wtangent),normalize(data.noraml)) * v.tangent.w;
                data.rotation = float3x3(   wtangent.x,wbinormal.x,data.noraml.x,
                                            wtangent.y,wbinormal.y,data.noraml.y,
                                            wtangent.z,wbinormal.z,data.noraml.z    );
                return data;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                //取出法线信息
                float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
                float3 tangentNormal = UnpackNormal(packedNormal);
                //凹凸度计算
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
                //变换到 世界坐标系下
                float3 normalDir = mul(i.rotation,tangentNormal);

                //UV
                fixed3 uvColor = tex2D(_MainTex,i.uv.xy);
                uvColor *= _MainColor.rgb;//乘漫反射颜色

                //Lambert
                float3 lightDir = normalize(_WorldSpaceLightPos0);
                fixed3 LambertColor = _LightColor0 * uvColor * max(0,dot(normalDir,lightDir));

                //BlinnPhong高光
                float3 viewDir = normalize(_WorldSpaceCameraPos - i.wpos);
                float3 halfA = normalize(lightDir+viewDir); 
                fixed3 specularColor = _LightColor0 *_SpecularColor * pow(max(0,dot(normalDir,halfA)),_SpecNum);
                
                //BlinnPhong光照模型
                fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT * uvColor + LambertColor + specularColor;
               
                return fixed4(blinnPhongColor,1);
            }
            ENDCG
        }
    }
}

凹凸度系数计算

如果直接将法线*凹凸度系数

当凹凸系数趋近于0时,会影响光照模型的计算

所以只让其法线中的xy乘以凹凸系数

$$ z=\sqrt{1-(x^2+y^2)} $$