凹凸映射,在不改变顶点位置的前提下,修改模型表面的法线方向,为模型提供更多的细节。
使用一张高度纹理
来模拟表面位移(Displacement),然后得到一个修改后的法线值。此方法也叫做高度映射(Height Mapping)
。
颜色越浅,越向外凸;颜色越深,越向内凹。能明确表面的凹凸信息
,缺点是计算复杂。
使用一张法线纹理
来直接存储表面法线。此方法也叫做法线映射(Normal Mapping)
。用于存储表面的法线向量
,法线向量的取值范围为 [ -1 , 1] 。但是像素分量的取值范围是 [ 0 , 1],因此需要进行以下两个映射:
UnpackNormal
是unity内置的函数(在后面⑥定义片元着色器中会出现)。当我们把贴图纹理设置为Normal map类型时,该函数可以得到正确的法线方向。
不仅如此,Unity还可以根据不同平台来调整Normal的细节,使用UnpackNormal函数针对不同压缩格式对法线纹理进行正确采样。(目前存在DXT1、DXT5、DXT5nm的格式,DXT3以及被弃用——Tech-Artists)
可以在UnityCG.cginc之中找到UnpackNormal的具体定义:
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalmapRGorAG(packednormal);
#endif
}
代码中可以看到我们可爱的Unity并不能识别除了DXT5nm以外的贴图格式,因此翻阅官方文档然后直接Ctrl-F搜索DXT得到如下结果:
说明在unity之中可以有2个选择,包括XYZ和DXT5nm,且更改法线编码时,最好直接在Unity的项目设置中,而不是在CG Shader代码中,否则解码成本会增加。
Create from Grayscale
的选项。这个选项的主要作用是可以将我们的先前讲到的 高度图 转换为 法线贴图,方便我们进行法线处理。Sobel滤波
来生成法线
对于先前的纹理映射和纹理贴图之中,我们可以直到,要在CG语言中实现相应效果,不可或缺的是纹理的坐标空间(是ObjectSpace还是WorldSpace?)。而对于法线纹理的坐标空间来说,有个直接点的想法是——将修改后的模型空间中的表面法线存储在一张纹理中。(这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。代码部分会加以证明。)这种纹理被称为——模型空间的法线纹理(Object-Space Normal Map)。
然而,实际上我们真正使用的是——模型顶点的切线空间(Tangent Space)。具体解释如下:
对于模型的每一个顶点,都有属于自己的切线空间,这个切线空间的原点就是顶点本身。
Z轴
是顶点的法线
方向 n
X轴
是顶点的切线
方向 t
Y轴
是由顶点的切线和法线叉积
而得
代码开始
Properties{
_Color("Base Color",Color) = (1,1,1,1)
_MainTex("Main Tex",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
}
_BumpMap,默认值可以使用“Bump”——Unity内置法线纹理。
_BumpScale为0时,意味着该法线纹理不会起作用。
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"}
#pragma vertex vert
#pragma pragment prag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
根据上面的代码,可以看到,_BumpMap和普通纹理类似,同样也声明了一个_ST变量,这也正说明我们前面的理解是正确的——这样一来就可以结合前面“纹理贴图”一样实现凹凸效果。
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;
};
这里的TANGENT就是我们所说的切线。Unity会像传送POSITION一样,把TANGENT所包含的每个顶点的切线方向填充到tangent之中
,但是需要注意的是,和法线方向的normal不同,tangent的类型是float4,而非float3.是因为其额外多了一个tangent.w
分量来决定切线空间的第三个坐标轴——副切线的方向性。
v2f vert(a2v v) {
v2f o;
o.pos = 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;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));
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.xy, tangentNormal.xy)));
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);
}
Shader "LeonShader/Shader_7_2_HeightMap"{
Properties{
_Color("Base Color",Color) = (1,1,1,1)
_MainTex("Main Tex",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{
Pass {
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Specular;
float _Gloss;
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 v) {
v2f o;
o.pos = 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;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
o.lightDir = mul(worldToTangent, WorldSpaceLightDir(v.vertex));
o.viewDir = mul(worldToTangent, WorldSpaceViewDir(v.vertex));
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.xy, tangentNormal.xy)));
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 "Specular"
}