把 Shader 想成「會被大量重複執行的小程式」,跑在你的顯示卡(GPU)上。
在 Unity 裡,我們用一種叫 ShaderLab 的外層語法包住 HLSL 程式,告訴引擎「這個材質要怎麼畫」。今天的目標是:看得懂 + 跑得起 + 改得動。
Shader "你的名字/路徑"
{
Properties { //← 在 Inspector 露出的旋鈕(顏色、貼圖、數值…)
_BaseColor ("Base Color", Color) = (1,1,1,1)
_BaseMap ("Base Map", 2D) = "white" {}
}
SubShader { // ← 真正的繪製規則(built-in可以有多個)
Tags { "RenderPipeline"="UniversalRenderPipeline" // URP
"RenderType"="Opaque" "Queue"="Geometry" }
Pass { // ← 一次繪製的階段(Forward、Shadow 等)
Name "ForwardUnlit"
Tags{ "LightMode"="UniversalForward" }
HLSLPROGRAM // ← 這裡面就是 HLSL(像 C 語言)!
// #pragma vertex / fragment 指定進入點
ENDHLSL
}
}
}
我們先做最小可運行範例:把模型用一個顏色畫出來。
Project 視窗 > 右鍵 > Create > Shader > URP > Unlit Shader
(或「Empty Shader」自己貼下方內容)。Create > Material
,選這個 Shader;把材質拖到場景中的 Cube。Shader "Unlit/PBR"
{
Properties
{
_BaseColor ("Base Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
// built-in 內建的函式庫
#include "UnityCG.cginc"
#include "UnityLightingCommon.cginc"
#pragma vertex vert
#pragma fragment frag
#pragma target 3.0
// 1) Inspector 旋鈕對應到「可被 SRP Batcher 使用」的常數緩衝
float4 _BaseColor;
// 2) VS 輸入/輸出:位置一定要有,其他想傳就加
struct appdata {
float4 positionOS : POSITION; // 物件空間位置
};
struct v2f {
float4 positionHCS : SV_POSITION; // 齊次裁剪空間(畫到螢幕要用)
};
// 3) 頂點著色器:把物件位置轉到螢幕(裁剪)空間
v2f vert (appdata v)
{
v2f o;
o.positionHCS = UnityObjectToClipPos(v.positionOS);
return o;
}
// 4) 像素著色器:輸出顏色
half4 frag (v2f i) : SV_Target
{
return _BaseColor;
}
ENDCG
}
}
}
你剛學到:
P*V*M
變換(Day 2 的內容)。把顏色換成從貼圖取樣,並保留一個顏色做調整(乘上去)。
Properties
{
_MainTex ("Texture", 2D) = "white" {}
}
// 1) Inspector 旋鈕對應到的常數緩衝
float4 _BaseColor;
sampler2D _MainTex;
//ST = Scale / Translation
//xy=縮放(tile) zw=偏移(offset)
float4 _MainTex_ST;
// 2) VS 輸入/輸出:位置一定要有,其他想傳就加
struct appdata {
float4 positionOS : POSITION; // 物件空間位置
//第一紋理座標
float2 texcoord :TEXCOORD0;
};
struct v2f {
float4 positionHCS : SV_POSITION; // 齊次裁剪空間(畫到螢幕要用)
float2 uv:TEXCOORD2; // 存在 TEXCOORD2
};
// 3) 頂點著色器:把物件位置轉到螢幕(裁剪)空間
v2f vert (appdata v)
{
v2f o;
o.positionHCS = UnityObjectToClipPos(v.positionOS);
//計算UV
//o.uv.xy=v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex) ;
return o;
}
// 4) 像素著色器:輸出顏色
half4 frag (v2f i) : SV_Target
{
//模型貼圖顏色
fixed3 albedo=tex2D(_MainTex ,i.uv).rgb ;
return half4(albedo,1);
}
小提醒(很重要):
現在加上「一盞方向光」,用 Lambert公式(簡化)拿到主光。
// 2) VS 輸入/輸出:位置一定要有,其他想傳就加
struct appdata {
float4 positionOS : POSITION; // 物件空間位置
float3 normal :NORMAL; // 法線
//第一紋理座標
float2 texcoord :TEXCOORD0;
};
struct v2f {
float4 positionHCS : SV_POSITION; // 齊次裁剪空間(畫到螢幕要用)
float3 worldNormal:TEXCOORD0; // 存在 TEXCOORD0
float3 worldPos:TEXCOORD1; // 存在 TEXCOORD1
float2 uv:TEXCOORD2; // 存在 TEXCOORD2
};
// 3) 頂點著色器:把物件位置轉到螢幕(裁剪)空間
v2f vert (appdata v)
{
v2f o;
o.positionHCS = UnityObjectToClipPos(v.positionOS);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos=mul(unity_ObjectToWorld,v.positionOS).xyz;
//計算UV
//o.uv.xy=v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv=TRANSFORM_TEX(v.texcoord,_MainTex) ;
return o;
}
// 4) 像素著色器:輸出顏色
half4 frag (v2f i) : SV_Target
{
//因內建函數通常沒有歸一化,計算frag時,常要用normalize進行歸一化
fixed3 worldNormal=normalize(i.worldNormal);
fixed3 worldLightDir=normalize(UnityWorldSpaceLightDir(i.worldPos));
float Lambertian=dot(worldNormal,worldLightDir);
Lambertian=saturate(Lambertian); //限制在0~1之間
//模型貼圖顏色
fixed3 albedo=tex2D(_MainTex ,i.uv).rgb ;
return half4(albedo * Lambertian,1);
}
成果: (可以動一下太陽光的角度,觀察陰影變化)
你剛學到:
UnityWorldSpaceLightDir
能拿到給定point與主方向光的方向。有時你會做玻璃/特效圖層,需要調繪製狀態:
// 透明
Tags { "Queue"="Transparent" "RenderType"="Transparent" }
ZWrite Off
Blend SrcAlpha OneMinusSrcAlpha
// 雙面(雙面渲染注意法線方向與陰影)
Cull Off
可以看到因ZWrite Off導致深度錯誤,且blend讓他能做顏色疊加。
透明會讓 Early-Z 失效,排序與效能都更敏感。初學先用不透明材質,等熟悉再玩透明。
_BaseColor
、_BaseMap
要在 HLSL 也有對應:TEXTURE2D
/ SAMPLER
宏配對。法線怪、光斑亂
TransformObjectToWorldNormal
;若有 Normal Map,要 TBN 轉空間並再 normalize
。透明順序錯
顏色灰/偏色
材質看不到
_BaseColor
,確認你可以控制畫面。Unity Shader = ShaderLab(外層) + GLSL/HLSL(內核)。
先把 Unlit → Texture → Lambert 三步走完,你就完成 Shader 入門的 80%。
接下來你可以往 法線貼圖、PBR、陰影、後處理、Compute 延伸——但無論做什麼,記得「先讓最小範例能跑、能改、能量測」。
我們要比較 Phong / Blinn-Phong / Cook-Torrance:用同一個模型切換三種光照,讓你看懂為什麼 PBR(Cook-Torrance)在真實感上更勝一籌,以及「何時用傳統、何時用 PBR」。