凹凸映射有两种办法:一种办法是高度纹理来模拟表面位移,然后得到一个修改后的法线值。另一种方法是法线纹理来直接存储表面法线.
法线纹理
法线纹理中存储的就是表面的法线方向。法线方向的分量范围在[-1,1],而像素的分量范围在[0,1],因此需要做一个映射:
rgb_normal = normal * 0.5 + 0.5;
因此我们在Shader中对法线纹理进行纹理采样后,还需要对结果进行反映射的过程,得到原先的法线方向,比如:
normal = rgb_noraml x 2 - 1;
法线纹理存储的法线方向在哪个坐标空间中呢?
一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为模型空间的法线纹理。
不过用这样的法线贴图有个问题是你必须记住模型的起始朝向,如果模型运动了还要记录模型的变换,这是非常不方便的,如果
另一个稍微有点难的解决方案是,在一个不同的坐标空间中进行光照,这个坐标空间里,法线贴图向量总是指向这个坐标空间的正z方向;所有的光照向量都相对与这个正z方向进行变换。这样我们就能始终使用同样的法线贴图,不管朝向问题。这个坐标空间叫做切线空间. 因此实际制作,一般采用的是模型顶点的切线空间来存储法线.
一张典型的法线贴图如下:
可以看到这个图片是偏蓝色的,这是因为所有法线的指向都偏向z轴(0, 0, 1)这是一种偏蓝的颜色.
切线空间下的法线纹理
首先要使用法线纹理,我们需要在Properties语义里面添加法线纹理的属性:
_BumpMap("Normal Map",2D) = "bump" {
}
"bump"是Unity内置的法线纹理,当没有提供任何法线纹理时,"bump"就对应了模型自带的法线信息.
其次,我们还添加了一个属性,用来控制凹凸程度:
_BumpScale("Bump Scale", Float) = 1.0
别忘记在Cg代码块中声明对应属性类型匹配的变量.
切线空间是由顶点法线和切线构建出的一个坐标空间,因此我们需要得到顶点的切线信息:
struct a2v
{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;//把顶点的切线方向填充到tangent变量中,注意的是tangent是float4
//需要tangent.w 来决定切线空间中的第三个坐标轴-副切线的方向性
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 * _MainTex_ST.xy + _MainTex_ST.zw; //xy存储_MainTex的纹理坐标
o.uv.zw = v.texcoord * _BumpMap_ST.xy + _BumpMap_ST.zw; //zw存储_MainTex的纹理坐标
//其实_MainTex和_BumpMap通常都会使用同一张纹理坐标的.
//计算切线空间下的光照方向
//第一步求出世界空间到切线空间的变换矩阵,我们已经有了切线、法线,在求一个副切线
//float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz)) * v.tangent.w; //计算副切线,使用w决定方向,因为和切线与法线方向都垂直的方向有两个
有了切线 法线 副切线,就可以得到一个变换矩阵
//float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
TANGENT_SPACE_ROTATION;//内置变量rotation
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;//ObjSpaceLightDir内置函数 得到光照方向
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);
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv.zw));//使用UnpackNormal是为了针对不同压缩格式来对法线纹理进行正确采样.
tangentNormal.xy *= _BumpScale