文章

shader入门基础-凹凸映射

shader入门基础-凹凸映射

本文内容主要参考 《Unity shader 入门精要》一书,旨在总结所学知识与加深个人理解

凹凸映射

使用一张纹理来修改模型表面的法线,为模型提供更多细节。

使用法线纹理进行凹凸映射

法线方向的分量范围在[-1,1],像素分量范围为[0,1],法线到像素的映射 $pixel = \frac{normal+1}{2}$. 我们在 shader 中对法线纹理采样后,需要进行反映射 $normal=pixel\ast 2-1$。

模型空间的法线纹理(object-space normal map)

定义:模型空间中的表面法线存储在一张纹理中。 优点:

  • 实现简单,直观。
  • 纹理坐标缝合处和尖锐的边角部分,课件突变较少,可以提供平滑的边界。

切线空间的法线纹理(tangent-space normal map)

定义: 模型定点的切线空间来存储法线,对于模型的每个定点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身, 而 z 轴是顶点的法线方向(n),x 轴是顶点的切线方向(t),而 y 轴可由法线和切线叉积而得,也被称为副切线。

切线空间法线纹理

优点:

  • 自由度高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型。切线空间下的纹理记录的是相对法线信息, 意味着,把纹理应用到完全不同的网格上,也可以得到合理结果。
  • 可进行 UV 动画。我们可以移动一个纹理的 UV 坐标来实现凹凸移动的效果,但使用模型空间下的法线纹理得到完全错误的结果。这种 UV 动画在水 或者火山熔岩。
  • 可以重用法线纹理。
  • 可压缩。

在切线空间下计算光照模型

实现:在片元着色器中通过纹理采样得到切线空间的法线,然后在于切线空间下的视角方向,光照方向等进行计算。 需要在顶点着色器中把视角方向,光照方向从来模型空间变换到切线空间中。即我们需要知道的从模型空间到切线空间的变换 矩阵。这个变换矩阵的逆矩阵,在顶点着色器中按切线(x 轴),副切线(y 轴),法线(z 轴)的顺序按列排列。在 UnityCG.cginc 中 定义了 TANGENT_SPACE_ROTATION

实现

_BumpMap,使用”bump”作为它的默认值。”bump”是 Unity 内置法线纹理,当没有提供任何法线纹理时,bump 对应模型自带法线信息。 _BmupScale 控制凹凸程度,0 则意味该法线纹理不会对光照产生任何影响。 tangent.w 分量决定切线空间中的第三个坐标轴-副切线的方向性。 法线纹理中存储的是把法线经过映射后得到的像素值,因此需要把他们反映射出来。

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
81
82
83
Shader "Practice/NormalMapTagSpace"
{
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)// 漫反射颜色
        _MainTex ("Main Tex", 2D) = "white" {}//贴图
        _BumpMap("Bump Map",2D) = "bump" {}//法线贴图
        _BumpScale("Bump Scale",float) = 1.0//凹凸程度
        _Specular("Specular",Color)= (1,1,1,1)//高光颜色
        _Gloss("Gloss",Range(8,256)) = 20 //光泽度
    }
    SubShader
    {
        Pass{
            Tags { "LightModel" = "ForwardBase" }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            sampler2D _BumpMap;//法线纹理
            float4 _MainTex_ST;
            float4 _BumpMap_ST;//法线纹理属性
            fixed4 _Specular;
            float _Gloss;
            float _BumpScale;//凹凸程度

            struct a2v{
                float4 vertex:POSITION; //模型顶点信息
                float3 normal:NORMAL;//法线
                float4 tangent:TANGENT;//切线
                float4 texcoord:TEXCOORD0;//纹理
            };

            struct v2f {
                float4 pos:SV_POSITION;
                float4 uv:TEXCOORD0;//纹理映射坐标
                float3 lightDir:TEXCOORD1;//光线
                float3 viewDir:TEXCOORD2;//视线
            };

            v2f vert(a2v i){
                v2f o;
                o.pos = UnityObjectToClipPos(i.vertex);
                //减少插值寄存器的 xy存储_MainTex的纹理坐标 ,zw存储_BumpMap坐标
                o.uv.xy =i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
                o.uv.zw = i.texcoord.xy*_BumpMap_ST.xy+_BumpMap_ST.zw;
                //计算副切线
                float3 binormal = cross(normalize(i.normal),normalize(i.tangent.xyz))*i.tangent.w;
                float3x3 rotation = float3x3(i.tangent.xyz,binormal,i.normal);
                //获取到顶点坐标的光线防线后,把光线方向从模型空间转换到切线空间
                o.lightDir = mul(rotation,ObjSpaceLightDir(i.vertex)).xyz;
                o.lightDir = mul(rotation,ObjSpaceViewDir(i.vertex)).xyz;
                return o;
            }

            fixed4 frag(v2f i):SV_TARGET
            {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);
                fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
                fixed3 tangentNormal;
                tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal,tangentViewDir)));
                fixed3 albedo = tex2D(_MainTex,i.uv).rgb*_Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
                fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
                fixed3 halfDir = normalize(tangentLightDir+tangentViewDir);
                fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss);
                return fixed4(ambient+diffuse+specular,1.0);
            }


            ENDCG
        }

    }
    Fallback "Diffuse"
}

切线空间下计算的 凹凸映射 切线空间法线纹理

本文由作者按照 CC BY 4.0 进行授权