• 法线贴图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]
    
    • 1

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

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

    实际应用

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

          fixed3 normalDir = UnpackNormal(noramlColor);
    
    
    • 1
    • 2

    思考:那么为什么我们不直接使用 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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    当我们把纹理类型设置成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"
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    调整凹凸感

    上面我们已经实现的法线贴图,那么一张法线贴图,我们可以调整凹凸感吗,答案是肯定的。下面我们来实现。
    我们让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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    
    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"
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

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

  • 相关阅读:
    8月算法训练------第七天(哈希表)解题报告
    Linux设备驱动模型之devicetree
    JVM(一)
    通用型安全监测数据管理系统
    深入详解Mybatis的架构原理与6大核心流程
    第2章 持久化初始数据到指定表
    07_项目开发_用户信息列表
    linux挖矿病毒kthreaddk横行,如何灭掉它?
    从零学习 InfiniBand-network架构(七) ——IB协议中数据如何传输
    笔记1.6:计算机网络发展历史
  • 原文地址:https://blog.csdn.net/u014196765/article/details/127727566