我的第一个法线贴图

  2018.1.16更新:在看一本书时,里面提到了“增强法线贴图的强度”:采样normalTex后,归一化前,让法线分量的x和y乘以一个强度参数:

  normal = normal*fixed3(param,param,1);,然后再归一化,当param>1时,模型更加“凹凸”了,原因是:如果没凹凸,则normal=(0,0,1)(可以参考深入理解法线贴图),x和y乘以一个>1的数后,normal的z分量被“稀释”,即越发地偏离(0,0,1),即光照计算结果变大,所以就更加凹凸了。

  2017.11.8更新:以前不懂tangent.w的含义,读此文得到:“where m = ±1 represents the handedness of the tangent space”,大意是不同模型使用左手或右手螺旋得到B,所以必须根据模型确定B的方向,所以就把这个方向(±1)存到tangent.w里了;

  另外需要提及的是:

  下面的:

  float3x3 objectToTanM = float3x3(tangent, binnormal, normal);//tangent是行向量
  mul(objectToTanM, ObjSpaceLightDir(v.vertex));

  这里有几个点被省略了。因为这里的第二行使用的是矩阵右乘向量,所以构建切线空间基矩阵时应当使用列向量矩阵:[T,B,N](都是列向量),我们要转换向量到切线空间则需使用他的逆,又因为[T, B, N]基本正交,所以直接使用它的转置作为转换矩阵:[T', B', N'](T'是行向量),这就是上面构建objectToTanM这个转到切线空间矩阵所对应的。

  同理,如果第二行使用的是mul(ObjSpaceLightDir(v.vertex), objectToTanM),则是向量左乘变换矩阵,所以空间基应当是行向量矩阵:[T',B',N'],其逆用其转置代替:[T,B,N](都是列向量),这样objectToTanM就需要在原来的基础上转置一下了。

先上简单的surf版法线贴图:

Shader "Custom/NormalSurfShader" {
    Properties {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _NormalMap("Normal Map", 2D) = "white" {}
    }
    SubShader {
        Tags { "RenderType"="Opaque" }
        LOD 200
        
        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf BlinnPhong fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0
        
        struct Input {
            float2 uv_MainTex;
            float2 uv_NormalMap;
        };

        sampler2D _MainTex;
        sampler2D _NormalMap;
        fixed4 _Color;

        void surf (Input IN, inout SurfaceOutput o) {
            o.Albedo = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Normal = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap));
        }
        ENDCG
    }
    FallBack "Diffuse"
}

效果:

然后上顶点shader:

Shader "Unlit/NormalVFShader"
{
    //法线贴图是,在主贴图基础上,先算出在切线空间的光照方向lightDir,视线方向viewDir
    //接着,通过Normal Map采样得到各个顶点的在切线空间的法线Normal,然后由此三者根据光照模型
    //算出该点漫反射和高光,则该点的像素值就是diff+specular+ambient(环境光)
    //所以法贴是为了计算漫反射和高光,即为光照模型服务。surf shader把这个过程都封装起来了。
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _NormalMap("Normal Map", 2D) = "white" {}
        _SpecColor("Specular Color", Color) = (1,1,1,1)
        _Shininess("Shininess", Float) = 228
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 lightDir: TEXCOORD1;
                float3 viewDir  : TEXCOORD2;
            };

            sampler2D _MainTex;
            sampler2D _NormalMap;
            float4 _SpecColor;
            float _Shininess;
            uniform float4 _LightColor0;
            float4 _MainTex_ST;
            
            v2f vert (appdata_tan v)
            {
                v2f o;
                o.vertex = mul(UNITY_MATRIX_MVP, v.vertex);
                o.uv = TRANSFORM_TEX(v.texcoord.xy, _MainTex);
                //下面的6句是为了得到切线空间的光照方向和视角方向,为什么要转到切线空间?因为法贴中的法线是以切线空间坐标存的,
                //后面计算漫反射,高光需要他们统一坐标系,所以其实也可以把大家(3者)都转到世界坐标系,但那样效率估计比这样慢一点
                float3 normal = v.normal;
                float3 tangent = v.tangent;
                float3 binnormal = cross(normal, tangent) * v.tangent.w;//为什么乘tangent.w?记住就好
                float3x3 objectToTanM = float3x3(tangent, binnormal, normal);
                //TANGENT_SPACE_ROTATION 这个宏可以代替上面4句
                o.lightDir = mul(objectToTanM, ObjSpaceLightDir(v.vertex));
                o.viewDir = mul(objectToTanM, ObjSpaceViewDir(v.vertex));
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                // sample the texture
            fixed4 col = tex2D(_MainTex, i.uv);
            float3 ambient = col * UNITY_LIGHTMODEL_AMBIENT.rgb;
            fixed4 encodeNormal = tex2D(_NormalMap, i.uv);//切线空间的法线
            fixed4 normal = encodeNormal * 2 - 1;//(0-1)=》(-1,1),
            normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));//解压出z
            //漫反射
            // I = k*texColor*LightColor*(NL),这里的texColor我是抄Lighting.cginc里的
            float3 diff = col.rgb * _LightColor0.rgb * max(0, dot(normal, i.lightDir)) * 2;//*2是我自己加的,为了亮一点
            //计算高光,使用Blinn-Phong光照模型
            //                      (ns)
            //I = k*LightColor*(NH) , H = (L+V)/|L+V|
            float3 H = normalize(i.lightDir+i.viewDir);
            float3 specular = _SpecColor * _LightColor0 * pow(max(0, dot(normal, H)), _Shininess);
            return float4(ambient + diff + specular, col.a);
            }
            ENDCG
        }
    }
}

效果:

当然,因为资源、光照等原因,这样的对比是完全不靠谱的,但我想说的是,vfshader是可控的,这点就足够好了,关键注释都在代码里了。

原文地址:https://www.cnblogs.com/Tearix/p/6860641.html