投影贴花开发指南
功能概述
贴花投影(Decal Projector)把一张 2D 图片像幻灯机一样投射到 3DGS 点云表面。投影仪是世界空间中一个固定的透视视锥,落在视锥内的点云被贴花颜色覆盖,视锥外的点云不受影响。贴花是全局着色状态,对整个 3DGS 场景具有一致的着色效果。
在基础投影之上可选开启遮挡剔除。默认贴花是 X 光式的:由于 3DGS 本身并没有表面的概念,贴花会穿透打到被前方物体遮挡的背面,并穿透整个 3DGS 场景;开启遮挡剔除后,只有正对投影仪的前表面被染色,被挡住的背面与被遮场景不再染色,从而得到真实的幻灯机效果。
说明:
贴花与遮挡剔除仅在 PC / Mac(URP 管线) 下生效。在移动端或 VR 平台调用相关接口会被 SDK 忽略,并打印一条警告日志。无需在业务侧做平台判断,但也不要期望在这些平台看到效果。
步骤 1:创建场景与游戏对象
在 Unity 中创建一个全新的场景,Unity 默认会创建 Main Camera 和 Directional Light。
设置 Main Camera
在 Hierarchy 窗口中选择 Main Camera,在 Inspector 窗口中按下图设置其属性。

设置 Main Camera 的属性
创建 LCCManager
在 Hierarchy 窗口空白处单击鼠标右键,在弹出的菜单中选择 Create Empty,创建一个空游戏对象,命名为 LCCManager,将其 Transform 归一,并通过 Add Component 为其添加 LCCManager 脚本。

创建 LCCManager 游戏对象并添加 LCCManager 脚本
创建 LCCRenderer
在 Hierarchy 窗口空白处单击鼠标右键,选择 Create Empty,创建一个空游戏对象,命名为 LCCRenderer,将其 Transform 设置为 Rotation.x = -90、Scale.x = -1。

创建 LCCRenderer 并设置 Transform(Rotation.x = -90、Scale.x = -1)
注意:
LCCRenderer 游戏对象的 Transform 设置(Rotation.x = -90、Scale.x = -1)是与 LCC 数据坐标系的对齐。关于坐标系对齐的详细说明,参见 Unity SDK 简介 中的「LCC 数据坐标系」。
创建 Projector
在 Hierarchy 窗口空白处单击鼠标右键,选择 Create Empty,创建一个空游戏对象,命名为 Projector。创建该空对象的目的是使用其 Transform 组件来控制投影仪的位置、方向等属性;将其关联到脚本代码后,可在运行时调整投影仪属性。投影仪沿其 +Z 方向投影,其属性可根据需要设置。

创建 Projector 空游戏对象作为投影仪(+Z 为投影方向)
步骤 2:添加 Render Feature
- 在 Unity 菜单栏中选择 File → Project Settings,在左侧选择 Graphics,确认当前使用的 Default Render Pipeline Asset。本示例为 URP(Universal Render Pipeline Asset)。

在 Graphics 设置中确认 Default Render Pipeline Asset
点击该 Asset,在 Project 窗口中定位到对应的资源文件,找到与之关联的 Renderer(本示例为
URP_Renderer.asset)。选中该 Renderer 资源文件,在 Inspector 窗口中点击 Add Renderer Feature,添加 Decal Depth Feature。

在 URP Renderer 中添加 Decal Depth Feature
注意:
Decal Depth Feature 是遮挡剔除的前提。若只使用基础投影(X 光式贴花),可不添加;但要开启遮挡剔除,则必须先在 URP Renderer Asset 中注册该 Feature,否则遮挡深度图不会生成,遮挡不生效。
步骤 3:编写脚本
新建一个 C# 脚本,命名为 Decal.cs。下面的完整代码模拟接入方视角,覆盖全部 7 个贴花相关方法(5 个基础投影 + 2 个遮挡剔除),可直接复制到工程使用。
using LCCCore;
using UnityEngine;
/// <summary>
/// Decal 贴花投影 —— 开发者最小使用示例。
///
/// 本脚本模拟"接入方脚本",只调用 LCCManager 的 public API,不触碰 SDK 内部。
/// 覆盖全部 7 个贴花 API:
/// 基础投影(5) SetDecalEnabled / SetDecalTexture / SetDecalProjection / SetDecalOpacity / SetDecalEdgeFade
/// 遮挡剔除(2) SetDecalOcclusion / SetDecalOcclusionSettings
///
/// 使用步骤:
/// 1) 新建空 GameObject,挂上本脚本;
/// 2) Inspector 里把场景中的 LCCManager 拖到 m_manager;
/// 3) m_filePath 填一个有效 LCC 场景路径;
/// 4) m_decalTexture 拖入要投影的图片(建议在导入设置里勾 Read/Write);
/// 5) m_projector 拖入一个空物体当投影仪(留空则用本物体),它的 +Z 朝向即投影方向;
/// 6) Play。场景加载完成后贴花自动开启,拖动投影仪即可实时看到贴花打在点云上。
///
/// 平台说明:贴花与遮挡剔除仅在 PC/Mac(URP)生效,移动端/VR 调用会被 SDK 忽略并打印 LogWarning。
/// </summary>
public class Decal : MonoBehaviour
{
[Header("必填")]
[Tooltip("场景中的 LCCManager 实例")]
public LCCManager m_manager;
[Tooltip("有效的 LCC 场景路径")]
public string m_filePath;
[Tooltip("要投影的贴花图片。RGB=颜色, A=贴花自身遮罩")]
public Texture2D m_decalTexture;
[Header("投影仪 (沿 +Z 方向投影)")]
[Tooltip("投影仪位姿。留空则用本物体 transform")]
public Transform m_projector;
[Range(1f, 179f)] public float m_fov = 60f;
[Tooltip("投影宽高比; <=0 时按贴花图片宽高比自动计算")]
public float m_aspect = 0f;
public float m_near = 0.1f;
public float m_far = 100f;
[Header("贴花外观")]
[Range(0f, 1f)] public float m_opacity = 1.0f;
[Range(0f, 0.5f)][Tooltip("边缘羽化宽度(UV 比例)。0 = 硬边")]
public float m_edgeFade = 0.1f;
[Tooltip("每帧刷新投影,便于运行时拖动投影仪 / 改参数看效果")]
public bool m_updateEveryFrame = true;
[Header("遮挡剔除 (仅 PC/Mac)")]
[Tooltip("开启后被前方遮挡的背面/被挡点云不再被贴花染色(幻灯机效果),关闭则 X 光穿透")]
public bool m_occlusion = false;
[Range(0f, 1f)][Tooltip("opacity 阈值: 仅 opacity ≥ 此值的 splat 参与深度图,滤浮点/雾状点。0=全部参与")]
public float m_occlusionCutoff = 0f;
[Range(0f, 0.5f)][Tooltip("相对深度容差。过小→近面自遮挡; 过大→漏挡穿透。默认 0.05")]
public float m_occlusionBias = 0.05f;
[Range(0, 16)][Tooltip("方形足迹半径上限(px),去穿透漏染主旋钮。0=单纹素,默认 2")]
public int m_occlusionFootprint = 2;
[Tooltip("深度图分辨率(px, 2 的幂 256~4096)。调低补掠射面空洞,默认 1024")]
public int m_occlusionResolution = 1024;
// ── 运行时状态 ──
private bool m_loaded;
private bool m_lastOcclusion;
private float m_lastCutoff = float.NaN;
private float m_lastBias = float.NaN;
private int m_lastFootprint = -1;
private int m_lastResolution = -1;
void Start()
{
if (m_manager == null || string.IsNullOrEmpty(m_filePath))
{
Debug.LogError("[DecalDemo] m_manager 或 m_filePath 未赋值。");
enabled = false;
return;
}
// 1) 初始化渲染管线为 PC 单场景编辑模式
m_manager.SetPlatformType(PlatformType.PC);
m_manager.SwitchRenderPass(ActiveRenderMode.SingleRender);
m_manager.SetEditorMode(true);
// 2) 加载点云场景,完成后再开启贴花(贴花是全局状态,场景就绪后设置最稳妥)
var renderer = m_manager.GetRender(this.transform);
Debug.Log($"[DecalDemo] 开始加载: {m_filePath}");
renderer.Load(m_filePath,
_ => { /* 进度回调,示例不使用 */ },
OnSceneLoaded);
}
private void OnSceneLoaded()
{
Debug.Log("[DecalDemo] 加载完成,开启贴花投影。");
// ── 基础投影:5 个 API ──
m_manager.SetDecalTexture(m_decalTexture); // 贴花内容
m_manager.SetDecalOpacity(m_opacity); // 整体不透明度
m_manager.SetDecalEdgeFade(m_edgeFade); // 边缘羽化
ApplyProjection(); // 投影仪位姿(内部 SetDecalProjection)
m_manager.SetDecalEnabled(true); // 开启贴花 (_DECAL keyword)
// ── 遮挡剔除:2 个 API ── 必须在 ApplyProjection 之后(深度图需有效投影仪 VP)
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
m_manager.SetDecalOcclusion(m_occlusion);
CacheOcclusionState();
m_loaded = true;
}
/// <summary>
/// 仅演示使用,实际项目中,为提高性能,不需要在 Update() 方法中每帧调用
/// SetDecalProjection / SetDecalOpacity / SetDecalEdgeFade 等,仅在参数变化时调用相应 API 即可。
/// </summary>
void Update()
{
if (!m_loaded) return;
// 遮挡开关运行时切换
if (m_occlusion != m_lastOcclusion)
{
m_manager.SetDecalOcclusion(m_occlusion);
m_lastOcclusion = m_occlusion;
}
// 遮挡质量参数任一变化 → 一次性下发结构体
if (!Mathf.Approximately(m_occlusionCutoff, m_lastCutoff) ||
!Mathf.Approximately(m_occlusionBias, m_lastBias) ||
m_occlusionFootprint != m_lastFootprint ||
m_occlusionResolution != m_lastResolution)
{
m_manager.SetDecalOcclusionSettings(BuildOcclusionSettings());
CacheOcclusionState();
}
// 每帧刷新投影仪 / 外观,方便实时调试
if (!m_updateEveryFrame) return;
m_manager.SetDecalOpacity(m_opacity);
m_manager.SetDecalEdgeFade(m_edgeFade);
ApplyProjection();
}
/// <summary>用当前投影仪位姿与透视参数构建投影矩阵。</summary>
private void ApplyProjection()
{
Transform p = m_projector != null ? m_projector : this.transform;
float aspect = m_aspect > 0f
? m_aspect
: (m_decalTexture != null ? (float)m_decalTexture.width / m_decalTexture.height : 1f);
m_manager.SetDecalProjection(p.position, p.rotation, m_fov, aspect, m_near, m_far);
}
/// <summary>把 4 项遮挡参数汇集为结构体。</summary>
private DecalOcclusionSettings BuildOcclusionSettings()
{
return new DecalOcclusionSettings
{
opacityCutoff = m_occlusionCutoff,
depthBias = m_occlusionBias,
footprintMaxPx = m_occlusionFootprint,
resolution = m_occlusionResolution,
};
}
private void CacheOcclusionState()
{
m_lastOcclusion = m_occlusion;
m_lastCutoff = m_occlusionCutoff;
m_lastBias = m_occlusionBias;
m_lastFootprint = m_occlusionFootprint;
m_lastResolution = m_occlusionResolution;
}
// ── 便捷调试入口 (Inspector 右键) ──
[ContextMenu("Decal On")] private void DecalOn() { m_manager?.SetDecalEnabled(true); }
[ContextMenu("Decal Off")] private void DecalOff() { m_manager?.SetDecalEnabled(false); }
[ContextMenu("Occlusion On")] private void OcclusionOn() { m_occlusion = true; m_manager?.SetDecalOcclusion(true); m_lastOcclusion = true; }
[ContextMenu("Occlusion Off")] private void OcclusionOff() { m_occlusion = false; m_manager?.SetDecalOcclusion(false); m_lastOcclusion = false; }
}
注意:
贴花是全局着色状态,需要在 LCC 场景加载完成后再设置(即在 onComplete回调中设置)。
示例把
SetDecalTexture/SetDecalProjection/SetDecalEnabled放在Load完成回调(OnSceneLoaded)里,避免点云尚未就绪时设置。遮挡剔除相关的
SetDecalOcclusionSettings/SetDecalOcclusion必须在ApplyProjection(即SetDecalProjection)之后调用,因为遮挡深度图需要有效的投影仪视图投影矩阵。
步骤 4:挂载脚本与属性设置
- 将
Decal.cs脚本挂载到 Hierarchy 窗口中的 LCCRenderer 游戏对象上。 - 在 Inspector 窗口中,将 LCCManager 游戏对象拖动到脚本的 Manager 属性上。
- 将 Projector 游戏对象拖动到脚本的 Projector 属性上。
- 在脚本的 File Path 属性中填写 LCC 文件的路径地址,并将贴花纹理图片拖到 Decal Texture 属性上。
- 其他属性参数可根据需要调整,各属性含义见下表。

Decal 脚本的 Inspector 属性设置
| 字段 | 含义 | 默认值 |
|---|---|---|
| Manager | 场景中的 LCCManager 实例 | 必填 |
| File Path | 有效的 LCC 场景路径 | 必填 |
| Decal Texture | 要投影的贴花图片,RGB 为颜色、A 为贴花自身遮罩 | 必填 |
| Projector | 投影仪位姿,+Z 为投影方向;留空则用本物体 Transform | 空 |
| Fov | 投影仪透视视角 | 60 |
| Aspect | 投影宽高比;小于等于 0 时按贴花图片宽高比自动计算 | 0(自动) |
| Near / Far | 投影仪视锥近 / 远裁剪面 | 0.1 / 100 |
| Opacity | 贴花整体不透明度 | 1 |
| Edge Fade | 边缘羽化宽度(UV 比例),0 为硬边 | 0.1 |
| Update Every Frame | 每帧刷新投影,便于运行时拖动投影仪或改参数 | 勾选 |
| Occlusion | 是否开启遮挡剔除 | 关 |
| Occlusion Cutoff | opacity 阈值,仅 opacity 不低于该值的 splat 参与深度图 | 0 |
| Occlusion Bias | 相对深度容差 | 0.05 |
| Occlusion Footprint | 方形足迹半径上限(px),去穿透漏染主旋钮 | 2 |
| Occlusion Resolution | 遮挡深度图分辨率(px,2 的幂 256~4096) | 1024 |
步骤 5:运行效果
在 Unity 中点击 Play 按钮,场景加载完成后贴花会自动开启。当勾选 Update Every Frame 时,运行过程中可直接在 Scene 视图拖动投影仪对象,实时看到贴花在 3DGS 表面移动;调整 Opacity、Edge Fade 等参数也会即时生效。脚本组件的右键菜单还提供 Decal On/Off、Occlusion On/Off 四个快捷入口,便于单独验证开关效果。

运行后贴花投影到 3DGS 表面(基础投影,X 光式穿透)

开启遮挡剔除后,只有正对投影仪的前表面被染色(幻灯机效果)
常见问题:关闭兼容模式
如果 Unity Console 窗口报错且没有出现贴花效果,请关闭兼容模式:通过 Edit → Project Settings → Graphics 打开设置面板,选择 URP 选项卡,不要勾选 Render Graph → Compatibility Mode (Render Graph Disabled) 后的多选框。

不要勾选 Render Graph 兼容模式(Render Graph Disabled)
遮挡剔除与调参
遮挡剔除需在设置好投影仪之后再开启。示例在场景加载完成回调里先调用 SetDecalProjection,再调用 SetDecalOcclusionSettings 与 SetDecalOcclusion,以保证遮挡深度图拿到有效的投影仪视图投影矩阵。运行时把 Inspector 的 Occlusion 勾选打开,即可看到穿透块被剔除、只保留正对投影仪的前表面。
前提:
开启遮挡剔除前,URP Renderer Asset 中必须已注册 Decal Depth Feature(本工程已作为子资源写入
URP_Renderer.asset)。若未注册,遮挡深度图不会生成,遮挡不会生效。
遮挡质量由四个参数控制,通过一个结构体一次性下发。四者相互正交,按下表调节:
| 参数 | 作用 | 默认 | 调节建议 |
|---|---|---|---|
opacityCutoff | 过滤漂浮 / 雾状 splat,避免它们把遮挡面拉近 | 0 | 前景出现块状噪点时适度调大;调太大会漏染 |
depthBias | 相对深度容差,按距离吸收点云表面厚度 | 0.05 | 近面出现自遮挡噪点时调大;薄遮挡物漏挡时调小 |
footprintMaxPx | 前表面近深度按世界尺度盖满稀疏缝隙,根治穿透漏染(主旋钮) | 2 | 穿透块残留时调大;轮廓过遮挡时调小,0 为单纹素 |
resolution | 遮挡深度图分辨率 | 1024 | 掠射面出现成片漏染小方块时调低(如 512 / 256),代价是遮挡边界略糙 |
关键步骤
实现投影贴花的关键步骤:
- 场景中创建 Projector 空对象作为投影仪,其 +Z 朝向即投影方向。
- 若需要遮挡剔除,确保在 URP Renderer 中添加了 Decal Depth Feature 渲染特性。
- 在 LCC 场景加载完成后依次设置贴花纹理、外观、投影仪位姿,再开启贴花(
SetDecalEnabled(true))。 - 遮挡剔除的
SetDecalOcclusionSettings/SetDecalOcclusion需在SetDecalProjection之后调用。
贴花相关 API
以下方法均位于 LCCManager 门面类,仅 PC / Mac(URP 管线)生效,移动端 / VR 调用会被忽略并打印警告。
SetDecalEnabled
// 返回值:void
public void SetDecalEnabled(bool enable)
开关贴花投影。开启时会把当前缓存的不透明度 / 边缘羽化参数推到 GPU 并启用 _DECAL shader keyword。贴花是世界固定的全局状态,对所有相机一致。需另外调用 SetDecalTexture 与 SetDecalProjection 设置内容与投影仪位姿。
| 参数 | 类型 | 说明 |
|---|---|---|
enable | bool | 是否开启贴花投影 |
调用示例:
m_manager.SetDecalEnabled(true);
m_manager.SetDecalTexture(decalTex);
m_manager.SetDecalProjection(projectorPos, projectorRot, 60f, 1.778f, 0.1f, 20f);
说明:
- 关闭时仅禁用
_DECALkeyword,不清空已缓存的纹理 / 参数。
SetDecalTexture
// 返回值:void
public void SetDecalTexture(Texture2D tex)
设置贴花纹理。RGB 为颜色,A 为贴花自身遮罩,会与全局不透明度、边缘羽化相乘。
| 参数 | 类型 | 说明 |
|---|---|---|
tex | Texture2D | 贴花纹理 |
调用示例:
m_manager.SetDecalTexture(logoTex);
说明:
- 传入
null会清空全局纹理引用。
SetDecalProjection
// 返回值:void
public void SetDecalProjection(Vector3 pos, Quaternion rot, float fov, float aspect, float near, float far)
设置贴花投影仪的位姿与透视参数,投影仪沿其 forward(+Z)方向投影,视锥内的点被贴花覆盖。
| 参数 | 类型 | 说明 |
|---|---|---|
pos | Vector3 | 投影仪世界坐标 |
rot | Quaternion | 投影仪朝向 |
fov | float | 垂直 FOV(度),钳制 [1, 179] |
aspect | float | 宽高比,最小 1e-4 |
near | float | 近裁剪面,最小 1e-3 |
far | float | 远裁剪面,最小 near + 1e-3 |
调用示例:
m_manager.SetDecalProjection(new Vector3(0, 10, 0), Quaternion.Euler(90, 0, 0), 60f, 1.778f, 0.1f, 20f);
说明:
- 若已开启
SetDecalOcclusion(true),本方法会同步把新的 VP / view 转发给Decal Depth Feature。
SetDecalOpacity
// 返回值:void
public void SetDecalOpacity(float opacity)
设置贴花整体不透明度,立即推到 GPU 并缓存,供下次 SetDecalEnabled(true) 时复用。
| 参数 | 类型 | 说明 |
|---|---|---|
opacity | float | 贴花整体不透明度,范围 [0, 1] |
调用示例:
m_manager.SetDecalOpacity(0.8f);
说明:
- 范围
[0, 1],超出自动钳制。
SetDecalEdgeFade
// 返回值:void
public void SetDecalEdgeFade(float edgeFade)
设置贴花边缘羽化宽度。0 为硬边,值越大边缘过渡越柔和。
| 参数 | 类型 | 说明 |
|---|---|---|
edgeFade | float | 边缘羽化宽度,UV 空间比例,范围 [0, 0.5) |
调用示例:
m_manager.SetDecalEdgeFade(0.1f);
说明:
- 范围
[0, 0.5),超出自动钳制。
SetDecalOcclusion
// 返回值:void
public void SetDecalOcclusion(bool enable)
开关贴花遮挡剔除(基于投影仪深度图),与阴影功能正交、独立开关。开启前应已调用 SetDecalProjection 设定投影仪,否则深度图没有有效 VP。
| 参数 | 类型 | 说明 |
|---|---|---|
enable | bool | 是否开启贴花遮挡剔除 |
调用示例:
m_manager.SetDecalOcclusion(true);
说明:
- 仅 URP 支持(内置管线调用直接 no-op 并打印日志)。
- 需要
Decal Depth Feature已注册在 URP Renderer Asset 中。 - 依赖
SetDecalOcclusionSettings配置的质量参数。
SetDecalOcclusionSettings
// 返回值:void
public void SetDecalOcclusionSettings(DecalOcclusionSettings settings)
一次性设置贴花遮挡剔除的全部质量参数(四项含义见下方结构体与「遮挡剔除与调参」表)。
| 参数 | 类型 | 说明 |
|---|---|---|
settings | DecalOcclusionSettings | 遮挡剔除质量参数 |
调用示例:
m_manager.SetDecalOcclusionSettings(DecalOcclusionSettings.Default);
m_manager.SetDecalOcclusionSettings(new DecalOcclusionSettings {
opacityCutoff = 0.1f,
depthBias = 0.05f,
footprintMaxPx = 2,
resolution = 1024
});
说明:
- 随时可调,参数会立即推到 GPU 与
Decal Depth Feature。 - 变更
resolution会触发深度图 RT 重建。
相关数据结构
DecalOcclusionSettings
public struct DecalOcclusionSettings {
public float opacityCutoff; // [0,1] splat 参与深度图的最低 opacity,默认 0(全部参与)
public float depthBias; // 相对深度容差比例,默认 0.05
public int footprintMaxPx; // 方形足迹半径上限(px),0~16,默认 2
public int resolution; // 深度图分辨率(px,2 的幂,256~4096),默认 1024
public static DecalOcclusionSettings Default { get; } // cutoff=0, bias=0.05, footprint=2, resolution=1024
}
用于 SetDecalOcclusionSettings(),可通过 DecalOcclusionSettings.Default 获取默认值。
| 字段 | 含义 | 默认 |
|---|---|---|
opacityCutoff | float,[0,1],opacity 阈值,仅不低于该值的 splat 参与深度图 | 0 |
depthBias | float,相对深度容差 | 0.05 |
footprintMaxPx | int,0~16,方形足迹半径上限(px) | 2 |
resolution | int,2 的幂 256~4096,遮挡深度图分辨率 | 1024 |
注意事项
- 仅 PC / Mac 生效。 移动端与 VR 平台调用贴花相关接口会被忽略并打印警告,无需在业务侧做平台判断,但也不要期望在这些平台看到效果。
- 建议在场景加载完成后再设置贴花。 贴花是全局着色状态,示例把
SetDecalTexture/SetDecalProjection/SetDecalEnabled放在Load完成回调里,避免点云尚未就绪时设置。 - 遮挡剔除依赖两个前提。 一是 URP Renderer Asset 已注册 Decal Depth Feature,二是已调用过
SetDecalProjection设定投影仪;两者缺一则遮挡不生效。 - 静态投影几乎零额外开销。 遮挡深度图仅在投影仪位姿或点云发生变化时重算,投影仪静止时不产生额外的每帧计算。