如果之前在 OpenGL 或者 directX 中成功应用过法线贴图,那么在 U3D 中实现就容易多了
里面已经做过好理解且详细的介绍了,可以只关心理论部分
因为法线存储于切线空间中,因此有两种方法计算最终的光照效果:
对于①代码如下,里面有注释:
Shader "Jaihk662/NewSurfaceShader"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
_SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
_MainTex ("MainTex", 2D) = "white" {}
_NormalMap ("NormalMap", 2D) = "bump" {} //bump为模型自带法线信息
_NormalScale ("NormalScale", float) = 1.0 //控制法线贴图展现的凹凸效果,0~1
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
LOD 200
PASS
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert //声明顶点着色器的函数
#pragma fragment frag //声明片段着色器的函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
sampler2D _MainTex;
float _Gloss;
sampler2D _NormalMap;
float _NormalScale;
float4 _NormalMap_ST;
float4 _MainTex_ST;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 tangent: TANGENT; //targent.w表示副切线的方向
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; //用一个float4同时存储两张纹理的uv坐标
float3 lightTDir: TEXCOORD1;
float3 viewTDir: TEXCOORD2;
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
TANGENT_SPACE_ROTATION; //内置宏TANGENT_SPACE_ROTATION可以直接帮我们计算得到切线空间变换矩阵rotation
//其中TANGENT_SPACE_ROTATION在UnityCG.cginc中被定义,也可以自己进行计算:
//float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w;
//float3x3 rotation = float3x3(v.tangent.xyz, binormal, normal)
o.lightTDir = normalize(mul(rotation, ObjSpaceLightDir(v.vertex)));
o.viewTDir = normalize(mul(rotation, ObjSpaceViewDir(v.vertex)));
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv.zw)); //内置函数UnpackNormal自动帮我们将法线向量由[0,1]映射到[-1,1]
normal.xy *= _NormalScale;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy))); //反正是标准化过的单位向量,可以自己由xy算出z的值
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb;
fixed3 wLightDir = normalize(_WorldSpaceLightPos0.xyz);
fixed3 diffuse = _LightColor0.rgb * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb * saturate(dot(normal, i.lightTDir));
fixed3 reflectDir = normalize(reflect(-i.lightTDir, normal));
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, i.viewTDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
一样可以很容易得看出来差距:
对于②的代码:
Shader "Jaihk662/NewSurfaceShader"
{
Properties
{
_DiffuseColor ("DiffuseColor", Color) = (1.0, 1.0, 1.0, 1.0)
_SpecularColor ("SpecularColor", Color) = (1.0, 1.0, 1.0, 1.0)
_MainTex ("MainTex", 2D) = "white" {}
_NormalMap ("NormalMap", 2D) = "bump" {} //bump为模型自带法线信息
_NormalScale ("NormalScale", float) = 1.0 //控制法线贴图展现的凹凸效果,0~1
_Gloss ("Gloss", Range(8.0, 256)) = 20
}
SubShader
{
LOD 200
PASS
{
Tags { "LightMode" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert //声明顶点着色器的函数
#pragma fragment frag //声明片段着色器的函数
#include "UnityCG.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _SpecularColor;
sampler2D _MainTex;
float _Gloss;
sampler2D _NormalMap;
float _NormalScale;
float4 _NormalMap_ST;
float4 _MainTex_ST;
struct _2vert
{
float4 vertex: POSITION;
float3 normal: NORMAL;
float4 tangent: TANGENT; //targent.w表示副切线的方向
float4 texcoord: TEXCOORD0;
};
struct vert2frag
{
float4 pos: SV_POSITION;
float4 uv: TEXCOORD0; //用一个float4同时存储两张纹理的uv坐标
float4 TtoW1: TEXCOORD1;
float4 TtoW2: TEXCOORD2;
float4 TtoW3: TEXCOORD3;
//float3 wPos: TEXCOORD4; //不需要了,把wPos的三个向量xyz分别放入TtoW1、TtoW2、TtoW3的第四个参数w中
};
vert2frag vert(_2vert v)
{
vert2frag o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _NormalMap);
float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 wNormal = UnityObjectToWorldNormal(v.normal);
float3 wTangent = UnityObjectToWorldDir(v.tangent);
float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
o.TtoW1 = float4(wTangent.x, wBinormal.x, wNormal.x, wPos.x);
o.TtoW2 = float4(wTangent.y, wBinormal.y, wNormal.y, wPos.y);
o.TtoW3 = float4(wTangent.z, wBinormal.z, wNormal.z, wPos.z);
return o;
}
fixed4 frag(vert2frag i): SV_Target
{
float3 wPos = float3(i.TtoW1.w, i.TtoW2.w, i.TtoW3.w);
fixed3 normal = UnpackNormal(tex2D(_NormalMap, i.uv.zw)); //内置函数UnpackNormal自动帮我们将法线向量由[0,1]映射到[-1,1]
normal.xy *= _NormalScale;
normal.z = sqrt(1.0 - saturate(dot(normal.xy, normal.xy))); //反正是标准化过的单位向量,可以自己由xy算出z的值
normal = normalize(half3(dot(i.TtoW1.xyz, normal), dot(i.TtoW2.xyz, normal), dot(i.TtoW3.xyz, normal)));
fixed3 wLightDir = normalize(UnityWorldSpaceLightDir(wPos)); //同等与normalize(_WorldSpaceLightPos0.xyz);
fixed3 wViewDir = normalize(UnityWorldSpaceViewDir(wPos)); //同等于normalize(UnityWorldSpaceViewDir(i.wPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb;
fixed3 diffuse = _LightColor0.rgb * tex2D(_MainTex, i.uv.xy).rgb * _DiffuseColor.rgb * saturate(dot(normal, wLightDir));
fixed3 reflectDir = normalize(reflect(-wLightDir, normal));
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(saturate(dot(reflectDir, wViewDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
在应用法线贴图前,需要正确设置法线的纹理类型:
其中对于 NormalMap,多了一个 CreateFromGrayscale 的复选框,它可以根据高度图生成对应的法线贴图,如果你导入的是高度图,那么就可以通过勾选这个选项来将其当成法线贴图对待,勾选后还会多出一些设置:
设置为 NormalMap,后,就可使用 Unity 的内置函数 UnpackNormal 来获得正确的法线方向,对于不同的纹理压缩算法它都能给出正确的纹理采样方法:
下面是它的内部实现
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
}