杰瑞科技汇

Unity3D Shader教程从哪学起?

Unity Shader 超详细学习指南

Unity Shader 是一种在 GPU 上运行的微型程序,它决定了你的 3D 模型在屏幕上最终呈现的外观,学习 Shader 编程是进入图形学大门的关键一步,它能让你实现各种酷炫的视觉效果。

Unity3D Shader教程从哪学起?-图1
(图片来源网络,侵删)

本教程将分为以下几个部分:

  1. 基础概念篇:理解 Shader 的本质和工作原理。
  2. Shader Graph 可视化篇:无需编写代码,轻松创建 Shader。
  3. ShaderLab 与 HLSL 编程篇:手写代码的核心。
  4. 实用案例篇:通过实例巩固所学知识。
  5. 学习资源推荐:继续深造的路径。

第一部分:基础概念篇

在开始之前,你必须理解几个核心概念。

1 什么是 Shader(着色器)?

Shader 是一段运行在 GPU 上的程序,它的主要任务是告诉 GPU 如何计算一个像素(片元)的颜色,它接收模型的顶点数据、纹理、光照等信息,并输出最终的像素颜色。

2 渲染流水线

想象一下,GPU 如何将一个 3D 模型变成屏幕上的 2D 图像?这个过程就是渲染流水线,主要分为三个阶段:

Unity3D Shader教程从哪学起?-图2
(图片来源网络,侵删)
  1. 应用阶段:CPU 负责,处理逻辑、计算动画、准备要渲染的物体等,CPU 会将模型数据(顶点、法线、UV 等)发送给 GPU。
  2. 几何阶段:GPU 负责,处理顶点数据,进行模型视图投影变换、裁剪、光栅化等,最终将 3D 坐标转换为屏幕上的 2D 像素位置。
  3. 光栅化阶段:GPU 负责,确定哪些像素被哪些三角形覆盖,并调用 Shader 来计算这些像素的最终颜色。

Shader 主要在光栅化阶段工作。

3 Shader 的基本结构

一个 Shader 程序通常包含两个核心部分:

  • 顶点着色器:处理每个顶点的数据,计算顶点在屏幕上的最终位置、传递光照信息给片元着色器等。
  • 片元着色器:处理每个像素(或称片元)的数据,这是决定像素颜色的关键部分,它会接收顶点着色器传递过来的插值数据,并进行颜色计算。

一个形象的比喻

  • 顶点着色器:像是在给一个网格模型的每个顶点“打点”,告诉 GPU 这个点最终应该画在屏幕的哪个位置。
  • 片元着色器:像是在两个点之间“填充颜色”,并决定这个填充区域的每一个像素应该是什么颜色。

4 Unity 中的 Shader 种类

Unity 提供了多种类型的 Shader,以满足不同需求:

Unity3D Shader教程从哪学起?-图3
(图片来源网络,侵删)
  1. 内置 Standard Shader (PBR):基于物理的渲染,是目前最主流、效果最好的 Shader,它模拟了真实世界中的光照反射,能产生非常逼真的材质效果。
  2. URP/Lit Shader (URP/LWRP):URP (Universal Render Pipeline) 和 LWRP (Lightweight Render Pipeline) 的标准 Shader,是 Unity 未来发展的方向,性能和跨平台能力更好。
  3. Shader Graph:一种节点化的 Shader 创建工具,通过连接节点来生成 Shader,无需编写代码,非常适合视觉设计师和初学者。
  4. 内置着色器语言 (ShaderLab + HLSL/GLSL):这是传统的编写 Shader 的方式,需要直接编写代码,功能最强大,灵活性最高,也是本教程的重点。

第二部分:Shader Graph 可视化篇(零代码入门)

如果你是新手,或者不想面对复杂的代码,Shader Graph 是你最好的起点

1 为什么选择 Shader Graph?

  • 直观:所见即所得,通过拖拽节点即可看到实时效果。
  • 快速:大大缩短了创建和迭代视觉效果的时间。
  • 易学:基本逻辑与编程思想一致,但避免了语法错误。

2 创建第一个 Shader Graph

  1. 在 Project 窗口中,右键 -> Create -> Shader -> URP -> PBR Graph。
  2. 双击打开这个 .shadergraph 文件。
  3. 你会看到一个空白的画布和左侧的节点窗口。
  4. 从左侧的 "Blackboard" 或 "Search" 窗口中,拖出一个 PBR Master 节点到画布上,这是 Shader 的输出节点。
  5. 拖出一个 Texture Sample 节点,并将一张图片赋值给它。
  6. Texture Sample 节点的 RGBA 输出连接到 PBR Master 节点的 Base Color 输入。
  7. 恭喜! 你已经创建了一个基础的纹理显示 Shader,将这个 Shader 应用到一个材质球上,再将材质球赋给一个 3D 模型,你就能看到效果了。

3 实践案例:制作一个卡通风格的 Shader

  1. 创建新 Graph:创建一个新的 PBR Graph。
  2. 使用 Step 节点:从搜索框中找到 Step 节点。Step(edge, value) 函数,value 大于 edge,输出 1,否则输出 0,这是制作硬边效果的利器。
  3. 获取法线信息:拖出一个 Normal Vector 节点,它提供模型表面每个点的朝向。
  4. 计算光照:拖出一个 Dot Product(点积)节点,将 Normal Vector 连接到 A,拖出一个 View Direction(视角方向)节点连接到 B,点积的结果可以用来判断这个面是朝向你还是背向你,从而模拟一个简单的光照。
  5. 创建阈值:连接 Dot Product 的结果到 Step 节点的 Value 输入,给 Edge 输入一个值(0.7),大于这个值的面就是亮的,小于的就是暗的。
  6. 应用颜色:创建两个 Color 节点,一个亮色(如白色),一个暗色(如黑色),将它们连接到一个 Lerp(插值)节点的 AB,将 Step 节点的输出连接到 LerpT(权重)输入。
  7. 连接到输出:将 Lerp 节点的输出连接到 PBR MasterBase Color
  8. 你的模型就有了卡通风格的明暗交界线,你还可以通过调整 Edge 值来控制线条的粗细。

通过这种方式,你可以组合各种节点(如数学运算、时间、噪声、采样等)来创造出几乎任何你想要的视觉效果。


第三部分:ShaderLab 与 HLSL 编程篇(核心与进阶)

这是最强大、最灵活的 Shader 开发方式,我们需要学习 Unity 的 ShaderLab 语言和图形 API HLSL (High-Level Shading Language)。

1 ShaderLab 的结构

ShaderLab 是 Unity 用来组织 Shader 文件的“壳”,它定义了 Shader 的名称、属性、渲染状态等,一个基本的 Shader 文件结构如下:

Shader "MyCustomShader/Name"
{
    Properties // 在 Inspector 面板中显示的属性
    {
        _MainTex ("Texture", 2D) = "white" {} // 定义一个纹理属性
        _Color ("Color", Color) = (1,1,1,1)   // 定义一个颜色属性
    }
    SubShader // 一个 Shader 可以包含多个 SubShader,会依次尝试,直到找到一个能运行的
    {
        Tags { "RenderType"="Opaque" } // 标签,告诉渲染器如何处理这个 Shader
        Pass // 一次完整的渲染过程,一个 SubShader 可以有多个 Pass
        {
            // 渲染状态设置
            Cull Off // 关闭背面剔除
            ZWrite On // 开启深度写入
            HLSLPROGRAM // HLSL 代码开始
            #pragma vertex vert // 声明顶点着色器函数名为 vert
            #pragma fragment frag // 声明片元着色器函数名为 frag
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" // 引入 URP 核心库
            // CBUFFER 是常量缓冲区,用于向 GPU 传递数据,性能更好
            CBUFFER_START(UnityPerMaterial)
                float4 _MainTex_ST; // 纹理的 Tiling 和 Offset
                float4 _Color;
            CBUFFER_END
            // 从 Properties 中声明的变量
            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);
            // 顶点着色器的输入结构体
            struct Attributes
            {
                float4 positionOS   : POSITION; // 对象空间下的顶点位置
                float2 uv           : TEXCOORD0; // UV 坐标
            };
            // 顶点着色器输出 -> 片元着色器输入 的结构体
            struct Varyings
            {
                float4 positionCS   : SV_POSITION; // 裁剪空间下的顶点位置 (系统值)
                float2 uv           : TEXCOORD0; // 传递给片元着色器的 UV
            };
            // 顶点着色器函数
            Varyings vert (Attributes input)
            {
                Varyings output;
                output.positionCS = TransformObjectToHClip(input.positionOS.xyz); // 将顶点位置转换到裁剪空间
                output.uv = TRANSFORM_TEX(input.uv, _MainTex); // 应用 UV 的 Tiling 和 Offset
                return output;
            }
            // 片元着色器函数
            float4 frag (Varyings input) : SV_Target
            {
                // 采样纹理
                float4 col = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
                col *= _Color; // 应用颜色
                return col; // 返回最终颜色
            }
            ENDHLSL // HLSL 代码结束
        }
    }
}

2 HLSL 编程核心

  • 数据类型float, float2, float3, float4 (向量), float4x4 (矩阵), TEXTURE2D, SAMPLER 等。
  • 内置函数TransformObjectToHClip, TRANSFORM_TEX, SAMPLE_TEXTURE2D 等,URP/HDRP 提供了大量方便的内置函数。
  • 核心函数
    • 顶点着色器:核心工作是坐标变换,将顶点从对象空间 -> 世界空间 -> 视图空间 -> 裁剪空间,Unity 的 TransformObjectToHClip 帮你完成了大部分工作。
    • 片元着色器:核心工作是计算颜色,最简单的就是采样纹理 (SAMPLE_TEXTURE2D) 并返回。

3 实践案例:实现一个边缘发光效果

这个效果在很多游戏中都很常见,通常用后处理实现,但也可以在模型上直接实现。

思路

  1. 在片元着色器中,获取当前像素的法线。
  2. 获取当前像素的视角方向。
  3. 计算法线和视角方向的点积,如果它们接近 90 度(点积接近 0),说明这个像素处于模型的边缘。
  4. 根据这个值,插值出基础颜色和发光颜色。

Shader 代码修改

// 在 Properties 中添加
_EdgeColor ("Edge Color", Color) = (1,1,1,1)
_EdgeWidth ("Edge Width", Range(0, 1)) = 0.1
// 在 CBUFFER 中添加
float4 _EdgeColor;
float _EdgeWidth;
// 在 frag 函数中修改
float4 frag (Varyings input) : SV_Target
{
    // 1. 采样基础颜色
    float4 baseColor = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, input.uv);
    // 2. 获取法线和视角方向 (URP 中有内置变量)
    float3 normal = normalize(input.normalWS); // 需要从顶点着色器传递法线过来
    float3 viewDir = normalize(GetViewDirection(input.positionWS)); // 需要从顶点着色器传递位置过来
    // (需要修改顶点着色器来传递 normalWS 和 positionWS)
    // ...
    // 3. 计算边缘强度
    float fresnel = 1.0 - saturate(dot(normal, viewDir)); // fresnel 效应,边缘处值大
    float edgeStrength = smoothstep(0.0, _EdgeWidth, fresnel); // 使用 smoothstep 让边缘过渡更平滑
    // 4. 混合颜色
    float4 finalColor = lerp(baseColor, _EdgeColor, edgeStrength);
    return finalColor;
}

注意:这个例子需要修改 AttributesVaryings 结构体,并在顶点着色器中计算并传递 normalWSpositionWS,这展示了 Shader 编程中常见的“传递数据”模式。


第四部分:实用案例篇

  1. 水波纹效果:在片元着色器中使用 sin 函数,结合时间 _Time.y,根据 UV 坐标计算出偏移量来扰动纹理采样坐标。
  2. 溶解效果:在片元着色器中采样一张噪声纹理,如果噪声值小于一个随时间变化的阈值,就丢弃这个像素 (clip(value))。
  3. 卡通描边:可以在模型渲染后,再 Pass 一次,用一个非常小的模型顶点偏移来绘制轮廓(称为“扩张法”),或者像上面边缘发光那样计算法线与视角的夹角。
  4. 顶点动画:在顶点着色器中修改 input.positionOS,例如通过 sin 函数让旗帜飘动,或者通过噪声函数让地面起伏。

第五部分:学习资源推荐

中文资源

  1. Bilibili (必看!)
    • 小新学图形学:国内最好的 Unity Shader 入门系列之一,从零开始,讲解清晰,实例丰富。
    • 某某(UWA):也有很多高质量的 Shader 教程,偏向于实战和优化。
  2. CSDN / 知乎 / 掘金:搜索关键词 "Unity Shader 教程",有大量技术博客和文章,可以解决具体问题。
  3. Unity 官方文档:虽然是英文,但翻译质量很高,是查阅 API 和概念最权威的地方。

英文资源

  1. Catlike Coding强烈推荐! 这可能是全世界最好的 Unity 图形学教程网站,从最基础的 C# 和数学讲起,逐步深入到 Shader、光线追踪等高级主题,逻辑严谨,循序渐进。
  2. The Book of Shaders:专注于 GLSL(与 HLSL 语法非常相似),从数学基础讲起,教你如何从零开始构建着色器,对理解图形学原理帮助巨大。
  3. Unity Learn:Unity 官方学习平台,有很多官方的交互式项目和教程。
  4. Urchin (by Alan Zucconi):一本免费的在线书籍,深入浅出地讲解了图形学在游戏中的应用,包括 Shader。

学习路径建议

  1. 新手入门:从 Shader Graph 开始,快速建立信心,理解 Shader 的基本逻辑。
  2. 理论学习:开始学习 Catlike Coding 的前几章,补充线性代数、三角函数等基础知识。
  3. 代码入门:当对 Shader Graph 熟悉后,尝试阅读和修改 Unity 自带的简单 Shader(如 Unlit/Texture),然后跟着教程(如小新学图形学)手写一个简单的 Shader。
  4. 实战进阶:尝试自己实现一些效果(如溶解、水波),遇到问题再查资料,这是最快成长的方式。
  5. 精通之路:深入阅读 GPU Gems、Real-Time Rendering 等书籍,学习更高级的渲染技术,如 PBR 原理、后处理、体积光等。

学习 Shader 是一个漫长但回报丰厚的过程,需要耐心和大量的实践,祝你学习愉快!

分享:
扫描分享到社交APP
上一篇
下一篇