Unity Shader 完整学习指南
第一部分:思想准备与基础概念
在开始写代码之前,理解一些核心概念至关重要。

什么是 Shader(着色器)?
Shader 是一段运行在 GPU 上的小程序,它的核心任务是告诉 GPU 如何计算和渲染屏幕上的每一个像素。
想象一下:你的 3D 模型只是一个线框和顶点的集合,Shader 就像是给这个模型穿上“衣服”,并定义了这件衣服的颜色、质感、光泽、透明度等所有外观属性。
GPU 与 CPU 的分工
- CPU (中央处理器):负责游戏逻辑、物理计算、资源管理等“重量级”任务,它准备好所有需要渲染的数据(如模型、摄像机、灯光信息),然后下达“渲染”命令。
- GPU (图形处理器):拥有成千上万个小型核心,擅长进行大规模的并行计算,当 CPU 下达渲染命令后,GPU 会启动成千上万个 Shader 实例,同时处理屏幕上成千上万个像素点的颜色计算。
核心思想:把所有与图形渲染相关的计算都交给 GPU,让 CPU 专注于游戏逻辑,这样游戏才能流畅运行。
Shader 的渲染流水线
Shader 在渲染过程中主要参与以下几个阶段(以最常用的 Forward Rendering 为例):

- 应用阶段:CPU 收集所有需要渲染的物体,准备好数据,并调用 Draw Call 告诉 GPU 开始渲染。
- 几何阶段:GPU 接收到数据后,进行顶点处理(如模型空间 -> 世界空间 -> 观察空间 -> 裁剪空间 -> 屏幕空间)。
- 光栅化阶段:确定哪些图元(三角形)覆盖了屏幕上的哪些像素。
- 片段着色阶段:这是 Shader 大显身手的地方,GPU 对每一个覆盖到的像素(此时称为“片段”或 Fragment)运行你的片段着色器代码,计算它的最终颜色。
- 合并阶段:将计算出的颜色写入帧缓冲区,并处理透明度测试、深度测试等。
你写的 Shader,主要就是在控制第 4 阶段。
第二部分:Unity Shader 的三种形式
Unity 提供了创建 Shader 的三种方式,它们代表了不同的抽象层次。
固定功能管线
这是最古老、最简单的方式,你不需要编写代码,而是通过设置材质面板上的各种参数(如颜色、高光颜色、高光强度等)来告诉 Unity 如何渲染,Unity 内部会使用一套预设好的 Shader 代码。
- 优点:极其简单,无需编程知识。
- 缺点:功能极其有限,无法实现复杂效果。
- 适用场景:初学者快速入门,或对效果要求极简单的项目。
Surface Shader (表面着色器)
这是 Unity 为了简化 Shader 开发而提供的一种高级抽象,你只需要关注光照模型和表面属性(如颜色、法线、平滑度等),Unity 会自动帮你处理顶点光照、阴影、光照贴图等复杂逻辑。

- 优点:开发速度快,能轻松实现复杂的光照效果,自动处理很多底层细节。
- 缺点:不够灵活,生成的 Shader 代码较多,性能开销比自定义 Shader 大。
- 适用场景:需要快速实现符合物理标准的材质,如皮肤、金属、布料等。
Vertex & Fragment Shader (顶点/片段着色器)
这是最底层、最灵活、最强大的方式,你需要自己编写 GLSL/HLSL 代码来控制顶点变换和像素颜色计算,Unity 提供了 CGPROGRAM 和 HLSLPROGRAM 两种编写方式,推荐使用后者(更现代,跨平台支持更好)。
- 优点:完全控制渲染流程,性能最高,可以实现任何你能想象到的视觉效果。
- 缺点:学习曲线最陡峭,需要深入理解图形学原理,代码量较大。
- 适用场景:实现特效(如火焰、水流、卡通渲染)、后处理效果、自定义渲染管线等。
本教程将主要聚焦于第三种 Vertex & Fragment Shader,因为它代表了 Shader 编程的核心能力。
第三部分:从零开始编写你的第一个 Vertex & Fragment Shader
我们将创建一个最简单的 Shader:它只会将物体显示为一种纯色。
步骤 1:创建 Shader 文件
在 Project 窗口中右键 -> Create -> Shader -> Standard Surface Shader,然后双击打开,删掉所有内容,我们从头开始写。
步骤 2:Shader 文件的基本结构
一个 Shader 文件通常包含以下几个部分:
Shader "MyUnlitShader" // 1. Shader 名称,在材质面板中显示
{
Properties // 2. 属性面板,显示在材质的 Inspector 窗口中
{
_MainTex ("Texture", 2D) = "white" {} // 示例属性
_Color ("Color", Color) = (1,1,1,1)
}
SubShader // 3. 子着色器,一个 Shader 可以包含多个,供不同平台选择
{
Tags { "RenderType"="Opaque" } // 标签,用于告诉渲染器如何处理这个物体
Pass // 4. 通道,一次完整的渲染过程
{
// 这里是具体的渲染代码
}
}
}
步骤 3:编写一个最简单的 Pass
我们来实现一个“无光照”的纯色 Shader。
Shader "MyUnlit/01 Simple Color"
{
Properties
{
_MainColor ("Main Color", Color) = (1,1,1,1)
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100 // Level of Detail,LOD 值越低,Shader 越简单
Pass
{
// --- HLSL 编程开始 ---
HLSLPROGRAM
#pragma vertex vert // 告诉编译器,顶点着色函数名为 "vert"
#pragma fragment frag // 告诉编译器,片段着色函数名为 "frag"
#include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // 引入核心库
// --- CBUFFER (常量缓冲区) ---
// 用于将 CPU 的数据高效传递给 GPU
CBUFFER_START(UnityPerMaterial)
float4 _MainColor;
CBUFFER_END
// --- 顶点着色器输入结构体 ---
struct Attributes
{
float4 positionOS : POSITION; // 模型空间下的顶点位置
};
// --- 顶点着色器输出/片段着色器输入结构体 ---
struct Varyings
{
float4 positionHCS : SV_POSITION; // 裁剪空间下的顶点位置 (必须叫 SV_POSITION)
};
// --- 顶点着色器函数 ---
// 输入: v (顶点数据)
// 输出: o (处理后的顶点数据)
Varyings vert (Attributes v)
{
Varyings o;
o.positionHCS = TransformObjectToHClip(v.positionOS.xyz); // 将顶点从模型空间转换到裁剪空间
return o;
}
// --- 片段着色器函数 ---
// 输入: i (从顶点着色器传递过来的数据)
// 输出: float4 (最终像素的 RGBA 颜色)
float4 frag (Varyings i) : SV_Target
{
return _MainColor; // 直接返回我们定义的颜色
}
// --- HLSL 编程结束 ---
ENDHLSL
}
}
}
代码解释:
#pragma vertex frag:这是指令,告诉 Unity 哪个函数是顶点着色器,哪个是片段着色器。struct Attributes:定义了从模型传递给顶点着色器的数据。POSITION是一个语义,告诉 GPU 这个变量是顶点位置。struct Varyings:定义了从顶点着色器传递给片段着色器的数据。SV_POSITION是一个系统值语义,表示这个变量是最终在屏幕上的位置。vert函数:对每个顶点执行一次,最重要的工作是将顶点位置从模型空间转换到裁剪空间(屏幕空间的前身)。TransformObjectToHClip是 URP 提供的便捷函数。frag函数:对每个覆盖的像素执行一次,这里我们直接返回_MainColor,所以整个物体都会是这个颜色。SV_Target:这是一个系统值语义,表示函数的返回值是最终渲染到屏幕的颜色。
步骤 4:使用你的 Shader
- 创建一个材质。
- 在材质的 Inspector 窗口中,点击 Shader 下拉菜单,选择 "MyUnlit/01 Simple Color"。
- 你会发现材质面板上出现了 "Main Color" 属性,可以在这里调整颜色。
- 将材质拖拽到一个 3D 物体上,你就能看到效果了!
第四部分:进阶学习路径
掌握了基础后,你可以沿着以下路径深入:
路径 1:添加纹理
这是最常见的需求,我们需要在 Shader 中使用一张纹理图片。
-
在 Properties 中添加纹理属性:
_MainTex ("Main Texture", 2D) = "white" {} -
在 CBUFFER 中声明纹理:
TEXTURE2D(_MainTex); // 声明纹理 SAMPLER(sampler_MainTex); // 声明采样器
-
在片段着色器中采样纹理:
float4 frag (Varyings i) : SV_Target { // UV坐标通常由顶点着色器传递过来 // float2 uv = i.uv; // 假设你的 Varyings 结构里有 uv float4 texColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, i.uv); return texColor * _MainColor; }注意:UV 坐标也需要从
Attributes结构体中传递进来。
路径 2:理解光照
要让物体看起来更真实,我们需要实现光照模型,最简单的是 Lambert 漫反射模型。
-
获取光照信息:Unity 的渲染管线(如 URP)会自动将光照信息(如主方向光的颜色、方向)传递给 Shader,你需要定义一个
Light结构体来接收它。 -
在片段着色器中计算光照:
// 在 frag 函数内部 Light mainLight = GetMainLight(); // 获取主光源信息 float3 normalDir = normalize(i.normalWS); // 获取世界空间下的法线 float NdotL = saturate(dot(normalDir, mainLight.direction)); // 计算法线与光照方向的点积 float3 diffuse = mainLight.color.rgb * NdotL; // 计算漫反射颜色 float4 finalColor = float4(diffuse * _MainColor.rgb, 1.0); return finalColor;
这需要你的
Varyings结构体中包含世界空间下的法线i.normalWS。
路径 3:实现卡通渲染
卡通渲染是一个经典案例,它通过将光照强度分级来实现。
- 修改光照计算:将
NdotL的结果进行离散化。float NdotL = saturate(dot(normalDir, mainLight.direction)); float toon = step(0.5, NdotL); // NdotL > 0.5,结果为 1,否则为 0 // 或者更平滑的梯度 // float toon = smoothstep(0.4, 0.5, NdotL); float3 diffuse = mainLight.color.rgb * toon;
路径 4:学习后处理效果
后处理效果是在渲染完所有 3D 物体后,对整个屏幕图像进行处理,灰度、泛光、颜色分级等。
- 创建一个 Post-Processing Stack:在 URP 中,你需要创建一个
Post-process Volume。 - 编写一个全屏 Pass:你的 Shader 的 Pass 需要渲染一个覆盖整个屏幕的四边形。
- 采样屏幕颜色:在片段着色器中,使用
SAMPLE_TEXTURE2D采样_CameraColorTexture(这个纹理是上一帧渲染好的屏幕图像)。 - 修改颜色:对采样到的颜色进行数学运算,如
float gray = dot(color.rgb, float3(0.299, 0.587, 0.114));。
第五部分:推荐资源
理论学习 + 大量实践是掌握 Shader 的唯一途径。
书籍
- 《Unity Shader 完整开发指南》:国内经典,由毛星云所著,内容非常全面,适合入门和进阶。
- 《GPU Gems》系列:图形学圣经,汇集了 NVIDIA 工程师的各种高级图形技巧,可以当参考书查阅。
在线教程与网站
- Catlike Coding:强烈推荐! 这可能是全世界最好的 Unity Shader 入门教程,它从零开始,用极富启发性的方式带你一步步理解图形学原理和 Shader 编程,网站地址:
catlikecoding.com/unity/tutorials/ - The Book of Shaders:专注于 GLSL 的教程,但图形学原理是通用的,非常适合理解 Shader 的数学基础,网站地址:
thebookofshaders.com/ - B站/YouTube:搜索 "Unity Shader Tutorial",有大量视频教程,可以关注一些优秀的 UP 主,如 Sebastian Lague(他的算法和图形学视频非常惊艳)、Catlike Coding(有视频版)等。
- Unity Learn:Unity 官方学习平台,有关于 Shader 的官方课程。
工具
- Amplify Shader Editor:一个节点式的 Shader 编辑器,通过拖拽节点就能生成 Shader 代码,对于不擅长写代码的人来说是神器,也可以用它来学习 Shader 的结构。
- Shader Forge:另一个流行的节点式编辑器。
社区
- Unity 官方论坛
- Reddit (r/Unity3D, r/Unity2D)
- 知乎、CSDN 等技术社区
总结与建议
- 不要怕:Shader 的数学和概念看起来很吓人,但只要分解开,一步步来,你会发现它是有逻辑可循的。
- 从模仿开始:找一些你喜欢的 Shader 效果,尝试去读懂别人的代码,然后模仿它实现出来。
- 多写多调试:Shader 编程离不开调试,学会使用
#pragma debug和Debug函数(在 URP 中)来输出颜色信息,或者使用 RenderTexture 来观察中间结果。 - 打好数学基础:向量、矩阵、三角函数是 Shader 的语言,不需要精通所有推导,但要理解它们的几何意义和在图形学中的应用。
祝你学习顺利,早日创作出令人惊艳的视觉效果!
