標準Phong模型實現
Shader "Unlit/PhongJian"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_Shininess ("Shininess", Range(0.01, 100)) = 1.0 // 高光亮度對比度
_SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half3 normal_dir = normalize(i.normal_dir);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity;
half3 final_color = diffuse_color + spec_color + _AmbientColor.xyz;
return fixed4(final_color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
AO貼圖
Ambient Occlusion(環境遮擋貼圖)簡稱AO貼圖,模擬物體之間所產生的陰影,在不打光的時候增加體積感。也就是完全不考慮光線,單純基於物體與其他物體越接近的區域,受到反射光線的照明越弱這一現象來模擬現實照明(的一部分)效果。
遮擋貼圖用於提供關於模型哪些區域應接受高或低間接光照的資訊。間接光照來自環境光照和反射,因此模型的深度凹陷部分(例如裂縫或摺疊位置)實際上不會接收到太多的間接光照。
遮擋紋理貼圖通常由 3D 應用程式使用建模器或第三方軟體直接從 3D 模型進行計算。
遮擋貼圖是灰度影像,其中以白色表示應接受完全間接光照的區域,以黑色表示沒有間接光照。有時,對於簡單的表面而言,這就像灰度高度貼圖一樣簡單(例如前面高度貼圖示例中顯示的凸起石牆紋理)。
在其他情況下,生成正確的遮擋紋理稍微複雜一些。例如,如果場景中的角色穿著罩袍,則罩袍的內邊緣應設定為非常低的間接光照,或者完全沒有光照。在這些情況下,遮擋貼圖通常將由美術師製作,使用 3D 應用程式基於模型自動生成遮擋貼圖。
程式碼實現
half4 ao_color = tex2D(_AOMap, i.uv);
half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;
左:無AO 右:有AO
點光源衰減公式計算
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
half distance = length(_WorldSpaceLightPos0.xyz - i.pos_world);
half range = 1.0 / unity_WorldToLight[0][0];
half attuenation = saturate((range - distance) / range);
法線貼圖
每個頂點有法線和切線資料,其中切線的走向模型匯入unity時根據UV座標中的U來確定的(並不是模型自帶),下圖藍色是法線,綠色是切線,紅色是副切線
法線、切線、副切線
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是為了處理不同平臺下的翻轉問題,不是1就是-1
PC端上,採用DXT/BC對法線貼圖壓縮,通道資訊會變更,所以要用UnpackNormal函式進行解碼,順便把法線圖範圍從0-1轉為-1到1
解碼後,會得到如下圖所示的貼圖,其中大量藍色說明法線仍然是平面朝上的(0,0,1),紅色是(0,1,0)程式碼沿著平面,這樣在特定角度看,我們就能感受到它的凹凸不平,但平行於平面看還是會感覺很平,這是因為沒有它的3D資料。
_NormalIntensity法線xy偏移強度,預設為1,下面會將將頂點法線改變為貼圖上的位置
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
右圖:無法線貼圖 左圖:有法線貼圖
完整程式碼提供
點選檢視程式碼
Shader "Unlit/PhongJian"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_NormalMap("NormalMap",2D) = "bump"{}
_AOMap ("Texture", 2D) = "white" {} // AO貼圖
_SpecMask ("Texture", 2D) = "white" {} // 粗糙度貼圖反向得到
_Shininess ("Shininess", Range(0.01, 100)) = 1.0 // 高光亮度對比度
_SpecIntensity("SpecIntensity",Range(0.01,5)) = 1.0 // 高光亮度控制
_NormalIntensity("NormalIntensity", Range(0, 5)) = 1// 法線xy偏移強度
_AmbientColor ("Ambient Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
Tags{"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 tangent_dir : TEXCOORD3;
float3 binormal_dir : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
sampler2D _AOMap;
sampler2D _SpecMask;
sampler2D _NormalMap;
float _NormalIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是為了處理不同平臺下的翻轉問題,不是1就是-1
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half4 ao_color = tex2D(_AOMap, i.uv);
half4 spec_mask = tex2D(_SpecMask, i.uv);
half4 normalMap = tex2D(_NormalMap, i.uv);
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb;
half3 final_color = (diffuse_color + spec_color + _AmbientColor.xyz) * ao_color;
return fixed4(final_color,1);
}
ENDCG
}
Pass
{
Tags{"LightMode" = "ForwardAdd"}
Blend One One
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdadd
#include "UnityCG.cginc"
#include "AutoLight.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 texcoord : TEXCOORD0;
float3 normal : NORMAL;
float4 tangent : TANGENT;
};
struct v2f
{
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
float3 normal_dir : TEXCOORD1;
float3 pos_world : TEXCOORD2;
float3 tangent_dir : TEXCOORD3;
float3 binormal_dir : TEXCOORD4;
};
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _LightColor0;
float _Shininess;
float4 _AmbientColor;
float _SpecIntensity;
sampler2D _AOMap;
sampler2D _SpecMask;
sampler2D _NormalMap;
float _NormalIntensity;
v2f vert (appdata v)
{
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
o.normal_dir = normalize(mul(float4(v.normal, 0), unity_WorldToObject).xyz);
o.tangent_dir = normalize(mul(unity_ObjectToWorld, float4(v.tangent.xyz, 0)).xyz);
o.binormal_dir = normalize(cross(o.normal_dir, o.tangent_dir)) * v.tangent.w; // tangent.w 是為了處理不同平臺下的翻轉問題,不是1就是-1
o.pos_world = mul(unity_ObjectToWorld, v.vertex).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
half4 base_color = tex2D(_MainTex, i.uv);
half4 ao_color = tex2D(_AOMap, i.uv);
half4 spec_mask = tex2D(_SpecMask, i.uv);
half4 normalMap = tex2D(_NormalMap, i.uv);
half3 normal_data = UnpackNormal(normalMap);
normal_data.xy = normal_data.xy * _NormalIntensity;
half3 normal_dir = normalize(i.normal_dir);
half3 tangent_dir = normalize(i.tangent_dir);
half3 binormal_dir = normalize(i.binormal_dir);
float3x3 TBN = float3x3(tangent_dir, binormal_dir, normal_dir);
normal_dir = normalize(mul(normal_data.xyz, TBN));
//normal_dir = normalize(tangent_dir * normal_data.x * _NormalIntensity + binormal_dir * normal_data.y * _NormalIntensity + normal_dir * normal_data.z);
half3 view_dir = normalize(_WorldSpaceCameraPos.xyz - i.pos_world);
#if defined (DIRECTIONAL)
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz);
half attenuation = 1;
#elif defined (POINT)
half3 light_dir = normalize(_WorldSpaceLightPos0.xyz - i.pos_world);
half distance = length(light_dir);
half range = 1.0 / unity_WorldToLight[0][0];
half attenuation = saturate((range - distance) / range);
#endif
half NdotL = dot(normal_dir, light_dir);
half3 diffuse_color = max(0, NdotL) * _LightColor0.xyz * base_color * attenuation;
half3 reflect_dir = reflect(-light_dir, normal_dir);
half RdotV = dot(reflect_dir, view_dir);
half3 spec_color = pow(max(0, RdotV), _Shininess) * _LightColor0.xyz * _SpecIntensity * spec_mask.rgb * attenuation;
half3 final_color = (diffuse_color + spec_color) * ao_color; // add中不計算環境光
return fixed4(final_color,1);
}
ENDCG
}
}
Fallback "Diffuse"
}
參考文件
- https://mp.weixin.qq.com/s?__biz=MzU0MDcxOTc5MA==&mid=2247493155&idx=1&sn=856fc57a251058d31695f840c88c277e&chksm=fb3649e2cc41c0f4e479fc23d468ef3491f55b3e3ee6f109205b92a873b0d5776ae8474faf26&scene=27
- https://zhuanlan.zhihu.com/p/655471214