UnityShader12:Unity中应用法线贴图

一、关于法线贴图和切线空间

如果之前在 OpenGL 或者 directX 中成功应用过法线贴图,那么在 U3D 中实现就容易多了 

  1. 切线空间:https://blog.csdn.net/Jaihk662/article/details/107917594
  2. 法线贴图:https://blog.csdn.net/Jaihk662/article/details/108102673

里面已经做过好理解且详细的介绍了,可以只关心理论部分

二、Unity中应用法线贴图

因为法线存储于切线空间中,因此有两种方法计算最终的光照效果:

  1. 顶点着色器中计算向量:将光照方向和观察方向变换到切线空间中,这样做效率高
  2. 片段着色器中计算向量:将法线信息变换到世界空间中,这样做通用性好,可以更好的做一些其它的计算

对于①代码如下,里面有注释:

Shader "Jaihk662/NewSurfaceShader"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex ("MainTex", 2D) = "white" {}
        _NormalMap ("NormalMap", 2D) = "bump" {}        //bump为模型自带法线信息
        _NormalScale ("NormalScale", float) = 1.0       //控制法线贴图展现的凹凸效果,0~1
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }

            CGPROGRAM
            #pragma vertex vert             //声明顶点着色器的函数
            #pragma fragment frag           //声明片段着色器的函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            sampler2D _MainTex;
            float _Gloss;
            sampler2D _NormalMap;
            float _NormalScale;

            float4 _NormalMap_ST;
            float4 _MainTex_ST;

            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 tangent: TANGENT;                //targent.w表示副切线的方向
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float4 uv: TEXCOORD0;                   //用一个float4同时存储两张纹理的uv坐标
                float3 lightTDir: TEXCOORD1;
                float3 viewTDir: TEXCOORD2;
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
                
                TANGENT_SPACE_ROTATION;                 //内置宏TANGENT_SPACE_ROTATION可以直接帮我们计算得到切线空间变换矩阵rotation
                        //其中TANGENT_SPACE_ROTATION在UnityCG.cginc中被定义,也可以自己进行计算:
                        //float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
                        //float3x3 rotation = float3x3(v.tangent.xyz, binormal, normal)
                o.lightTDir = normalize(mul(rotation, ObjSpaceLightDir(v.vertex)));
                o.viewTDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)));
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv.zw));       //内置函数UnpackNormal自动帮我们将法线向量由[0,1]映射到[-1,1]
                normal.xy *= _NormalScale;
                normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));     //反正是标准化过的单位向量,可以自己由xy算出z的值

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb;
                fixed3 wLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb * saturate(dot(normal, i.lightTDir));
                
                fixed3 reflectDir = normalize(reflect(-i.lightTDir, normal));
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, i.viewTDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0); 
            } 
            ENDCG
        }
    }
    FallBack "Specular"
}

一样可以很容易得看出来差距:

对于②的代码:

Shader "Jaihk662/NewSurfaceShader"
{
    Properties
    {
        _DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
        _MainTex ("MainTex", 2D) = "white" {}
        _NormalMap ("NormalMap", 2D) = "bump" {}        //bump为模型自带法线信息
        _NormalScale ("NormalScale", float) = 1.0       //控制法线贴图展现的凹凸效果,0~1
        _Gloss ("Gloss", Range(8.0, 256)) = 20
    }
    SubShader
    {
        LOD 200
        PASS 
        {
            Tags { "LightMode" = "ForwardBase" }

            CGPROGRAM
            #pragma vertex vert             //声明顶点着色器的函数
            #pragma fragment frag           //声明片段着色器的函数
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
 
            fixed4 _DiffuseColor;
            fixed4 _SpecularColor;
            sampler2D _MainTex;
            float _Gloss;
            sampler2D _NormalMap;
            float _NormalScale;

            float4 _NormalMap_ST;
            float4 _MainTex_ST;

            struct _2vert 
            {
                float4 vertex: POSITION;
                float3 normal: NORMAL;
                float4 tangent: TANGENT;                //targent.w表示副切线的方向
                float4 texcoord: TEXCOORD0;
            };
            struct vert2frag 
            {
                float4 pos: SV_POSITION;
                float4 uv: TEXCOORD0;                   //用一个float4同时存储两张纹理的uv坐标
                float4 TtoW1: TEXCOORD1;
                float4 TtoW2: TEXCOORD2;
                float4 TtoW3: TEXCOORD3;
                //float3 wPos: TEXCOORD4;               //不需要了,把wPos的三个向量xyz分别放入TtoW1、TtoW2、TtoW3的第四个参数w中
            };
 
            vert2frag vert(_2vert v) 
            {
                vert2frag o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
                
                float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
                float3 wNormal = UnityObjectToWorldNormal(v.normal);
                float3 wTangent = UnityObjectToWorldDir(v.tangent);
                float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
                o.TtoW1 = float4(wTangent.x, wBinormal.x, wNormal.x, wPos.x);
                o.TtoW2 = float4(wTangent.y, wBinormal.y, wNormal.y, wPos.y);
                o.TtoW3 = float4(wTangent.z, wBinormal.z, wNormal.z, wPos.z);
                return o;
            }
            fixed4 frag(vert2frag i): SV_Target 
            {
                float3 wPos = float3(i.TtoW1.w, i.TtoW2.w, i.TtoW3.w);
                fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv.zw));       //内置函数UnpackNormal自动帮我们将法线向量由[0,1]映射到[-1,1]
                normal.xy *= _NormalScale;
                normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy)));     //反正是标准化过的单位向量,可以自己由xy算出z的值
                normal = normalize(half3(dot(i.TtoW1.xyz, normal), dot(i.TtoW2.xyz, normal), dot(i.TtoW3.xyz, normal)));

                fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(wPos));        //同等与normalize(_WorldSpaceLightPos0.xyz);
                fixed3 wViewDir = normalize(UnityWorldSpaceViewDir(wPos));          //同等于normalize(UnityWorldSpaceViewDir(i.wPos));
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb;
                fixed3 diffuse = _LightColor0.rgb * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb * saturate(dot(normal, wLightDir));
                
                fixed3 reflectDir = normalize(reflect(-wLightDir, normal));
                fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, wViewDir)), _Gloss);

                return fixed4(ambient + diffuse + specular, 1.0); 
            } 
            ENDCG
        }
    }
    FallBack "Specular"
}

三、法线纹理类型

在应用法线贴图前,需要正确设置法线的纹理类型:

其中对于 NormalMap,多了一个 CreateFromGrayscale 的复选框,它可以根据高度图生成对应的法线贴图,如果你导入的是高度图,那么就可以通过勾选这个选项来将其当成法线贴图对待,勾选后还会多出一些设置:

  • Bumpiness:控制凹凸程度
  • Filtering:使用哪种方式计算凹凸程度,关于高度图转法线图的算法,可以参考 Sobel 滤波

设置为 NormalMap,后,就可使用 Unity 的内置函数 UnpackNormal 来获得正确的法线方向,对于不同的纹理压缩算法它都能给出正确的纹理采样方法:

下面是它的内部实现

inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
    return normal;
}

// Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
// Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5
fixed3 UnpackNormalmapRGorAG(fixed4 packednormal)
{
    // This do the trick
   packednormal.x *= packednormal.w;

    fixed3 normal;
    normal.xy = packednormal.xy * 2 - 1;
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
    return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#else
    return UnpackNormalmapRGorAG(packednormal);
#endif
}

猜你喜欢

转载自blog.csdn.net/Jaihk662/article/details/111565760