法线纹理是在模型切线坐标系储存表面法向量,我们有两者方式应用纹理中的法线量。
- 使用切线坐标系,使得光照在切线坐标系中计算。
- 使用世界坐标系,使得光照在世界坐标系中计算
代码中其他部分代码是继承自上个博客 -> 高光反射
切线坐标系
Shader "Lit/DiffuseLitShader2"
{
Properties
{
_MainTex("Main Tex", 2D) = "white"{}
_BumpMap("Bump Map", 2D) = "bump"{}
_BumpScale("Bump Scale", Float) = 1.0
_Color("Color", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256.0)) = 20
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include <UnityCG.cginc>
#include <UnityShaderUtilities.cginc>
#include <UnityLightingCommon.cginc>
struct a2v
{
float4 pos : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float3 litDir : TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Color;
fixed4 _Specular;
float _Gloss;
v2f vert(a2v v)
{
v2f o;
//得到裁剪空间
o.pos = UnityObjectToClipPos(v.pos.xyz);
//一个 texcoord 的 uv 的 xy 存主纹理, zw 存法线纹理,减少插值计算
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//获得副切线,可以使用 UnityCG.cginc 文件中的宏定义 TANGENT_SPACE_ROTATION,与下面代码相同
float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
//获得 光照 与 观察 方向(在物体切线坐标系中)
o.litDir = mul(rotation, ObjSpaceLightDir(v.pos));
o.viewDir = mul(rotation, ObjSpaceViewDir(v.pos));
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//在切线坐标系中
//在插值过程中,可能会导致大小不为 1,需要进行归一化
fixed3 tangentLitDir = normalize(i.litDir);
fixed3 tangentViewDir = normalize(i.viewDir);
//读取纹理像素,并算出对于的法线方向,
// fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
// fixed3 tangentNormal;
//我们通过对 法线的 xy 进行缩放,来控制凹凸程度,在由 xy 来得到对应的 z 值
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//我不推荐自己解法线方向,由于不同的法线贴图的格式不同,会采用不同的解法,使用 Unity 的内置函数,可以避免这样的情况。
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
// //我们通过对 法线的 xy 进行缩放,来控制凹凸程度,在由 xy 来得到对应的 z 值
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//获得主纹理颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
//得到 ambient
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//使用 半兰伯特定律
fixed3 diffuse = _LightColor0.rgb * albedo * (dot(tangentNormal, tangentLitDir) * 0.5 + 0.5);
//高光反射 使用 Blinn - Phong 模型
fixed3 halfDir = normalize(tangentLitDir + tangentViewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);
return fixed4(diffuse + ambient + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
世界坐标系
Shader "Lit/DiffuseLitShader2"
{
Properties
{
_MainTex("Main Tex", 2D) = "white"{}
_BumpMap("Bump Map", 2D) = "bump"{}
_BumpScale("Bump Scale", Float) = 1.0
_Color("Color", Color) = (1, 1, 1, 1)
_Specular ("Specular", Color) = (1, 1, 1, 1)
_Gloss ("Gloss", Range(8.0, 256.0)) = 20
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include <UnityCG.cginc>
#include <UnityShaderUtilities.cginc>
#include <UnityLightingCommon.cginc>
struct a2v
{
float4 pos : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float4 texcoord : TEXCOORD0;
};
struct v2f
{
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0;
float4 TtoW0 : TEXCOORD1;
float4 TtoW1 : TEXCOORD2;
float4 TtoW2 : TEXCOORD3;
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
fixed4 _Color;
fixed4 _Specular;
float _Gloss;
v2f vert(a2v v)
{
v2f o;
//得到裁剪空间
o.pos = UnityObjectToClipPos(v.pos.xyz);
//一个 texcoord 的 uv 的 xy 存主纹理, zw 存法线纹理,减少插值计算
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
float3 worldPos = mul(unity_ObjectToWorld, v.pos).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
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);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
//在世界坐标系中
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
fixed3 litDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
//读取纹理像素,并算出对于的法线方向,
// fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
// fixed3 tangentNormal;
//我们通过对 法线的 xy 进行缩放,来控制凹凸程度,在由 xy 来得到对应的 z 值
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//我不推荐自己解法线方向,由于不同的法线贴图的格式不同,会采用不同的解法,使用 Unity 的内置函数,可以避免这样的情况。
//此时提取的是切线坐标系的法向量
fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
//我们通过对 法线的 xy 进行缩放,来控制凹凸程度,在由 xy 来得到对应的 z 值
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
//需要从切线坐标系转换为世界坐标系
tangentNormal = normalize(half3(dot(i.TtoW0.xyz, tangentNormal),
dot(i.TtoW1.xyz, tangentNormal), dot(i.TtoW2.xyz, tangentNormal)));
//获得主纹理颜色
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
//得到 ambient
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
//使用 半兰伯特定律
fixed3 diffuse = _LightColor0.rgb * albedo * (dot(tangentNormal, litDir) * 0.5 + 0.5);
//高光反射 使用 Blinn - Phong 模型
fixed3 halfDir = normalize(litDir + viewDir);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(tangentNormal, halfDir)), _Gloss);
return fixed4(diffuse + ambient + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
来源为 Unity Shader 入门精要这本书 - 基础纹理和法线纹理的实现