切线空间与法线贴图
法线:
法线是指垂直于物体某一顶点的向量,可以表示为(x,y,z) 归一化的法线x,y,z范围为(-1,1)
法线贴图:
所有法线的指向都偏向z轴(0, 0, 1),所以法线贴图对应到rgb时是偏蓝色的。
由于图像的rbg值取值范围是[0,1],所以采样法线贴图之后还要做一次normal*2-1的操作得到正确的结果
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNormal = UnpackNormal(packedNormal);
//UnpackNormal已经做过一次取xyz的操作了,所以不需要在UnpackNormal(packedNormal)后面加上.xyz
inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
return packednormal.xyz * 2 - 1;
#else
return UnpackNormalmapRGorAG(packednormal);
#endif
}
uniform sampler2D normalMap;
void main()
{
// 从法线贴图范围[0,1]获取法线
normal = texture(normalMap, fs_in.TexCoords).rgb;
// 将法线向量转换为范围[-1,1]
normal = normalize(normal * 2.0 - 1.0);
[...]
// 像往常那样处理光照
}
切线空间和切线
切线空间的法线信息是相对的,因此,切线中的法线纹理比模型空间的法线纹理更通用,常用的法线纹理就是切线空间中的。
模型空间的法线向量是各项异性的,x方向和y方向都有不同的数值,所以说模型空间的法线比切线空间的法线更难压缩。
切线方向和网格uv的u方向是一致的,副切线同时垂直于切线方向和法线方向,可以通过叉乘得到。
TBN矩阵
TBN矩阵产生的原因是对物体进行光照计算时需要坐标在统一的空间,因此就有切线空间坐标到世界空间的转换和世界坐标空间到切线空间的转换。
世界空间到切线空间称为TBN,切线空间到世界空间称为TBN(-1);
这里的Tx,Bx,Nx都是指世界空间下切线的x向量,副切线的x向量,法线的x向量
推导过程可参考: https://zhuanlan.zhihu.com/p/414740003
https://zhuanlan.zhihu.com/p/266334297
相当于用点乘分别把x,y,z分量投影到切线空间
代码参考:
//求世界空间TBN
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//float3 worldTangent = normalize(mul(unity_ObjectToWolrd,float4(v.tangent.xyz, 0 ) ).xyz) ;
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinormal =normalize(cross(worldNormal,worldTangent) * v.tangent.w);
float3 worldPos = mul(unity_ObjectToWolrd,v.vertex).xyz;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
完整代码
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float4 tangent:TEXCOORD1;
float4 normal :TEXCOORD2;
};
struct v2f
{
float4 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
float4 TtoW0:TEXCOORD2;
float4 TtoW1:TEXCOORD3;
float4 TtoW3:TEXCOORD4;
};
sampler2D _MainTex;
sampler2D _NormalMap;
float4 _MainTex_ST;
float4 _NormalMap_ST;
v2f vert (appdata v)
{
v2f o;
o.vertex =UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.uv, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.uv,_NormalMap);
//求世界空间TBN
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
// float3 worldTangent = normalize(mul(unity_ObjectToWolrd,float4(v.tangent.xyz, 0 ) ).xyz) ;
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinormal =normalize(cross(worldNormal,worldTangent) * v.tangent.w);
float3 worldPos = mul(unity_ObjectToWolrd,v.vertex).xyz;
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
//
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
UNITY_APPLY_FOG(i.fogCoord, col);
//TBN矩阵
float3x3 TBN = (i.TtoW0.xyz,i.TtoW1.xyz,i.TtoW2.xyz);
//在世界空间计算光照的代码
float3 worldPos = float3x3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(worldPos));
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//获得切线空间中的法线贴图解码后的法线
float3 bump = tex2D(_NormalMap,i.uv.zw);
bump.z= sqrt(1.0 - saturate(dot(bump.xy, bump.xy) ) );
//转到世界空间
bump = normalize(mul(TBN,bump) );
// bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
float3 albedo = tex2D(_MainTex, i.uv);
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
float3 diffuse = _LightColor0.rgb * albedo * max(0, dot(bump, lightDir));
float3 halfDir = normalize(lightDir + viewDir);
float3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
return float4((ambient+diffuse+specular),1.0);
}
ENDCG