方法一:使用一张高度纹理来模拟表面位移,然后得到一个修改后的法线值,这种方法也叫做高度映射
方法二:使用一张法线纹理来直接存储表面法线,这种方法叫做法线映射
高度纹理:高度图中存储的是强度值,用于表示模型表面局部的海拔高度,颜色越浅表示越向外凸起,而颜色越深表明该位置越向里凹
优点:更加直观的知道一个模型表面的凹凸情况
缺点:计算更复杂,不能直接得到表面法线,而是要由像素的灰度值计算而得

法线纹理:法线纹理中存储的是法线方向。由于法线方向的分量范围是在[-1,1],而像素的分量为[0,1],因此我们需要做一个映射,通常:
pixel = (normal + 1) / 2
这就要求我们在对法线纹理采样之后进行一次反映射得到原来的法线方向 normal = pixed * 2 - 1
对于模型每个顶点都有切线空间,这个切线空间的原点就是该顶点本身,而Z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而Y轴可由法线和切线的叉积而得,也称副切线或者副法线
模型空间来存储法线的优点:
实现简单,更加直观,计算少,生成也非常简单。而如果要生成切线空间下法线纹理,由于模型的切线一般是和UV方向相同,因此想要得到效果比较好的法线映射就要求纹理映射也是连续的。
切线空间来存储法线的优点:
自由度很高。模型空间下法线纹理记录的是绝对法线信息,仅可以用于创建它时的那个模型,而应用到其他模型上效果就完全错误了。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即便吧该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果
可以进行UV动画,比如可以移动纹理的UV坐标来实现一个凹凸移动的效果,这种UV动画在水或者火山熔岩这种类型的物体上会经常用到。
可以重用法线纹理
可以压缩。由于切线空间下的法线纹理中法线的Z方向总是正的所以我们仅存XY方向即可


- Shader "Unlit/NormalMapTangentSpace"
- {
- Properties
- {
- _MainTex("Texture",2D) = "white" {}
- _BumpMap("Normal Map",2D) = "bump" {} //对应了模型自带的法线信息
- _BumpScale("Bump Scale",Float) = 1.0 //控制凹凸程度
- _Specular("Specular",Color) = (1,1,1,1)
- _Gloss("Gloss",Range(8.0,256)) = 20
- }
- SubShader
- {
- Tags { "LightMode" = "ForwardBase" }
- LOD 100
-
- Pass
- {
- CGPROGRAM
- #pragma vertex vert
- #pragma fragment frag
-
- #include "Lighting.cginc"
- #include "UnityCG.cginc"
-
- struct appdata
- {
- float4 vertex : POSITION;
- float3 normal:NORMAL;
- float4 texcoord:TEXCOORD0;
- float4 tangent:TANGENT;
- };
-
- struct v2f
- {
- float4 uv : TEXCOORD0;
- float4 vertex : SV_POSITION;
- float3 lightDir:TEXCOORD1;
- float3 viewDir:TEXCOORD2;
- };
-
- sampler2D _MainTex;
- sampler2D _BumpMap;
- float _BumpScale;
- fixed4 _Specular;
- float _Gloss;
- float4 _MainTex_ST;
- float4 _BumpMap_ST;
-
- v2f vert(appdata v)
- {
- v2f o;
- o.vertex = UnityObjectToClipPos(v.vertex);
- o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
- o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
- //副法线方向 = corss(法线,切线) x:切线方向 y:顶点法线方向 z:副法线
- //与W相乘是因为与xy垂直的方向有两个 w决定哪个方向
- float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
- //从模型空间转换到切线空间的矩阵
- float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);//TANGENT_SPACE_ROTATION
-
- //灯光方向转到切线空间
- o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
- o.viewDir = mul(rotation, ObjSpaceViewDir(v.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;
- //根据法线贴图反映射得到原切线数据 像素[0,1] 法线[-1,1]
- tangentNormal.xy = UnpackNormal(packedNormal); //(packedNormal.xy * 2 - 1);
- tangentNormal.xy *= _BumpScale;
- //保证法线方向的z分量为正
- tangentNormal.z = sqrt(1 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
-
- fixed3 albedo = tex2D(_MainTex, i.uv);
-
- fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tangentNormal, tangentLightDir));
-
- fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
-
- //反射方向
- fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);
- fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
-
- return fixed4(ambient + diffuse + specular,1);
- }
- ENDCG
- }
- }
- }