Shader学习第十篇:法线贴图Shader

法线贴图 (Normal Map) 是一种凹凸贴图 (Bump Map)。它们是一种特殊的纹理,可让您将表面细节(如凹凸、凹槽和划痕)添加到模型,从而捕捉光线,就像由真实几何体表示一样。

获取源码

关注公众号:科技探幽
回复shader

法线贴图原理

在模型制作中,我们可以真实的去制作出凹凸感,但是这样会增加模型的面数,增加性能。那么有什么办法,不改模型的面数,就能出现凹凸感呢,那便是使用法线贴图,使用一个2D纹理来储存法线数据。

光照到物体上再通过反射光到人眼,当有凹凸面时,那么反射光线与平面是不一样的,从而产生凹凸感。而反射光线跟物体的法线有关,如果我们修改法线方向,那么反射的光线也会随之改变,当照射到人眼时,便会产生凹凸的感觉,也就模拟了真实的凹凸物体。

现实中我们无法做到,但是在计算机中,我们就可以做到,通过计算,实现一种模拟的凹凸感,用一张2D纹理来存储我们的法线数据,来修复模型的法线,从而实现凹凸的感觉。

那么法线贴图该是什么样的呢?平常我们看到法线贴图通常是这样的。那么为什么会是蓝紫色的呢?
在这里插入图片描述
在这里插入图片描述

在切线空间中,法线的方向使用z轴来表示,法线方向为(0,0,1)。法线向量从z轴方向往其他方向偏移,即修改x,y的值,法线向量方向便发生了变化,同时再经过光照计算得到反射光方向也发生了偏移,便产生了凹凸感。此时通过2d纹理如何来表示这种改变呢?由于法线的范围为-1~1,而颜色的范围为0 ~1,经过下面公式计算得到颜色值

vec3 rgb_normal = (normal + 1)/2; // 从 [-1,1] 转换至 [0,1]

此时颜色值为(0.5,0.5,1),通常软件工具颜色值的范围为0~255,通过软件工具我们查看颜色(128,128,255)
在这里插入图片描述
此时我们修改颜色值,如下图所示,变得到了一个产生凹凸感的颜色值。
在这里插入图片描述
以上便是法线贴图呈现蓝紫色的原因。此时再由法线贴图转为法线向量,即把上面的公式反过来,得到法线向量

vec3 normal  = (rgb_normal)*2-1  从 [0,1]转换至 [-1,1] 

实际应用

我们在Unity Shader中编写程序,来呈现法线贴图如何应用到模型上的。
我们把法线纹理的纹理类型标识成Normal map时,可以使用Unity的内置函数UnpackNormal来得到正确的法线方向,把法线范围设置为-1~1

      fixed3 normalDir = UnpackNormal(noramlColor);

思考:那么为什么我们不直接使用 normal = (rgb_normal)*2-1的计算方法获得法线方向呢?因为无法得到正确的结果。

查看源码看看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;
#elif defined(UNITY_ASTC_NORMALMAP_ENCODING)
    return UnpackNormalDXT5nm(packednormal);
#else
    return UnpackNormalmapRGorAG(packednormal);
#endif
}

当我们把纹理类型设置成Normal map时,Unity根据不同平台对纹理进行压缩(例如使用DXT5nm格式),从而减少减少法线纹理占用的内存空间。UnpackNormal函数内根据不同的压缩格式进行了判断,通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样。如源码中的UnpackNormalDXT5nmUnpackNormalmapRGorAG函数。

完整代码:


Shader "My/tietu2"
{
    Properties
    {
        _MainTex("Main Tex",2D) = "white"{}
        _NormalMap("Normal Map",2D) = "bump"{}
        _Range("Range",Range(0,1)) = 0.5
    }
    SubShader
    {
       Tags{"LightMode" = "ForwardBase" }
        

        Pass{
            CGPROGRAM
            #include "Lighting.cginc"
       
            #pragma vertex vert;
            #pragma fragment frag;
            float _Range;
            sampler2D _MainTex;
    
            float4 _MainTex_ST;
            sampler2D _NormalMap;
            float4 _NormalMap_ST;
            struct a2v
            {
                float4 vertex:POSITION;
                float4 texcoord:TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f
            {
                 float4 uv:TEXCOORD0;
                 float4 svPos:SV_POSITION;
                 float3 normal:TEXCOORD1;
                 float3 lightDir:TEXCOORD2;
            };


            v2f vert(a2v v)
            {
               v2f f;
                f.svPos = UnityObjectToClipPos(v.vertex);
                f.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                f.uv.zw = v.texcoord.xy*_NormalMap_ST.xy+_NormalMap_ST.zw;;
                // f.normal = UnityObjectToWorldNormal(v.normal);
                TANGENT_SPACE_ROTATION;//调用之后,会得到一个矩阵rotation,这个矩阵用来把模型空间下的方向转换为切线空间
                f.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));//把光从模型空间,转为切线空间
                return f;
            }
            fixed4 frag(v2f f):SV_Target{
                fixed3 texColor = tex2D(_MainTex,f.uv.xy);
    
                half4 noramlColor = tex2D(_NormalMap,f.uv.zw);
                 fixed3 normalDir = UnpackNormal(noramlColor);
                 normalDir = normalize(normalDir);
                fixed3 lightDir = normalize(f.lightDir);
                fixed3 texColo =  _LightColor0.rgb*texColor*max(0,dot(normalDir,lightDir)*0.5+0.5);
                fixed3 color = texColo+UNITY_LIGHTMODEL_AMBIENT.rgb;
                return fixed4(color,1);
            }

         
            ENDCG
        }
     
    }
    FallBack "Diffuse"
}

调整凹凸感

上面我们已经实现的法线贴图,那么一张法线贴图,我们可以调整凹凸感吗,答案是肯定的。下面我们来实现。
我们让xy的值乘以一个系数,增大xy方向值,偏移后的法线是归一化的,因此满足x2 + y2 + z2 = 1。xy值增大,则z方向的值减小,凹凸感越大;当xy方向的值越小,越趋近于0时,表面越光滑。
关键程序

                fixed3 normalDir = UnpackNormal(noramlColor);
                normalDir.xy = normalDir.xy*_BumpScale;
                //(dot(xy,xy))=x*x+y*y
		     	//由于偏移后的法线是归一化的,因此满足x2 + y2 + z2 = 1
			    //所以z=sqrt(1-(x2+y2))
                normalDir.z = sqrt(1.0 - saturate(dot(normalDir.xy,normalDir.xy)));
                normalDir = normalize(normalDir);

Shader "My/tietu3"
{
    Properties
    {
        _MainTex("Main Tex",2D) = "white"{}
        _NormalMap("Normal Map",2D) = "bump"{}
        _BumpScale("Bump Scale",Float) =1
    }
    SubShader
    {
       Tags{"LightMode" = "ForwardBase" }
        

        Pass{
            CGPROGRAM
            #include "Lighting.cginc"
       
            #pragma vertex vert;
            #pragma fragment frag;
            sampler2D _MainTex;
    
            float4 _MainTex_ST;
            sampler2D _NormalMap;
            float4 _NormalMap_ST;
            float _BumpScale;
            struct a2v
            {
                float4 vertex:POSITION;
                float4 texcoord:TEXCOORD0;
                float3 normal:NORMAL;
                float4 tangent:TANGENT;
            };

            struct v2f
            {
                 float4 uv:TEXCOORD0;
                 float4 svPos:SV_POSITION;
                 float3 normal:TEXCOORD1;
                 float3 lightDir:TEXCOORD2;
            };


            v2f vert(a2v v)
            {
               v2f f;
                f.svPos = UnityObjectToClipPos(v.vertex);
                f.uv.xy = v.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                f.uv.zw = v.texcoord.xy*_NormalMap_ST.xy+_NormalMap_ST.zw;;
                // f.normal = UnityObjectToWorldNormal(v.normal);
                TANGENT_SPACE_ROTATION;//调用之后,会得到一个矩阵rotation,这个矩阵用来把模型空间下的方向转换为切线空间
                f.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));//把光从模型空间,转为切线空间
                return f;
            }
            fixed4 frag(v2f f):SV_Target{
                fixed3 texColor = tex2D(_MainTex,f.uv.xy);
            
                half4 noramlColor = tex2D(_NormalMap,f.uv.zw);
                fixed3 normalDir = UnpackNormal(noramlColor);
                normalDir.xy = normalDir.xy*_BumpScale;
                //(dot(xy,xy))=x*x+y*y
		     	//由于偏移后的法线是归一化的,因此满足x2 + y2 + z2 = 1
			    //所以z=sqrt(1-(x2+y2))
                normalDir.z = sqrt(1.0 - saturate(dot(normalDir.xy,normalDir.xy)));
                normalDir = normalize(normalDir);
                
                fixed3 lightDir = normalize(f.lightDir);
                fixed3 texColo =  texColor*max(0,dot(normalDir,lightDir)*0.5+0.5);
                fixed3 color = texColo+UNITY_LIGHTMODEL_AMBIENT.rgb;
                return fixed4(color,1);
            }

         
            ENDCG
        }
     
    }
    FallBack "Diffuse"
}

应用到Unity如下图所示,调整Bump Scale的值调整凹凸感
在这里插入图片描述

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小~小

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值