【Shader基础】ShaderLab 语法
关键概念:ShaderLab 是 Unity 自己的"配置语言",它包裹着 CG/HLSL 着色器代码。ShaderLab 负责配置(属性、渲染状态、Tags),CG/HLSL 负责计算(顶点变换、颜色计算)。
一、Shader 文件的整体框架
一个 .shader 文件的嵌套结构是:
Shader ← 最外层,定义名称
├── Properties ← 声明 Inspector 面板参数
├── SubShader ← 可写多个,GPU 从上往下试
│ ├── Tags ← 告诉 Unity 渲染时机和类型
│ ├── 渲染状态 ← Cull / ZWrite / Blend 等
│ └── Pass ← 实际渲染通道(可以有多个)
│ ├── Tags
│ ├── 渲染状态
│ └── CGPROGRAM / HLSLPROGRAM ← 着色器代码
│ ├── #pragma ...
│ ├── struct appdata
│ ├── struct v2f
│ ├── vert()
│ ├── frag()
│ └── ENDCG / ENDHLSL
├── SubShader ← 备用方案(低配设备用)
├── Fallback ← 兜底:所有 SubShader 都不行就用它
└── CustomEditor ← 自定义 Inspector 面板(可选)
二、Shader 名称与路径
cpp
Shader "Custom/MyShader" {
}
- 字符串中用 / 分隔,对应材质面板的下拉菜单层级
- 例如 Shader "MyGame/Water" → 材质面板中会出现在 MyGame 分类下
- 名称可以随意取,但建议按项目组织
三、Properties --- Inspector 面板参数
Properties 里声明的参数会显示在材质面板(Inspector)中,美术可以直接调整。

cpp
_变量名 ("显示名", 类型) = 默认值
三件事必须搞清楚
- 变量名:以 _ 开头是约定(如 _Color、_MainTex)
- 显示名:Inspector 面板上看到的中文/英文标签
- 声明了两次 :Properties 中声明一次(给 Inspector 用),CG/HLSL 代码中再声明一次(给 GPU 用),名字必须一致
cpp
Properties {
_MainTex ("主纹理", 2D) = "white" {} // 纹理
_Tint ("色调", Color) = (1,1,1,1) // RGBA 颜色
_Speed ("速度", Float) = 1.0 // 浮点数
_Smooth ("平滑度", Range(0,1)) = 0.5 // 滑动条
_Count ("数量", Int) = 4 // 整数
_Dir ("方向", Vector) = (0,1,0,0) // 四维向量
_CubeMap ("天空盒", Cube) = "" {} // 立方体贴图
}
四、SubShader --- 渲染方案
一个 Shader 可以有多个 SubShader,GPU 从上往下尝试 ,第一个能跑的就用。
为什么需要多个 SubShader?
cpp
SubShader {
// 高配版本:用 PBR、法线贴图、多光源
}
SubShader {
// 低配版本:只用漫反射,省性能
}
Fallback "Diffuse" // 都不行就用 Unity 内置的漫反射
Tags --- 告诉 Unity 什么时候渲染这个物体
Tags 写在 SubShader 或 Pass 里,用键值对的形式:
cpp
SubShader {
Tags {
"RenderType" = "Opaque" // 渲染类型:不透明
"Queue" = "Geometry" // 渲染队列:不透明物体
"RenderPipeline" = "UniversalPipeline" // URP 专用
}
}
Queue 是最重要的 Tag,它决定了物体的渲染顺序:
| Queue 值 | 数值 | 用途 |
|---|---|---|
| Background | 1000 | 天空盒、背景 |
| Geometry | 2000 | 默认,不透明物体 |
| AlphaTest | 2450 | 透明裁切(植被用) |
| Transparent | 3000 | 半透明物体 |
| Overlay | 4000 | UI、光晕等最上层 |
值越大越晚渲染。透明物体必须在不透明物体之后渲染,所以 Queue 至少要设为 Transparent。
五、Pass --- 渲染通道
SubShader 里的每个 Pass 是一次完整的渲染流程(顶点着色器 → 光栅化 → 片元着色器)。
单 Pass vs 多 Pass
cpp
// 单 Pass:大多数不透明物体只需要一个
SubShader {
Pass {
CGPROGRAM
// 主体渲染
ENDCG
}
}
// 多 Pass:描边效果 = 第一个 Pass 正常渲染,第二个 Pass 画轮廓
SubShader {
// Pass 0: 正常渲染
Pass {
Cull Back
CGPROGRAM
// 主体渲染
ENDCG
}
// Pass 1: 描边
Pass {
Cull Front
CGPROGRAM
// 法线外扩 + 纯色
ENDCG
}
}
Pass 级 Tags --- 光照模式
cpp
Pass {
Tags { "LightMode" = "ForwardBase" } // 主方向光 + 环境光
// ...
}
Pass {
Tags { "LightMode" = "ForwardAdd" } // 额外逐像素光源
// ...
}
Pass {
Tags { "LightMode" = "ShadowCaster" } // 阴影投射
// ...
}
URP 中的 LightMode 不同:UniversalForward、UniversalGBuffer、ShadowCaster 等。
六、渲染状态 (Render States)
控制 GPU 固定管线的行为,写在 SubShader 或 Pass 里。

最常用的四个
cpp
// 1. 剔除模式
Cull Back // 默认:不渲染背面
Cull Front // 只渲染背面(描边 Pass 用)
Cull Off // 双面都渲染(玻璃、植被)
// 2. 深度写入
ZWrite On // 默认:写入深度缓冲
ZWrite Off // 透明物体必须关闭(否则后面的透明物体会被挡住)
// 3. 深度测试
ZTest LEqual // 默认:距离 <= 已有深度才通过
ZTest Always // 总是通过(X光透视效果)
// 4. 混合模式(透明物体必配)
Blend SrcAlpha OneMinusSrcAlpha // 标准半透明
Blend Off // 关闭混合(默认)
透明物体的经典组合
cpp
SubShader {
Tags { "Queue" = "Transparent" "RenderType" = "Transparent" }
ZWrite Off // 不写深度
Blend SrcAlpha OneMinusSrcAlpha // Alpha 混合
Pass { ... }
}
模板测试 (Stencil)
cpp
Stencil {
Ref 1 // 参考值
Comp Always // 比较方式:总是通过
Pass Replace // 通过后把模板值设为 Ref
}
用途:遮罩、镜子、小地图裁切等高级效果。
六、CGPROGRAM / HLSLPROGRAM --- 着色器代码区
这是你 CG/HLSL 代码的地方,被 CGPROGRAM ... ENDCG(或 HLSLPROGRAM ... ENDHLSL)包裹。
必须的 #pragma 指令
cpp
CGPROGRAM
#pragma vertex vert // 告诉 Unity 顶点着色器函数名叫 vert
#pragma fragment frag // 告诉 Unity 片元着色器函数名叫 frag
// 可选:
#pragma multi_compile_fog // 雾效变体
#pragma target 3.0 // 着色器模型版本
ENDCG
#include 引入头文件
cpp
// Built-in 管线
#include "UnityCG.cginc" // 基础工具函数
#include "Lighting.cginc" // 光照相关
#include "AutoLight.cginc" // 光照衰减、阴影
// URP 管线(注意用 .hlsl 而不是 .cginc)
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
数据结构:三个 struct
cpp
// 1. appdata --- 从 CPU 接收的顶点数据
struct appdata {
float4 vertex : POSITION; // 顶点位置(语义: POSITION)
float3 normal : NORMAL; // 法线
float2 uv : TEXCOORD0; // 第一套 UV
};
// 2. v2f --- 顶点着色器传给片元着色器的数据
struct v2f {
float4 pos : SV_POSITION; // 裁剪空间位置(必须)
float2 uv : TEXCOORD0; // UV
float3 worldNormal : TEXCOORD1; // 世界空间法线
};
// 3. vert --- 顶点着色器
v2f vert (appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // MVP 变换
o.uv = v.uv; // 传递 UV
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 法线转世界空间
return o;
}
// 4. frag --- 片元着色器
sampler2D _MainTex;
float4 _Tint;
fixed4 frag (v2f i) : SV_Target {
fixed4 col = tex2D(_MainTex, i.uv) * _Tint; // 采样纹理 * 色调
return col; // 输出最终颜色
}
语义 (Semantics) --- HLSL 和 GPU 的约定
语义告诉 GPU 这个变量的数据从哪来、到哪去:
| 语义 | 用途 | 方向 |
|---|---|---|
| POSITION | 顶点位置 | 输入 |
| NORMAL | 顶点法线 | 输入 |
| TEXCOORD0~7 | UV 坐标 / 自定义数据 | 输入/输出 |
| COLOR | 顶点颜色 | 输入 |
| SV_POSITION | 裁剪空间位置 | 输出(必须) |
| SV_Target | 片元着色器输出颜色 | 输出 |
八、Fallback --- 兜底方案
cpp
Fallback "Diffuse"
// 或者
Fallback Off // 没有后备
当所有 SubShader 都不支持当前硬件时,用 Fallback 指定的内置 Shader。
九、一个完整可运行的 Shader 模板
cpp
Shader "Custom/LearnShader01" {
Properties {
_MainTex ("Texture", 2D) = "white" {}
_Tint ("Tint", Color) = (1,1,1,1)
}
SubShader {
Tags { "RenderType" = "Opaque" }
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; // Tiling/Offset 自动生成
float4 _Tint;
v2f vert (appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex); // 应用 Tiling/Offset
return o;
}
fixed4 frag (v2f i) : SV_Target {
return tex2D(_MainTex, i.uv) * _Tint;
}
ENDCG
}
}
Fallback "Diffuse"
}
它做三件事:MVP 变换、采样纹理、乘以色调。
十、ShaderLab 学习要点总结
| 概念 | 理解 |
|---|---|
| Properties | Inspector 面板的参数声明,双份声明(ShaderLab + HLSL) |
| SubShader | 一套渲染方案,可写多个给不同硬件 |
| Tags | 告诉 Unity 渲染时机(Queue)和类型(RenderType) |
| Pass | 一次完整渲染流程,多 Pass = 多次绘制 |
| 渲染状态 | Cull/ZWrite/ZTest/Blend 控制固定管线行为 |
| CGPROGRAM | 包裹着色器代码,#pragma 指定入口函数 |
| appdata | CPU 传给顶点着色器的数据,用语义标记 |
| v2f | 顶点着色器传给片元着色器的数据 |
| 语义 | POSITION/TEXCOORD/SV_POSITION 等,GPU 靠它识别数据 |
| Fallback | 兜底方案 |
建议把这个完整模板放进 Unity 试一下,改改 _Tint 颜色看看效果,建立直观感受。