ShaderLab自学笔记(1):法线贴图和法线空间

ShaderLab自学笔记(1):法线贴图和法线空间,第1张

法线贴图和法线空间
  • 为啥要有切线空间和啥是切线空间
  • 纹理坐标跟位置坐标是啥关系
  • 切线坐标系的求法

1.为啥要有切线空间和啥是切线空间

  1. 为啥要有切线空间?
    3D空间中不同的坐标系有不用的用处。如局部空间坐标系(模型空间坐标系)方便对3D模型进行建模,在这个空间中我们不需要考虑模型在场景中的位置、朝向等细节,可以专注于模型本身。在世界空间下我们关心的问题是场景装各个物体的位置、朝向,而不必关注相机的观察位置及其朝向。可见一个坐标系的根本用途,是让我们在处理不同的问题时,能以合适的参照系,抛开不想管的因素,从降低问题的复杂度。
  2. 啥是切线空间?
    直白的说,模型顶点中的纹理坐标,就定义于切线空间。普通二维坐标系中包含U, V两项,其中U坐标增长的方向为切线空间的Tangent轴,V坐标增长的方向为切线空间的Bitangent轴。模型中不同的三角形都有对应的切线空间,其中Tangent轴和Bitangent轴位于三角形所在的平面上,结合三角形平面对应的法线,我们称Tangent(T),Bitangent(B), Normal(N)组成的坐标系为切线空间(TBN)


2.纹理坐标跟位置坐标是啥关系
通过切线空间可以将纹理坐标和位置坐标联系起来

一个三角形及其所在切线空间,已知这个三角形三个顶点在空间坐标下的位置为V0、V1、V2 , 以及对应的纹理坐标(u0,v0)、(u1,v1)、(u2,v2)。
定义三角形的两条边为:
E0 = V1 - V0
E1 = V2 - V0
对应的纹理坐标差值为:
(t1, b1) = (u1-u0, v1-v0)
(t2,b2) = (u2-u0, v2-v0)
我们可以用T,B的线性组合来表示E0和E1:
E0 = t1T + b1B
E1 = t2T + b2B
3. 切线坐标系的求法
根据上面得到的纹理坐标和切线空间关系可以将其表示为矩阵形式:

将E0, E1, T, B拆分成分量形式:


移到另一边得到:

求逆矩阵公式:

整理后得到:

切线空间包括TBN三个向量,大多数情况下,我们使用的切线空间这三个向量都是准备化且相互垂直的,因此,对于每个顶点,我们只要提供T、N两个向量即可,在运行时通过向量叉乘临时地计算B向量。
4. 法线贴图在Unity中的实际应用

Shader "NormalMap"
{
    Properties
    {
        _MainTex ("_MainTex", 2D) = "white" {}
        _NormalTex ("_NormalTex", 2D) = "white" {}
        _BumpScale("Bump Scale", Float) = 1 //凹凸度系数
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100
        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            // make fog work
            #pragma multi_compile_fog
            #include "UnityCG.cginc"
            #include "Lighting.cginc"
            struct appdata
            {
                float4 vertex : POSITION;   // 告诉Unity把模型空间下的顶点坐标填充给vertex属性
                float3 normal : NORMAL;     // 不再使用模型自带的法线。保留该变量是因为切线空间是通过(模型里的)法线和(模型里的)切线确定的。
                float4 tangent : TANGENT;   // tangent.w用来确定切线空间中坐标轴的方向
                float4 uv : TEXCOORD0;// UV0
            };
            struct v2f
            {
                float4 vertex : SV_POSITION;        // 用来存储顶点在裁剪空间下的坐标
                float3 lightDir : TEXCOORD0;        // 切线空间下,平行光的方向
                float4 uv : TEXCOORD2;              // xy存储MainTex的纹理坐标,zw存储NormalMap的纹理坐标
            };
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _NormalTex;
            float4 _NormalTex_ST;
            float _BumpScale;
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);                  // 使用Unity内置函数将世界空间坐标转化为剪裁空间坐标
                //f.uv = v.texcoord.xy;                                     // 不使用缩放和偏移
                o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw;        // 贴图的纹理坐标
                o.uv.zw = v.uv.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;    // 法线贴图的纹理坐标
                TANGENT_SPACE_ROTATION;                                     // 调用这个宏会得到一个矩阵rotation,该矩阵用来把模型空间下的方向转换为切线空间下
                o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));     //  ObjSpaceLightDir(v.vertex); 得到模型空间下的平行光方向
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb;      //环境光颜色
                // 法线方向。从法线贴图中获取。法线贴图的颜色值 --> 法线方向
                fixed4 normalColor = tex2D(_NormalTex, i.uv.zw);    //法线贴图的颜色值
                fixed3 tangentNormal = UnpackNormal(normalColor);   // 使用Unity内置的方法,从颜色值得到法线在切线空间的方向
                tangentNormal.xy = tangentNormal.xy * _BumpScale;   // 控制凹凸程度
                tangentNormal = normalize(tangentNormal);
                // 光照方向
                fixed3 lightDir = normalize(i.lightDir);
                // 贴图上的颜色
                fixed3 mainColor = tex2D(_MainTex, i.uv.xy);
                // 漫反射Diffuse颜色 = 直射光颜色 * max(0, cos(光源方向和法线方向夹角))  * 材质自身色彩(纹理对应位置的点的颜色
                fixed3 difussColor = _LightColor0 * max(0, dot(tangentNormal,  lightDir)) * mainColor;
                // 最终颜色 = 漫反射 + 环境光
                fixed3 tempColor = difussColor + ambient * mainColor; // 让环境光也跟纹理颜色做融合,防止环境光使得纹理效果看起来朦胧
                return fixed4(tempColor,1);
            }
            ENDCG
        }
    }
}

效果

原图

参考
https://blog.csdn.net/BonChoix/article/details/8619624
https://blog.csdn.net/sixdaycoder/article/details/79015765

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/731974.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-27
下一篇 2022-04-27

发表评论

登录后才能评论

评论列表(0条)

保存