纹理的另一种场景的应用就是凹凸映射。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是"凹凸不平"的,可以从模型的轮廓处看出“破绽”。
有两种主要的方法可以用来进行凹凸映射:
注意:
采样获取法线
fixed3 bump = UnpackNormal(tex2D(_BumpMap, v.uv.zw));
//上计算等价于
fixed4 packedNormal = tex2D(_BumpMap, v.uv.zw);
fixed3 tangentNormal;
tangentNormal.xy = (packedNormal.xy * 2 - 1) *_BumpScale;
tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy)));
原因:纹理坐标中只记录 xy,z 需要计算得到。而且 xy 是经过映射的 pixed = (normal + 1) / 2,需要首先进行反映射,然后求 z。
inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
fixed3 normal;
normal.xy = packednormal.wy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
// Unpack normal as DXT5nm (1, y, 1, x) or BC5 (x, y, 0, 1)
// Note neutral texture like "bump" is (0, 0, 1, 1) to work with both plain RGB normal and DXT5nm/BC5
fixed3 UnpackNormalmapRGorAG(fixed4 packednormal)
{
// This do the trick
packednormal.x *= packednormal.w;
fixed3 normal;
normal.xy = packednormal.xy * 2 - 1;
normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));
return normal;
}
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalmapRGorAG(packednormal);
#endif
}
从代码中可以看到,在 DXT5nm 格式的法线纹理中,纹素为 (1, y, 1, x);在 BC5 格式中则为(x, y, 0, 1) 。
法线纹理中存储的就是表面的法线方向。由于法线方向的分量范围在[-1,1],而像素的分量范围为[0, 1],因此需要做一个映射,通常使用的映射就是:
模型空间的法线纹理和切线空间的法线纹理
计算光照模型,需要统一各个方向矢量所在的坐标空间。由于法线纹理中存储的法线是切线空间下的方向,所以有两种选择:
在切线空间下计算
Shader "Custom/s7_2"
{
Properties
{
// 纹理贴图代替漫反射
_Color("Color",color)=(1,1,1,1)
_MainTex("Main Tex",2D)="white"{}
// 高光反射
_Specular("Specular",color)=(1,1,1,1)
_Gloss("Gloss",Range(0,20))=20
// 凹凸映射
_BumpMap("Bump Map",2D)="bump"{} // bump是Unity内置的法线纹理,当没有提供任何法线纹理时,bump对应了模型自带的法线信息
_BumpScale("Bump Scale",Range(0,1))=0.5 // 控制凹凸程度,为0时,意味着该法线纹理不会对光照产生任何影响
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Pass
{
Tags {"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
/*
* 切线空间下计算,需要把视角方向、光照方向变换到切线空间下
* 已知模型空间下视角方向、光照方向,
* 已知切线空间的三个轴在模型空间的表示x轴(切线)、z轴(法线),可以叉乘求得y轴(副切线)
* 从模型空间变换到切线空间,躺着,即按行展开
*/
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL; // 法线
float4 tangent:TANGENT; // 切线
float3 texcoord:TEXCOORD0; // 第一组纹理坐标
};
struct v2f
{
float4 pos:SV_POSITION; // 顶点坐标变换
float3 tangLightDir:TEXCOORD0; // 光照方向,从模型空间变换到切线空间
float3 tangViewDir:TEXCOORD1; // 视角方向
float4 uv:TEXCOORD2;
};
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.position);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
// 法线,切线得到y
float3 y = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
// 构建从模型空间到切线空间的矩阵
fixed3x3 trans = fixed3x3(v.tangent.xyz, y, v.normal);
// 空间变换
o.tangLightDir = mul(trans, ObjSpaceLightDir(v.position));
o.tangViewDir = mul(trans, ObjSpaceViewDir(v.position));
return o;
}
fixed4 frag(v2f v):SV_Target
{
// 归一化
fixed3 tangLightDir = normalize(v.tangLightDir);
fixed3 tangViewDir = normalize(v.tangViewDir);
// 纹理采样
fixed4 packedNormal = tex2D(_BumpMap, v.uv.zw);
fixed3 tangentNormal;
// 如果纹理图不是 normal map
// tangentNormal.xy = (packedNormal.xy * 2 - 1) *_BumpScale;
// tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 或者标识为 Normal map
tangentNormal = UnpackNormal(packedNormal);
// 如果没有 _BumpScale, 下面两步可以不执行
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// 反射率
fixed3 albedo = tex2D(_MainTex, v.uv).rgb * _Color.rgb;
// 环境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
// 漫反射
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tangentNormal, tangLightDir));
// 高光反射
fixed3 halfDir = normalize(tangViewDir + tangLightDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}
Shader "Custom/s7_2_w"
{
Properties
{
_Color("Color",color)=(1,1,1,1)
_MainTex("Main Tex",2D)="white"{}
_BumpMap("Bump Map",2D)="bump"{}
_BumpScale("Bump Scale", Range(0, 1)) = 1
_Specular("Specular",color)=(1,1,1,1)
_Gloss("Gloss",Range(8,255))=50
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
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;
/*
* 切线空间中的法线映射
* 在世界空间中计算光照模型,视角方向、光照方向易得
* 需要把采样得到的切线空间中的法线变换到世界空间,已知模型空间下切线空间的 x轴(切线)、z轴(法线),可得 y 轴(副切线),将它们变换到世界空间下
* 即可得到在世界空间中切线空间的3个轴,从切线空间到法线空间,需要站着,即按列展开
*/
struct a2v
{
float4 position:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT;
float3 texcoord:TEXCOORD0;
};
struct v2f
{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;
float4 tangx:TEXCOORD2;
float4 tangy:TEXCOORD3;
float4 tangz:TEXCOORD4;
};
// 从切线空间变换到世界空间,已知在模型空间中的 x轴 切线,z轴 法线
v2f vert(a2v v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.position);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex); // 代替漫反射
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap); // 法线
float3 worldPos = mul(unity_ObjectToWorld, v.position).xyz;
fixed3 worldtang = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldnormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldbinormal = cross(worldnormal, worldtang) * v.tangent.w;// w分量控制方向
// 充分利用插值寄存器的存储空间,把世界空间下的顶点位置存储在变量的 w 分量中
o.tangx = float4(worldtang.x, worldbinormal.x, worldnormal.x, worldPos.x);
o.tangy = float4(worldtang.y, worldbinormal.y, worldnormal.y, worldPos.y);
o.tangz = float4(worldtang.z, worldbinormal.z, worldnormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f v):SV_Target
{
float3 worldPos = float3(v.tangx.w, v.tangy.w, v.tangz.w);
// 光照方向
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
// 视角方向
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 计算法线,从切线空间变换到世界空间
fixed3 bump = UnpackNormal(tex2D(_BumpMap, v.uv.zw));
bump.xy *= _BumpScale;
bump.z = sqrt(1 - saturate(dot(bump.xy, bump.xy)));
bump = normalize(half3(dot(v.tangx.xyz, bump), dot(v.tangy, bump), dot(v.tangz, bump)));
// 替代漫反射的纹理采样
fixed3 albedo = tex2D(_MainTex, v.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(bump, worldLightDir));
// 高光反射
fixed3 halfDir = normalize(worldLightDir + worldViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}