Unity Shader法线图使用

发表于2018-06-21
评论0 2.8k浏览
法线图主要是用于低模获得高模的效果。法线图中的法线信息在切线空间或世界空间中的使用,在同一空间下与光照、观察方向计算出来的结果是一致的。

效果

切线空间下使用

1.顶点着色器 中使用顶点中的 法线normal、切线tangent 信息,通过 叉积cross 算出 副切线bitangent(也有人命名为binormal) ,然后通过 这三个向量 构建一个 模型空间 到 切线空间 的变换矩阵,可以将 模型空间 的 向量 变换到 切线空间下,如:将 光照、观察 向量 从 世界空间 变换到 模型空间 再变换到 切线空间,然后传递给 片段着色器

2.片段着色器 中拿到 切线空间 下的 光照、观察 向量,就可以和 法线图解包(UnpackNormal 像素到法线值的转换)出的 法线值 做 点积dot 处理,算出凹凸、反色光等

世界空间下使用

1.顶点着色器 将顶点中的 法线normal、切线tangent 转换到 世界空间下, 通过 叉积cross 算出世界空间下的 副切线bitangent,然后通过 这三个向量 构建一个 切线空间 到 世界空间 的变换矩阵,可以将 切线空间 的 向量 变换到 世界空间下,

2.片段着色器 中,将法线图解包(UnpackNormal 像素到法线值的转换)出的 法线值,通过构建的变换矩阵 装换到世界空间下,和 世界空间 下的 光照、观察 向量 做 点积dot 处理,算出凹凸、反色光等

两种方式的shader代码
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "ITS/test/testFlashLight"
{
    Properties
    {
        _NormalTex ("法线贴图", 2D) = "white" {}
        _Specular ("高光颜色", Color) = (1, 1, 1, 1)
        _Gloss ("高光系数", Range(8, 256)) = 20
        _RimColor ("边缘颜色", Color) = (1, 0, 0, 1)
        _RimPower ("边缘颜色强度", Range(0.1, 1)) = 1
        _MaskTex ("光遮罩图", 2D) = "white" {}
        _MoveDir ("边缘光移动方向", Range(-1, 1)) = 1
        _MainTex ("Base 2d", 2D) = "white" {}
    }
    SubShader
    {
        Tags { "LightMode" = "ForwardBase" }
        Pass
        {
            CGPROGRAM
            #pragma vertex vert2
            #pragma fragment frag2
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };
            struct v2f
            {           
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
            };
            sampler2D _NormalTex;
            fixed4 _Specular;
            float _Gloss;
            fixed4 _RimColor;
            half _RimPower;
            sampler2D _MaskTex;
            sampler2D _MainTex;
            fixed4 _MainTex_ST;
            fixed _MoveDir;
            // 方式一,切线空间下计算
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                TANGENT_SPACE_ROTATION;
                //rotation 是由 顶点法线和切线 计算除 副切线 后,组成的 切线空间的矩阵
                //转换 光线和观察方向 从 世界空间 到 模型空间(ObjSpaceLightDir) 再到 切线空间
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
                //不在这里归一化是为了会不影响 插值结果,所以在 frag 中归一化
                return o;
            }
            fixed4 frag (v2f i) : SV_Target
            {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                //采样 法线贴图,并转换 像素值 为 法线值 (normal = pixel * 2 -1)
                fixed4 packedNormal = tex2D(_NormalTex, i.uv);
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                fixed4 tex = tex2D(_MainTex, i.uv);
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex.rgb;
                //漫反射
                fixed3 diffuse = _LightColor0.rgb * tex.rgb * saturate(dot(tangentNormal, tangentLightDir));
                //Blinn-Phong高光光照模型,相对于普通的Phong高光模型,会更加光
                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);
                //边缘颜色,对于法线和观察方向,只要在同一坐标系下即可
                fixed dotProduct = 1 - saturate(dot(tangentNormal, tangentViewDir));
                fixed3 rim = _RimColor.rgb * pow(dotProduct, 1 / _RimPower);
                fixed4 maskCol = tex2D(_MaskTex, i.uv + float2(0, _Time.y * _MoveDir));
                return fixed4(ambient + diffuse + specular + rim * maskCol.rgb, 1);
                // return fixed4(ambient + diffuse, 1);
            }
            // 方式二,世界空间下计算
            struct appdata2
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
            };
            struct v2f2
            {           
                float4 vertex : SV_POSITION;
                float2 uv : TEXCOORD0;
                float4 TtoW0 :TEXCOORD1;
                float4 TtoW1 :TEXCOORD2;
                float4 TtoW2 :TEXCOORD3;
                float3 lightDir : TEXCOORD4;
                float3 viewDir : TEXCOORD5;
            };
            v2f2 vert2 (appdata2 v)
            {
                v2f2 o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.lightDir = UnityWorldSpaceLightDir(worldPos);
                o.viewDir = UnityWorldSpaceViewDir(worldPos);
                // 构建 法线 从 切线空间 到 世界空间 的 变换矩阵(三个向量)
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
                o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
                o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
                o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z); // 顺便把 世界坐标 也存在这里
                return o;
            }
            fixed4 frag2 (v2f2 i) : SV_Target
            {
                //获得世界空间中的坐标
                float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
                //计算光照和视角方向在世界坐标系中
                fixed3 worldLightDir = normalize(i.lightDir);
                fixed3 worldViewDir = normalize(i.viewDir);
                fixed4 packedNormal = tex2D(_NormalTex, i.uv);
                fixed4 tex = tex2D(_MainTex, i.uv);
                // 法线图 解包 后 的 切线空间的法线向量
                fixed3 tangentNormal = UnpackNormal(packedNormal);
                // 转换 法线向量 从 切线空间 到 世界空间, 等价于 下面注释部分
                fixed3 worldNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal), dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal)));
                /* // 构建 转换矩阵
                float3x3 worldNormalMatrix = float3x3(i.TtoW0.xyz, i.TtoW1.xyz, i.TtoW2.xyz);
                fixed3 worldNormal = normalize(mul(worldNormalMatrix, tangentNormal));
                */
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex.rgb;
                //漫反射
                fixed3 diffuse = _LightColor0.rgb * tex.rgb * saturate(dot(worldNormal, worldLightDir));
                //Blinn-Phong高光光照模型,相对于普通的Phong高光模型,会更加光
                fixed3 halfDir = normalize(worldLightDir + worldViewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(worldNormal, halfDir)), _Gloss);
                //边缘颜色,对于法线和观察方向,只要在同一坐标系下即可
                fixed dotProduct = 1 - saturate(dot(worldNormal, worldViewDir));
                fixed3 rim = _RimColor.rgb * pow(dotProduct, 1 / _RimPower);
                fixed4 maskCol = tex2D(_MaskTex, i.uv + float2(0, _Time.y * _MoveDir));
                return fixed4(ambient + diffuse + specular + rim * maskCol.rgb, 1);
                // return fixed4(ambient + diffuse, 1);
            }
            ENDCG
        }
    }
}
来自:https://blog.csdn.net/yangxuan0261/article/details/79733914

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引