news 2026/10/18 1:06:47

Unity Shader Graph自定义贴花:深度重建与盒式投影实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Unity Shader Graph自定义贴花:深度重建与盒式投影实战

1. 为什么贴花值得用 Shader Graph 自己做

在一款射击游戏里,子弹打到墙上要留弹孔;赛车游戏里,轮胎需要划出刹车痕;开放世界项目里,地面涂鸦、井盖标识、角色脚下的血迹,几乎全都是“事后贴上去”的。如果这些效果全部由美术画进场景模型里,那么模型变体、贴图数量、内存占用和包体大小都会迅速失控。更合理的做法是:运行时把一张贴图“投影”到任意物体表面。这套技术的通用名字叫 Decal,也就是贴花。

很多人以为贴花的难点是“把纹理贴到模型上”,其实真正的难点是“把一张平面纹理,在不规则表面上看起来像长在表面上的”。贴花技术要解决的核心问题有三个:如何拿到表面位置、如何把纹理从正确的方向投影上去、如何避免在棱角和曲面上出现明显拉伸。

我打算用 Unity Shader Graph 从零做一套自定义贴花方案。整个方案不依赖额外插件,不写复杂的 C# 渲染代码,核心是“深度重建 + 盒式投影”这两个图形学技巧。读完这篇文章,你会掌握:

  • 贴花的原理是什么,为什么不能用普通 Mesh UV 解决;
  • 如何在 URP 中从深度纹理重建世界坐标;
  • 如何用 Shader Graph 节点实现贴花,以及如何升级为可复制的 HLSL 自定义函数;
  • 贴花出现闪烁、拉伸、贴不到半透明物体时,应该往哪几个方向排查;
  • 这套方案适合什么场景,不适合什么场景。

结论先放在这里:用 Shader Graph 做贴花完全可行,而且对大多数游戏场景足够用;但它是“世界空间盒式投影”,不是真正的曲面参数化贴花。理解了这个边界,你才知道什么时候该自己写,什么时候应该用 Unity 自带的 Decal Renderer Feature。

2. Shader Graph 贴花的原理:从深度纹理到世界坐标

2.1 没有额外 UV,怎么把贴图放到世界表面上

普通材质采样贴图靠的是模型自带 UV 坐标。模型在建模软件里已经展好了 UV,把贴图的左上角映射到模型表面的某个点。但这个流程对动态贴花是行不通的:弹孔在哪个位置出现,玩家不知道,美术提前也不知道。如果给整面墙展一套“额外 UV”,那就相当于在设计阶段把所有可能被贴花的位置全部预留出来,这显然不现实。

所以运行时贴花必须换一个思路:不依赖模型 UV,而是把整个贴花当成一个“投影仪”。这个投影仪处在一个固定位置,朝一个固定方向打光,把贴图投射到场景表面上。表面是什么网格并不重要,重要的是贴图以投影关系覆盖了那个区域的深度。

这就是贴花和普通纹理采样最根本的区别:普通材质告诉你“这个贴图在这个 Mesh 的哪里”,贴花告诉你“这个贴图在世界空间的这个长方体范围内覆盖什么”。

2.2 从深度纹理重建世界坐标

贴花投影的第一步,是知道屏幕上某个像素对应的世界空间位置在哪里。这靠一张关键纹理:_CameraDepthTexture,即相机深度纹理。

URP 在不透明物体渲染完之后,会生成一张深度纹理。这张纹理记录的是每个像素的深度值。只要我们知道相机的逆 VP 矩阵(View-Projection 矩阵的逆矩阵),就能把一个屏幕像素的 UV 坐标和深度值,反向换算成世界空间坐标。

这里有一个容易搞混的点:深度纹理里的深度值,在不同渲染管线和不同平台上含义不完全一样,有些是 NDC 深度,有些是 Eye Depth,有些还可能是反向 Z。所以在 Shader 里最好不要手工做一次 NDC 变换,而是直接使用 URP 提供的封装函数ComputeWorldSpacePosition,让它去处理平台差异。

换算逻辑的核心是下面这一句话:

屏幕像素 + 深度值 + 逆VP矩阵 = 世界空间坐标

得到世界坐标后,我们不需要关心目标物体是什么 Mesh,因为深度纹理已经告诉了我们“表面上这个像素在世界空间的确切位置”。接下来要做的,就是把世界坐标转换到贴花自己的局部坐标里。

2.3 盒式投影的核心思想

所谓盒式投影,就是假设贴花位于一个“透明盒子”内。这个盒子的位置、旋转、缩放,决定贴花的中心、朝向和影响范围。盒子在世界空间占据一个长方体区域。

对于任意一个世界空间坐标点,只要它在贴花盒子内部,就可以通过盒子自身的局部坐标算出 UV 采样坐标;如果它在盒子外部,就丢弃或透明处理。

举例:贴花物体的局部坐标是 X、Y、Z。如果我们规定贴花物体的 Z 轴正方向就是投影方向,那么:

局部坐标的 X 和 Y 对应贴图 UV 局部坐标的 Z 对应投影厚度

当某个世界点转换成贴花局部坐标后,X 和 Y 落在 [-0.5, 0.5] 范围内,Z 方向深度也在合理范围内,就说明这个点应该被贴花覆盖。把 X、Y 从 [-0.5, 0.5] 映射到 [0, 1],就得到了可以采样贴图的 UV。

这就是为什么用 Shader Graph 做贴花时,经常会创建一个空物体当“贴花体积”。空物体的 Transform 就是贴花的投影盒:

贴花物体中心 = 贴花中心 贴花物体旋转 = 投影方向 贴花物体缩放 = 贴花范围,其中 Z 方向是厚度

这个实现思路最直观,也最容易在 Shader Graph 节点中搭建。每一步都在做同一件事:把屏幕坐标还原成世界坐标,再变换到贴花物体的局部坐标。

2.4 为什么它依然能贴合大部分不规则表面

很多人的第一反应是:既然只是把纹理从某个方向投影过去,那么遇到曲面或者转角,不是一定会拉伸吗?

确实会有拉伸,但许多游戏场景并不那么敏感。血迹、弹孔、涂鸦、路面标识,人类视觉对这类贴花的容忍度很高。投影盒子只要覆盖范围不大,深度方向的厚度控制得足够薄,投影误差就很小。转角墙面上,投影贴花会“包”过墙角一点,但看起来依然是合理的动态效果。

这套方案真正的优势在于:它不关心目标 Mesh 是什么拓扑结构。平面、立方体、球体、地形、多边形网格,只要它们写入了深度纹理,贴花就能覆盖上去。这让它非常适合做运行时生成的动态内容,也是它相比“美术预烘焙贴花”的最大价值。

3. 环境准备与 URP 渲染管线配置

3.1 项目版本与依赖

本文使用的方案基于 Unity 的通用渲染管线(URP)和 Shader Graph,建议环境如下,如果你的项目版本较新或较旧并不影响整体思路:

  • Unity 2021.3 LTS 或 Unity 2022.3 LTS;
  • URP 包版本 12 或以上;
  • Shader Graph 包版本 12 或以上;
  • 渲染管线为工程默认的 URP Asset,而不是内置渲染管线。

如果你还在用 Built-in Render Pipeline,Shader Graph 不一定开箱即用,建议先迁移到 URP,或在同一个工程里单独配置 URP 管线的测试场景。

3.2 开启深度纹理

这一步很容易被忽略,却是整套贴花能否工作的前提。URP 中,如果 Depth Texture 没有开启,_CameraDepthTexture就不会生成,Shader 采样的结果会是全 0,最终贴花什么都显示不出来。

开启方法:

  1. 打开 URP Asset 文件;
  2. 找到 Depth Texture 选项;
  3. 把它设为On,或者如果你需要强制某些相机不关闭,可以设为Force。

如果你的项目用的是 Renderer Feature 或者多个 Renderer,记得检查实际使用的 Renderer 对应的 URP Asset。

这里也顺便说明一个常见坑:URP 的深度纹理只在“不透明物体全部渲染完成后”才生成。所以贴花必须在不透明渲染之后绘制,通常放在 Transparent 队列里,否则会采样到不完整的场景深度。

4. 纯 Shader Graph 实现贴花节点流程

这一章先不写 HLSL,完全用 Shader Graph 节点搭一个最小可用的贴花 Shader。这样你能够先理解完整的节点链路,后面再升级为 HLSL 版本。

4.1 创建 Shader Graph 与渲染面配置

在 Project 面板中新建 Shader Graph,选择Unlit Shader Graph。创建完成后,打开 Graph Settings,进行以下设置:

Surface Type = Transparent Blend Mode = Alpha Render Face = Front

这里的Render Face = Front实际上执行的是 Cull Front,也就是只渲染盒子的背面。因为真正要被看到的,是贴花盒子内侧在场景表面上投下的区域,而不是朝相机那一面的盒子正面。

还要关闭 ZWrite:

ZWrite = Off

这是贴花的通用配置:不写深度,避免贴花盒子污染场景深度;透明混合,让贴花能和场景原有颜色混合。

4.2 重建世界坐标并转换到贴花局部空间

在 Shader Graph 中,获取屏幕像素世界坐标的最直接方式,不是手动做逆矩阵变换,而是利用下面这套组合:

  1. 添加Screen Position节点,输出模式选 Default,得到 0 到 1 范围的屏幕 UV;
  2. 添加Scene Depth节点,输入屏幕 UV,输出深度值;
  3. 添加Custom Function节点引用一个简单的 HLSL 函数,传入屏幕 UV 和深度值,重建世界坐标;或者继续用节点组合,但这部分节点组合比较复杂,通常更建议直接用一个小型 HLSL 函数。

为了保持全节点思路,这里先给出可以理解的方式:你可以用Screen Position、Scene Depth、View Direction等节点,把屏幕像素沿相机射线外推得到世界位置。但因为涉及平台差异和深度语义,节点连线很容易出错。我的建议是:跳过纯节点重建世界坐标,直接使用第 5 章给出的 HLSL 函数,然后在 Shader Graph 中用一个Custom Function节点引进来。

这个做法并不违背“从零实现”的目标。你仍然是在 Shader Graph 中搭流程,只是把最敏感、最容易出错的“深度重建”封装成了一个小函数。

拿到世界坐标后,用Transform节点,把坐标从 World 转换到 Object 空间:

输入:世界坐标 World Position Transform 模式:World -> Object 输出:贴花物体局部坐标

注意,这个 Object 不是场景中某个被贴花的墙壁,而是挂载这个 Shader Material 的“贴花体积空物体”。因为贴花 Material 应该挂在贴花体积上,而不是挂在被贴花的 Mesh 上。

4.3 生成投影 UV 与有效性判断

假设贴花物体的局部坐标用 localPos 表示,用Split节点取出 X、Y、Z。

然后做三件事。

第一,判断某个像素在不在贴花盒子内部:

localPos 的每个分量都在 [-0.5, 0.5] 范围内,才说明这个点被贴花覆盖

可以用Step节点和Multiply节点组合,也可以用Clamp节点配合Subtract。最终得到 isValid,范围是 0 或 1。

第二,取 X 和 Y 作为贴图 UV:

uv.x = localPos.x + 0.5 uv.y = localPos.y + 0.5

如果发现贴花纹理上下颠倒,把 uv.y 改成:

uv.y = 1 - (localPos.y + 0.5)

第三,把 Z 方向作为投影厚度,用于做边缘柔化或者剔除。Z 越大,说明该点离投影中心平面越远,应该越透明。

有了 UV 之后,就可以加一个Sample Texture 2D节点,采样贴花贴图。贴图的 A 通道可以作为混合系数和透明度来源。

输出链路的最终连接:

Base Color = 贴花贴图 RGB × Alpha Alpha = 贴花贴图 A × isValid × DepthFade

这样,盒子外的像素 Alpha 为 0,不会显示;盒子内的像素根据贴图透明度显示;Z 方向边缘会自然柔化。

这个纯节点流程的优点是逻辑清晰,适合学习和调试。缺点是节点数量多,性能不是最优,而且不容易做平台细节处理。所以接下来把它升级为 HLSL 自定义函数版本。

5. 升级为 HLSL 自定义函数:完整可复制 Demo

5.1 DecalCore.hlsl 代码

在Assets/Shaders/Decal/目录下新建文件DecalCore.hlsl,写入下面内容:

// 文件路径:Assets/Shaders/Decal/DecalCore.hlsl #ifndef DECAL_CORE_INCLUDED #define DECAL_CORE_INCLUDED #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl" #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/ShaderVariablesFunctions.hlsl" void DecalProjection_float( float2 screenUV, float3 decalSize, out float2 uv, out float3 worldPos, out float isValid, out float depthFade) { // 1. 从深度纹理重建世界空间坐标 float rawDepth = SampleSceneDepth(screenUV); worldPos = ComputeWorldSpacePosition(screenUV, rawDepth, UNITY_MATRIX_I_VP); // 2. 将世界坐标转换到贴花体积物体的局部空间 // 注意:这里的 Object 指的是挂载该 Shader 的贴花体积物体 float4 localPos4 = mul(GetWorldToObjectMatrix(), float4(worldPos, 1.0)); float3 localPos = localPos4.xyz / max(localPos4.w, 1e-5); // 3. 判断是否在盒子范围 [-halfSize, halfSize] 内 float3 halfSize = decalSize * 0.5; float3 insideMin = step(-halfSize, localPos); float3 insideMax = step(localPos, halfSize); isValid = insideMin.x * insideMin.y * insideMin.z * insideMax.x * insideMax.y * insideMax.z; // 4. 使用局部 X/Y 作为贴图 UV uv = localPos.xy / decalSize.xy + 0.5; uv.y = 1.0 - uv.y; // 5. 局部 Z 方向作为投影厚度,用于边缘淡出 depthFade = saturate(1.0 - abs(localPos.z) / max(halfSize.z, 0.0001)); } #endif

解释关键点:

SampleSceneDepth(screenUV)从 URP 深度纹理中取出原始深度值。ComputeWorldSpacePosition(screenUV, rawDepth, UNITY_MATRIX_I_VP)是 URP 提供的重建世界坐标的标准函数,它处理了 NDC 坐标、平台 Y 翻转和反向 Z,比自己写矩阵变换可靠得多。

GetWorldToObjectMatrix()返回当前渲染物体的世界到局部矩阵。在 Shader Graph 里,当前渲染物体就是贴花体积空物体,这样我们不需要往函数里传 center、right、up、forward 这些参数,代码会非常简洁。

step 函数返回 0 或 1。step(-halfSize, localPos)表示当 localPos 大于等于 -halfSize 时返回 1,否则返回 0。六个 step 结果相乘,只有当 localPos 的每个分量都落在[-halfSize, halfSize]之间时,isValid 才等于 1。

5.2 在 Shader Graph 中接入

回到 Shader Graph,添加一个Custom Function节点,配置如下:

Name: DecalProjection Type: File Source: DecalCore.hlsl Function Name: DecalProjection_float

节点会出现两个输入和四个输出:

输入: screenUV (Vector2) decalSize (Vector3) 输出: uv (Vector2) worldPos (Vector3) isValid (Float) depthFade (Float)

其中screenUV连接Screen Position节点,模式选 Default,输出屏幕 0 到 1 UV。

decalSize连接Object节点的 Scale 输出。不过请注意,不同 Unity 版本的 Shader Graph,Object节点不一定都有 Scale 端口。如果没有,就自己暴露一个Vector3属性,手动填贴花体积的缩放值。为了简单可靠,推荐直接暴露属性:

属性名称:_DecalSize Type:Vector3 Default Value:(1, 1, 1)

然后从_DecalSize属性连到decalSize。

采样贴图时,把uv连到Sample Texture 2D节点的 UV。之后把贴图 RGB 和 Alpha 分别作为 Base Color 和 Alpha 输出。

最终输出链路:

Base Color = 贴花贴图 RGB * isValid * depthFade Alpha = 贴花贴图 A * isValid * depthFade

如果不需要贴花边缘渐变,可以让 depthFade 恒为 1,或只乘 isValid。如果你希望贴花出现“接触面越远越淡”的自然衰减,depthFade 很适合保留。

5.3 一个可选的 C# 贴花放置脚本

贴花体积通常不需要美术手动摆放。运行时,玩家开枪、踩血、画涂鸦,都应该由逻辑层在合适位置创建贴花。下面是一个最简脚本,鼠标点击时在射线命中点生成贴花:

// 文件路径:Assets/Scripts/DecalSpawner.cs using UnityEngine; public class DecalSpawner : MonoBehaviour { public GameObject decalPrefab; public float maxDistance = 100f; void Update() { if (Input.GetMouseButtonDown(0)) { Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition); if (Physics.Raycast(ray, out RaycastHit hit, maxDistance)) { PlaceDecal(hit); } } } void PlaceDecal(RaycastHit hit) { GameObject decal = Instantiate(decalPrefab, hit.point, Quaternion.LookRotation(hit.normal)); decal.transform.localScale = Vector3.one * 1.5f; } }

这段代码的关键点是Quaternion.LookRotation(hit.normal)。它会让贴花物体的 forward 方向对准表面法线,也就是让投影方向垂直于被贴表面。如果贴花没有被精确摆成这样,纹理就会在斜面上被拉伸或者变形。

6. 场景搭建、运行与效果验证

6.1 搭建测试台

建议不要在游戏主场景里直接测,先做一个干净的测试场景。

场景中放置:

  • 一个 Plane,模拟地面;
  • 一个 Cube,模拟墙面;
  • 一个 Sphere,模拟曲面;
  • 一个贴花体积 Prefab,是一个空物体,挂一个 Box Collider、一个 MeshRenderer,以及一个引用本节 Shader 的 Material。

MeshRenderer 上可以放一个半透明 Box Mesh,方便在 Scene 视图中看到贴花体积的范围。但要注意,运行时如果不想让这个透明盒影响视觉,可以把 MeshRenderer 关掉或者使用单独的 Debug 层。

把 DecalSpawner 挂到 Camera 上,拖入贴花 Prefab,运行游戏,点击地面、墙壁和球体生成贴花。

6.2 如何判断贴花是否正确

从三个角度验证结果。

第一,贴花是否出现在鼠标点击的位置附近。如果贴花完全不出现,优先检查深度纹理是否开启。这是出现概率最高的原因。

第二,贴花纹理是否相对于表面“躺平”。在平面上,一张写有文字的贴图应该清晰可读,没有透视变形;在立方体墙面上,文字应垂直于墙面法线,而不是像贴在相机上;在球面上,中心区域应正常,边缘会有拉伸,但这属于预期表现。

第三,贴花是否跟随表面。移动被贴花的 Cube,或者旋转视角,贴花应该始终停留在表面原来的位置。如果贴花和表面错开,说明重建出来的世界坐标和贴花体积的变换矩阵对不上,重点检查 localPos 的转换层级。

一个重要的视觉验证技巧:贴花贴图上故意放一个大的方向箭头或数字“L/R”,这样你一眼就能看出 UV 有没有反转、上下有没有颠倒。

7. 贴花常见问题与排查方法

问题现象可能原因排查方式解决方案
贴花完全看不见URP 深度纹理未开启检查 URP Asset 中 Depth Texture 是否为 On设置为 On 或 Force
贴花一闪一闪、抖动深度重建精度不足;贴花盒厚度过大导致 Z 冲突把贴花体积的局部 Z 缩放调小;开启帧调试器观察深度值减小投影厚度;给贴花材质增加深度偏移;改用更高精度深度纹理
贴花纹理拉伸严重投影方向不正确;贴花体积没有用 LookRotation 对准法线检查贴花物体旋转方向使用Quaternion.LookRotation(hit.normal)创建贴花旋转
贴花只显示一半投影盒大小小于贴花覆盖范围检查贴花物体 Scale 和 UV 映射边界放大 scale,或把 uv 映射改为从 [-0.5, 0.5] 映射到 [0,1]
贴花反向、镜像UV 的 Y 方向平台差异观察纹理方向尝试uv.y = 1 - uv.y翻转
半透明物体上没有贴花半透明物体默认不写深度,深度纹理中不包含这些表面检查半透明材质 ZWrite已知限制,玻璃、水面等不建议直接用该方案
Shader Graph 编译报错,找不到函数include 路径错误;函数名大小写错误控制台查看 HLSL 编译错误检查 Core.hlsl / DeclareDepthTexture.hlsl 路径,核对函数签名
贴花盒子自身遮挡场景贴花体积 MeshRenderer 在运行时可见检查贴花体积是否使用透明 Debug Mesh运行时关闭 MeshRenderer,或把 Debug Mesh 放在编辑器专用层
远处贴花出现条纹深度纹理精度不足查看相机近远裁剪面设置相机近远裁剪比值过大时,提高深度纹理格式精度,或缩小小贴花范围

如果在排查时遇到“贴花显示在物体背面”这类表现,通常不是深度重建错误,而是 Culling 配置反了。贴花材质应使用 Cull Front,也就是渲染背面,这样从相机看向贴花盒子时,看到的其实是盒子内侧的投影结果。

8. 工程化落地中的最佳实践

8.1 贴花图集与纹理规范

一个项目里弹孔可能有几十种,如果把每种弹孔都单独建一个 Material、单独采样一张纹理,Draw Call 和材质管理都会失控。更合理的做法是把所有贴花纹理打成一张 Atlas。在 Shader 中暴露 Tiling 和 Offset,或者用一个_DecalType参数控制采样区域。

实践中推荐这样组织:

贴花图集:一张 2048 或 4096 的 RGBA 贴图 A 通道:控制贴花透明度,也可用于边缘衰减 B 通道:如果未来要加法线细节,可放单通道法线或光滑度

UV 计算取决于你的图集布局。如果每个贴花是一张 1/N 的小图,就在采样时乘上对应 Tiling。

8.2 渲染队列与深度偏差

贴花最好的渲染位置是在不透明物体之后、半透明效果之前。URP 里用 Transparent 队列通常就能满足。但你需要注意,Transparent 队列内部的深度写入默认是关闭的,而贴花之间如果相互覆盖,可能会因为混合顺序出现错误。一个经验法则是:贴花材质统一使用相同的队列号,并使用Cull Front,尽量让贴花与贴花之间不互相影响。

如果你发现贴花和墙面之间出现闪烁,在 URP 中可以尝试给贴花材质加一个很小的深度偏移。Shader 中常见的做法是把采样到的深度值稍微向相机方向偏移一点,比如:

rawDepth = rawDepth - 0.0001;

这个值不能一开始就设很大,否则贴花会明显浮在表面上方,出现伪影。

8.3 半透明物体、法线贴花与 PBR 贴花的边界

这套 Shader Graph 自定义贴花方案最适合“纯颜色叠加贴花”。如果项目需要 PBR 贴花,比如弹孔附近的金属高光改变、表面粗糙度变化,Unlit Shader Graph 就不够了。你需要把贴花信息写进场景物体的 GBuffer,或者在 Lit Shader 中额外采样贴花贴图。这就不是简单一个 Unlit 贴花 Shader 能解决的事情。

对于法线贴花,同样是这个道理。颜色贴花可以直接用 Unlit Shader + Alpha Blend;法线贴花要参与光照计算,必须路由到 Lit 管线,或者在原 Lit 材质里判断贴花覆盖区域。

另外,半透明物体默认不写深度,所以贴花天然无法正确覆盖玻璃、水面、透明塑料这种表面。如果产品确实需要“水面上的焦散血迹”这类效果,建议改用其他技术,比如直接用一个贴花 Mesh 跟随水面,或者使用渲染时动态修改水面 UV。

8.4 SRP Batcher 与合批注意事项

Shader Graph 生成的 Shader 默认支持 SRP Batcher,只要项目开启了 SRP Batcher,并且贴花材质没有使用大量非 GPU Instancing 属性,性能通常问题不大。真正影响性能的往往是贴花体积过大。

一个贴花盒覆盖了半面墙,那么像素着色器的采样范围就会非常大。深度重建、世界坐标转换、贴图采样,全屏叠加起来开销不小。工程上的建议是:

  • 贴花体积尽量小,厚度尽量薄;
  • 使用 Atlas 减少纹理切换;
  • 控制同时存活的贴花数量,及时回收过期贴花;
  • 如果追求极致性能,考虑使用 URP 自带的Decal Renderer Feature或 Screen Space Decal 方案。

8.5 贴花生命周期管理

运行时贴花本质上是一个个 GameObject,如果不回收,场景会越来越卡。建议为贴花体积提供一个简单生命周期机制:生成后 5 秒到 30 秒淡出并销毁,或者常驻限定数量。淡出可以在 C# 里做,也可以直接在 Shader 里加一个_FadeFactor属性,每一帧更新,最终乘到 Alpha 上。

Alpha = 贴花贴图 A * isValid * depthFade * _FadeFactor

这种做法在射击游戏血迹淡出、脚印消失等需求中非常常用,而且实现成本很低。

9. 总结:这套方案的适用边界与下一步方向

用 Shader Graph 做自定义贴花,核心思路可以浓缩成一句话:用深度纹理从屏幕像素反推世界坐标,再把世界坐标变换到贴花体积的局部空间,用局部坐标生成投影 UV 和覆盖范围判断。它不依赖模型 UV,不依赖美术预烘焙,适合弹孔、血迹、涂鸦、路面标识这类运行时动态内容。

但也要清醒认识它的边界:它是盒式投影,不是曲面参数化。球体边缘会出现拉伸,转角处纹理不是严格几何映射。如果你需要的是一整套完整 PBR 贴花系统,需要在 Lit 管线里处理法线和粗糙度,那就不能只靠一个 Unlit Shader 解决。更好的选择是在深入这个基础方案后,去学习 URP 的 Decal Renderer Feature、屏幕空间贴花(Screen Space Decal)、以及 GPU Driven 的贴花管理方案。

下一步你可以这样实践:先照第 5 章的代码跑通一个最小 Demo,把贴花打在地面和墙壁上,观察纹理方向和边缘衰减;然后加一个贴花 Atlas,从脚本里控制不同贴花类型;最后再考虑加入_FadeFactor淡出逻辑,把它接入你的实际项目。

当你把这一条链路跑通之后,再看 Unity 自带的贴花方案,会发现它们的原理和你的这套自定义实现高度相似,区别只是性能优化和适用范围更广。到那时,你才算真正理解了贴花技术在引擎渲染中的全貌。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/18 1:06:39

STM32纯软件仿真:基于QEMU+CMSIS-SVD的嵌入式开发新范式

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:06:38

嵌入式项目如何避免重复造轮子:分层抽象与接口驱动设计

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:04:13

飞控H743接口分配实战:UART/SPI/I2C/CAN引脚复用与调试要点

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:04:11

国产MCU破亿出货:从选型到量产的实战避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:04:11

FOC驱动板NTC测温改造:从选型到软件保护完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/18 1:03:33

台风烧录器实战:从SPD读取到内存超频与救砖全指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华