Unity URP ShaderGraph 5分钟实现Sprite发光描边效果
2026/7/14 5:51:48 网站建设 项目流程

1. 项目概述与核心价值

在2D游戏开发中,给角色、道具或UI元素添加发光描边效果,是提升视觉反馈和表现力的经典手法。无论是角色被选中、受到攻击,还是特殊技能触发,一个醒目的发光轮廓都能立刻抓住玩家的眼球。过去,实现这种效果往往需要编写复杂的Shader代码,或者依赖第三方插件,对很多开发者来说门槛不低。但现在,借助Unity URP(通用渲染管线)和ShaderGraph,我们完全可以在可视化界面中,用节点连线的方式,快速构建出高质量的Sprite发光描边Shader。

这个项目的核心,就是教你如何在URP环境下,仅用5分钟,通过ShaderGraph制作一个独立、可控的Sprite发光描边效果。我们不仅会实现基础的外发光,还会深入拆解每个关键节点的作用,让你彻底明白背后的原理。这样一来,你不仅能“抄作业”,更能举一反三,定制出符合自己项目美术风格的各类边缘效果。无论是赛博朋克的霓虹光晕,还是奇幻风格的魔法辉光,其底层逻辑都是相通的。

2. 环境准备与ShaderGraph基础设置

在开始连线之前,确保你的Unity项目已经做好了正确的基础配置。这一步看似简单,却常常是后续所有问题的根源。

2.1 确认并配置URP环境

首先,你需要一个使用URP的项目。如果你是从头新建项目,在Unity Hub中选择模板时,务必选择“2D (URP)”“3D (URP)”(对于2D游戏,通常选2D URP更省事)。如果你是在现有项目中升级或转换,可以通过菜单栏Window > Rendering > Render Pipeline Converter进行转换,但请注意备份项目,因为材质转换有时会出问题。

一个关键检查点是你的渲染管线资产(Render Pipeline Asset)。在Project窗口中,你应该有一个名为UniversalRP-HighQuality(或类似名称)的资产。选中它,在Inspector面板中,确保Renderer List下至少有一个Universal Renderer Data资产。这个渲染器数据资产决定了后期处理等功能的可用性,对我们的发光效果至关重要。

2.2 创建正确的ShaderGraph

这是第一个容易踩坑的地方。在Project窗口中右键,选择Create > Shader Graph > URP,这里你会看到很多选项。为了给Sprite使用,我们不应该选择“Sprite Lit”或“Sprite Unlit”。根据网络上的开发者经验(如参考资料中的讨论),这些专为Sprite设计的Shader Graph模板内部会强制乘以SpriteRenderer的Color属性,导致我们在ShaderGraph中难以完全控制最终输出颜色,尤其是在处理发光叠加时会出现预期之外的颜色混合问题。

因此,最稳妥的选择是:Create > Shader Graph > URP > Lit Shader GraphUnlit Shader Graph。对于纯2D Sprite的发光描边,不需要复杂光照,选择Unlit Shader Graph即可,它更简单、性能更好。给它起个直观的名字,比如SG_SpriteOutline

创建后,双击打开ShaderGraph编辑器。首先,在Graph Inspector面板(通常位于编辑器左侧)中,找到Graph Settings

  1. 设置MaterialUnlit。这确保了我们的Shader不会进行光照计算。
  2. Surface Options中,将Surface Type设置为Transparent,并将Blend Mode设置为Alpha(即SrcAlpha OneMinusSrcAlpha)。这是因为Sprite通常需要透明通道,并且发光效果也常常是半透明的叠加。
  3. 关闭Depth Write。对于透明的、相互重叠的Sprite,关闭深度写入可以避免错误的遮挡问题,但记得要合理设置SpriteRenderer的Order in Layer来管理绘制顺序。

完成这些基础设置,我们的ShaderGraph画布就准备好了。接下来,我们将进入核心环节:构建描边逻辑。

3. 发光描边效果的核心原理与节点拆解

发光描边的本质,是在原始Sprite图像的轮廓外围,绘制一层放大的、带有颜色和模糊感的图像。在Shader中,我们无法直接“感知”轮廓,但可以通过一个经典技巧来实现:采样偏移

3.1 核心思路:多方向采样与叠加

我们不会去识别边缘像素。相反,我们会将Sprite的纹理(Texture)在多个方向上(比如上、下、左、右,或者八个方向)进行轻微的偏移采样。对于Sprite内部区域,这些偏移采样得到的颜色几乎是相同的;但对于边缘区域,向外的偏移采样会取到透明(Alpha为0)的背景部分,而向内的采样则能取到Sprite本身的颜色。

如果我们把这些偏移采样的结果用某种方式(比如取最大值)混合起来,那么只有在原始图像边缘的区域,才会因为采到了“非透明”像素而产生颜色值。这个混合后的结果,就是我们的“描边遮罩”。最后,将这个遮罩乘上我们想要的发光颜色,再与原始Sprite图像进行混合(通常是加法混合Additive),发光描边效果就诞生了。

3.2 关键节点详解与搭建步骤

让我们在ShaderGraph中一步步实现这个逻辑。首先,我们需要获取Sprite的纹理和UV坐标。

  1. 基础纹理采样:在空白处右键,搜索Sample Texture 2D节点并创建。我们需要为它指定纹理。在Graph Inspector的Blackboard区域,点击“+”号添加一个Texture2D类型的属性,命名为_MainTex。然后,将这个属性拖到画布上,并将其输出端口连接到Sample Texture 2D节点的Texture输入端口。再将UV节点的输出连接到Sample Texture 2DUV端口。这个节点输出的RGBA就是原始Sprite的颜色,其中A通道是透明度。

  2. 创建描边遮罩:这是最核心的一步。

    • 偏移量属性:在Blackboard中添加一个Vector2属性,命名为_OutlineOffset。这个属性将控制描边的“厚度”或“宽度”。我们可以将其默认值设为(0.01, 0.01)。在画布上创建这个属性节点。
    • 多方向采样:我们需要复制多个Sample Texture 2D节点。一个简单的方法是先选中第一个,然后按Ctrl+D复制。我们至少需要四个:分别用于上、下、左、右四个方向的偏移。
    • 计算偏移UV:创建四个Tiling And Offset节点(搜索Tiling And Offset)。将主UV节点分别连接到它们的UV输入端。
      • 对于“向上”偏移:将_OutlineOffset属性节点连接到Tiling And Offset节点的Offset端口,但我们需要调整Y分量。可以创建一个Vector2节点,手动输入(0, 0.01),或者使用更灵活的方法:创建一个Split节点将_OutlineOffset拆分成X和Y,然后创建一个Combine节点,输入为(0, Y),再连接到Offset。这样,我们就能通过一个属性同时控制X和Y方向的偏移量了。
      • 同理,“向下”偏移的Offset为(0, -Y),“向左”为(-X, 0),“向右”为(X, 0)
    • 连接与采样:将这四个计算好的偏移UV,分别连接到四个复制的Sample Texture 2D节点的UV端口。确保这四个采样节点使用的纹理都是_MainTex属性。
    • 提取Alpha并混合:描边我们通常只关心Alpha通道(即哪里不透明)。从这四个采样节点分别拉出线,选择A端口(Alpha)。现在我们有四个代表不同方向“边缘可能性”的Alpha值。使用一个Max节点(搜索Max),将它们两两合并,最终合并成一个值。这个值就是我们的“描边遮罩”(OutlineMask)。在边缘区域,至少会有一个方向的采样能取到Alpha>0的值,所以Max操作的结果也会>0;在内部区域,所有偏移采样到的Alpha都和中心点一样,取最大值后还是原值;在完全外部,则都为0。
  3. 应用发光颜色

    • 在Blackboard中添加一个Color属性,命名为_OutlineColor,用于设置发光颜色,默认可以设为亮蓝色或白色。
    • 将计算得到的OutlineMask_OutlineColor节点的RGB输出相乘,得到带有颜色的描边OutlineColor
    • OutlineMask也与_OutlineColorA(透明度)相乘,得到描边的透明度OutlineAlpha。再用一个Combine节点,将OutlineColorOutlineAlpha组合成最终的描边颜色OutlineFinal
  4. 最终混合输出

    • 原始Sprite的颜色我们已经有了,就是第一个Sample Texture 2D节点输出的RGBA,记为SpriteColor
    • 发光描边需要叠加在原始Sprite之上。因此,最终的片段颜色应该是SpriteColor + OutlineFinal
    • 但是,直接相加可能会导致叠加区域过度变亮。更常见的做法是使用屏幕空间加法(Screen)或加法(Additive)混合。由于我们在Graph Settings中已经设置了Alpha混合,这里我们使用一个Add节点将SpriteColorOutlineFinal相加即可。Shader的混合模式会处理透明度的叠加。
    • Add节点的输出连接到主Master Stack(在URP的ShaderGraph中可能是FragmentBase Color等输入端口,取决于版本)的Color输入端口。

至此,一个最基本的发光描边ShaderGraph就搭建完成了。你可以将材质球拖给一个SpriteRenderer,调整_OutlineOffset_OutlineColor,应该就能看到效果。

4. 效果优化与高级控制

基础效果实现了,但可能看起来有点“硬”,边缘不够柔和,也缺乏动态变化。我们来给它注入灵魂。

4.1 实现柔和的发光边缘

目前的描边边缘是生硬的,因为我们的遮罩是二值化的(要么有,要么无)。为了实现光晕那种从内到外衰减的效果,我们需要模糊这个遮罩。

  1. 更多方向采样:将四个方向的采样增加到八个(加上四个对角线方向:左上、右上、左下、右下)。这能初步产生更圆润的描边。创建另外四个Tiling And Offset节点,Offset分别设为(-X, Y),(X, Y),(-X, -Y),(X, -Y),并进行同样的采样和Alpha提取。
  2. 使用模糊节点:ShaderGraph提供了Blur节点,但它通常作用于整个纹理,性能开销较大。对于Sprite描边,一个更高效且可控的方法是:对多个采样结果取平均值,而不是最大值
    • 将八个方向的Alpha采样值,加上中心点的Alpha采样值(总共9个),全部输入到一个Add节点进行求和。
    • 然后,将求和的结果除以9(创建一个Float常量节点,值为0.111...即1/9,然后使用Multiply节点)。
    • 这样得到的OutlineMask值在边缘区域会有一个平滑的梯度过渡(例如,边缘像素可能只有3个方向能采到颜色,平均值就是 3/9 ≈ 0.33),从而产生柔和的发光效果。你可以通过调整除法的分母(即采样数量)来控制模糊的强度。

4.2 添加动态参数与效果控制

为了让美术同学或策划能更方便地调节效果,我们需要暴露更多可控属性。

  1. 描边宽度与强度分离:目前_OutlineOffset同时控制宽度和采样范围。我们可以将其拆分为两个属性:
    • _OutlineWidth(Float): 控制描边的视觉宽度。我们可以让它乘以一个很小的系数(如0.005)再作为UV偏移量,这样调节起来更直观。
    • _OutlineIntensity(Float): 控制发光颜色的强度。在将OutlineMask_OutlineColor相乘后,再乘一次这个强度系数。这样即使使用深色作为发光色,也能通过提高强度来让发光更明显。
  2. 内外发光切换:有时候我们需要的是内发光(光晕在Sprite内部)而不是外发光。这其实很简单,只需要改变一下混合逻辑。
    • 创建一个Toggle属性,命名为_IsInnerGlow
    • 内发光的原理是:用1 - OutlineMask作为新的遮罩。这样,只有在Sprite内部(非边缘)的区域,遮罩值才高。
    • 使用一个Branch(分支)节点。将_IsInnerGlow连接到Predicate,将OutlineMask连接到True,将1 - OutlineMask连接到False。节点的输出就是根据开关决定的最终遮罩。
  3. 基于纹理Alpha的边缘控制:你可能不希望Sprite半透明的区域(比如羽毛、火焰的边缘)也产生同样强度的描边。我们可以让描边强度与原始Alpha挂钩。
    • 在计算最终OutlineMask之后,将其与中心点采样的Sprite Alpha值相乘。这样,原始图像越透明的地方,发光也越弱,效果更自然。

将这些属性都暴露出来,你的ShaderGraph界面就会像一个小型控制面板,非常方便调节。

5. 性能考量与实战部署指南

效果做好了,但要用在项目中,尤其是移动端项目,性能是必须考虑的一环。

5.1 性能优化策略

  1. 采样次数是性能关键:我们的方案中,每个片段(像素)进行了9次纹理采样(1次中心+8次偏移)。对于大量使用该材质的Sprite,这会增加GPU的纹理采样压力。优化方法:
    • 减少采样方向:对于小尺寸或不需要高质量光晕的Sprite,可以回归到4方向甚至2方向(仅水平或垂直)采样,牺牲一些质量换取性能。
    • 使用预计算:如果描边颜色和宽度是固定的,可以考虑将“描边”作为第二张纹理(一张已经带有Alpha通道描边的纹理)与主纹理一起传入,通过一次采样和简单混合实现。但这需要美术配合制作,不够灵活。
  2. 合理使用材质实例:不要在运行时动态修改ShaderGraph的全局属性。应该为需要不同描边效果的Sprite创建不同的材质实例(Material Instance)。在Prefab或资源中直接分配好材质实例,这样Draw Call仍然可以合批,前提是它们使用同一个Shader。
  3. 控制渲染顺序与Overdraw:发光效果通常是半透明叠加的,会导致Overdraw(过度绘制)。确保使用该材质的Sprite数量可控,并利用SpriteRenderer的Order in LayerSorting Layer进行精细管理,避免大量半透明物体相互重叠绘制。

5.2 在项目中的实际使用流程

  1. 创建与配置材质
    • 在Project中右键你的SG_SpriteOutlineShader,选择Create > Material。这会生成一个使用该Shader的材质球。
    • 将你的Sprite纹理拖拽到材质球的_MainTex属性上。
    • 调整_OutlineColor,_OutlineWidth等参数,直到获得满意的效果。
  2. 应用到SpriteRenderer
    • 在场景中选择一个SpriteRenderer组件。
    • 将其Material属性从默认的Sprites-Default替换为你刚刚创建的材质球。
    • 立刻就能在Scene视图和Game视图中看到发光描边效果。
  3. 通过脚本动态控制:为了让效果活起来,我们通常需要通过代码在特定时刻(如受击时)触发或改变发光。
    using UnityEngine; public class SpriteOutlineController : MonoBehaviour { private MaterialPropertyBlock _propBlock; private SpriteRenderer _spriteRenderer; void Start() { _spriteRenderer = GetComponent<SpriteRenderer>(); _propBlock = new MaterialPropertyBlock(); } // 示例:触发一个闪烁的发光效果 public void FlashOutline(Color flashColor, float duration) { StartCoroutine(FlashOutlineRoutine(flashColor, duration)); } private System.Collections.IEnumerator FlashOutlineRoutine(Color targetColor, float dur) { float elapsed = 0f; Color originalColor = _spriteRenderer.material.GetColor("_OutlineColor"); while (elapsed < dur) { elapsed += Time.deltaTime; float t = Mathf.PingPong(elapsed * 4f, 1f); // 快速闪烁 Color lerpedColor = Color.Lerp(originalColor, targetColor, t); _spriteRenderer.GetPropertyBlock(_propBlock); _propBlock.SetColor("_OutlineColor", lerpedColor); _spriteRenderer.SetPropertyBlock(_propBlock); yield return null; } // 恢复原状 _spriteRenderer.GetPropertyBlock(_propBlock); _propBlock.SetColor("_OutlineColor", originalColor); _spriteRenderer.SetPropertyBlock(_propBlock); } }
    关键点:这里使用了MaterialPropertyBlock来修改材质属性,而不是直接修改material。这是因为直接修改material会创建该材质的一个新实例,破坏GPU实例化合批,对性能影响很大。而MaterialPropertyBlock可以在不创建新材质实例的情况下覆盖属性,是动态修改材质属性的最佳实践。

6. 常见问题排查与深度技巧

即使按照步骤操作,你也可能会遇到一些棘手的情况。这里记录了几个我踩过的坑和解决方案。

6.1 问题速查表

问题现象可能原因解决方案
完全没有描边效果1. 材质球没有正确赋值给SpriteRenderer。
2. ShaderGraph的Master Stack输出未连接。
3._OutlineOffset值太小(比如0.0001),在纹理空间下偏移量不足一个像素。
1. 检查Inspector中SpriteRenderer的Material字段。
2. 检查ShaderGraph,确保最后一个颜色节点连接到了Base ColorColor端口。
3. 将_OutlineOffset增大到0.01或0.02试试。纹理UV范围是0-1,0.01代表1%的偏移。
描边颜色异常,或受SpriteRenderer.color影响使用了错误的ShaderGraph模板(如Sprite Lit)。该模板内部强制叠加了顶点颜色。务必使用URP > Unlit Shader Graph作为起点。如果需要在Shader内使用SpriteRenderer.color,可以手动添加Vertex Color节点并与纹理颜色相乘。
描边有锯齿,不光滑1. 采样方向太少(如只有4个)。
2. Sprite纹理本身分辨率低,且没有开启Filter ModeBilinear
1. 增加采样方向到8个,并采用“平均值”法代替“最大值”法来获得平滑衰减。
2. 在纹理导入设置中,将Filter Mode设为Bilinear,并适当增加Aniso Level
发光效果在游戏打包后消失ShaderGraph或相关着色器变体(Shader Variant)没有被打包进去。1. 检查Edit > Project Settings > Graphics中的Shader Stripping设置,对于移动平台,可以尝试关闭Strip Unused Variants进行测试。
2. 更可靠的方法:将你的自定义Shader加入到某个Resources文件夹下的材质中,或者使用ShaderVariantCollection来预收集变体。
性能开销过大场景中大量Sprite使用了高采样次数(如9次)的描边Shader。1. 实施LOD(细节层次):距离摄像机远的Sprite使用简化版Shader(4方向采样)。
2. 使用对象池管理“高亮”状态,只对当前需要高亮的少数对象应用该材质,而不是所有对象常驻。

6.2 高级技巧:实现渐变描边与噪声扰动

当你掌握了基础之后,可以尝试更酷的效果。

  • 渐变描边:让描边的颜色从内到外发生变化。这需要将OutlineMask作为一个梯度来使用。我们可以用Smoothstep节点对OutlineMask进行重映射。例如,创建两个Float属性_InnerEdge_OuterEdge,用Smoothstep(_InnerEdge, _OuterEdge, OutlineMask)处理遮罩。然后,用这个处理后的值去混合两种颜色_OutlineColorInner_OutlineColorOuter
  • 噪声扰动:让发光边缘产生一种火焰或能量波动的效果。这需要引入一张噪声纹理(Noise Texture)。
    1. 添加一个Texture 2D属性_NoiseTex,并采样它。
    2. 将噪声的某个通道(如R通道)与时间节点Time的输出相加,然后通过SineFraction节点制造循环变化。
    3. 将这个变化值加到计算偏移UV之前的UV上,或者用它来扰动_OutlineOffset的强度。这样,描边的宽度或位置就会随时间动态变化,产生生动的效果。

最后,我想分享一个最深的体会:ShaderGraph最大的优势不是性能,而是迭代速度团队协作。美术同学完全可以自己在ShaderGraph中调整参数,实时看到效果,无需等待程序修改代码。作为开发者,我们的任务是将这些可调节的属性设计得直观、健壮,并通过MaterialPropertyBlock提供干净的脚本控制接口。把效果做出来只是第一步,让它能优雅地融入项目的生产管线,才是真正的价值所在。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询