解剖一个 Unity shader:Properties/SubShader/Pass
Unity 的 shader 文件不是纯粹的着色器代码,而是一份「材质说明书」:声明参数、给出多套渲染方案、每套方案里才是真正跑在 GPU 上的程序。读懂文件结构,是读懂任何现成 shader 的第一步。这篇用 Built-in 管线拆一个完整最小文件,URP 的差异点到即止。
一个完整的最小 shader
以下文件可直接保存为 .shader 使用(Built-in 管线):
Shader "MyGame/UnlitBasic"
{
Properties
{
_MainTex ("Base Color (RGB)", 2D) = "white" {}
_Color ("Tint", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType" = "Opaque" "Queue" = "Geometry" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
struct AppData {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct V2F {
float4 pos : SV_POSITION;
float2 uv : TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
V2F vert(AppData v) {
V2F o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag(V2F i) : SV_Target {
return tex2D(_MainTex, i.uv) * _Color;
}
ENDCG
}
}
FallBack "Diffuse"
}
每段干什么用
| 块 | 职责 | 关键点 |
|---|---|---|
Shader "名字" | 注册名,材质选择器里显示 | 与文件名无关 |
Properties | 材质面板上的参数 | 只声明「可被材质覆盖」的量,见 /notes/unity/structure-4/ |
SubShader | 一套完整可渲染方案 | Unity 从上往下选第一个硬件兼容的 |
SubShader Tags | 给管线看的分类 | Queue 决定绘制队列,RenderType 供替换/后处理识别 |
Pass | 一次绘制(一组 Draw Call) | 多 Pass = 同一物体画多遍(描边、回照光都靠它) |
CGPROGRAM...ENDCG | 真正的 GPU 程序 | #pragma vertex/fragment 指定入口函数 |
FallBack | 所有 SubShader 都不兼容时引用的别的 shader | 老设备/低端机的保底 |
多 SubShader 的典型用法:第一套用高端特性(如曲面细分),第二套纯保底;FallBack "Diffuse" 还能让 Unity 用 Diffuse 的编译结果做回退与部分固定功能(如投影)。
CGPROGRAM 与 HLSLPROGRAM 差在哪
两者都是「在这里写 GPU 程序」,差别在语言库:
| CGPROGRAM(Built-in 老写法) | HLSLPROGRAM(URP/SRP 写法) | |
|---|---|---|
| 头文件 | UnityCG.cginc / Lighting.cginc | Common.hlsl / Lighting.hlsl |
| 采样 | tex2D(_MainTex, uv) | SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, uv) |
| 类型 | 有 fixed/half/float | 只有 float/half(fixed 已废弃) |
| 现状 | Built-in 仍可用,URP 不支持 | URP/HDRP 唯一选择 |
fixed 的历史是「固定精度省移动端算力」,现代编译器的自动精度选择已接管这件事,Unity 官方已废弃 fixed——新代码按 float/half 写,精度坑见 /notes/unity/performance-3/。迁移对照表见 /notes/unity/engineering-1/。
Pass 的 Tags 与 LightMode
Pass 级 Tags { "LightMode" = "..." } 是给管线看的:Built-in 里值如 ForwardBase/ShadowCaster;URP 里换成 UniversalForward/ShadowCaster/DepthOnly 等,且 URP 靠这个标签挑 Pass——标签写错,阴影或深度贴图直接缺这个物体。这是老 shader 迁 URP 的第一大坑,展开见 /notes/unity/engineering-1/。
为什么一个文件要写多个 Pass
一个 Pass 画一遍。需要「同一物体叠加多次」时就得多个 Pass:描边的倒模法是两个 Pass(先背面描边再正面本体,见 /notes/unity/effects-3/);回照光、平面镜像同理。代价也要按遍数算——多一个 Pass 就多一组 Draw Call 与片元执行,见 /notes/unity/performance-1/。
自检三问
- 材质面板上没有某个参数:是没写进 Properties,还是没声明对应变量?两者后果一样吗?
- 硬件不支持第一个 SubShader 时,Unity 怎么选?
- URP 工程里把
CGPROGRAM原样保留会发生什么?