ALCCActorBase:3DGS Actor 基类
所有 LCC Actor 的基类,负责把 3DGS 数据放进关卡。ALCCActor、ALCC2Actor、ASogActor、ASpzActor、APlyActor 都继承自它。
| 模块 | LCC4UnrealRuntime |
| 头文件 | LCCActorBase.h |
| 父类 | AActor |
| 蓝图 | 可继承(BlueprintType) |
#include "LCCActorBase.h"
Actor 本身不做渲染,它持有一个 ULCCComponentBase 并把加载类操作转发过去。渲染参数、颜色、性能、碰撞全部在 Component 上,通过 GetLCCComponent 获取。
缩放限制:LCC 系列 Actor 只支持等比缩放。 不要使用带负值的缩放(如
(-1, 1, 1))或各轴不等的缩放(如(2, 1, 3)),否则会出现渲染异常,画面上通常表现为一道线。
Properties
| 属性 | 类型 | 访问 | 说明 |
|---|---|---|---|
DefaultSceneRoot | ULCCFocusRootComponent* | 只读 | 不可见的根组件。存在的唯一目的是给编辑器视口一个固定的小包围盒,按 F 聚焦时不会因为场景巨大而失效。 |
LCCComponent | ULCCComponentBase* | 只读 | 真正干活的组件。子类构造时决定它的具体类型。 |
两个属性都是 VisibleAnywhere + BlueprintReadOnly,不能在细节面板或蓝图里替换,只能读。
Methods
Load
UFUNCTION(BlueprintCallable, Category = "XGrids")
virtual void Load(const FString& String) const;
按路径加载 3DGS 数据。这是运行时加载场景的主入口。
参数:
| 参数 | 类型 | 说明 |
|---|---|---|
String | const FString& | 数据文件路径。支持绝对路径(D:/Data/Tower/Tower.lcc)和相对路径。相对路径以工程 Content 目录为基准,例如 Tower/Tower.lcc 指向 Content/Tower/Tower.lcc。 |
不同子类接受的路径形态不同:
| Actor | 传入什么 |
|---|---|
ALCCActor | LCC1 的 .lcc 文件路径。文件名不固定,同目录必须有 data.bin 与 index.bin |
ALCC2Actor | LCC2 的 .lcc2 文件路径。文件名不固定 |
ASogActor | .sog 文件路径 |
ASpzActor | .spz 文件路径 |
APlyActor | .ply 文件路径,扩展名不是 .ply 会被直接拒绝 |
注:
.lcc与.lcc2的文件名由数据生产端决定,不要假设它一定叫meta.lcc。判断格式请看扩展名,或用 ULCCUtilLibrary::DetermineFileFormat。
用法要点:
- 方法是
const,因为状态变更发生在LCCComponent内部,不修改 Actor 自身。 - 加载是异步的。调用返回时数据还没就绪,当前版本需要用
GetLCCComponent()->CheckIfLoaded()轮询。后续版本会提供加载完成回调,届时不必再轮询。 CheckIfLoaded()返回true只代表元信息与索引已建立、可以安全读参数配参数,Splat 数据仍在按视角流式加载,画面会继续补齐。- 想在关卡里预设路径而不写代码,直接在细节面板设 Component 的
DefaultLoadPath,运行时会自动加载。 - 传入空路径等价于卸载当前数据。
- 传入的路径与当前已加载的路径相同时,调用会直接返回,不会重新加载。
- 路径可疑时先用 ULCCUtilLibrary::CheckLCCValid 校验,避免加载失败只在日志里报错。
C++ 示例:
#include "LCCActor.h"
#include "LCCComponentBase.h"
void AMyGameMode::SpawnLCCScene()
{
// 生成 Actor
ALCCActor* LCCActor = GetWorld()->SpawnActor<ALCCActor>(
ALCCActor::StaticClass(),
FVector::ZeroVector,
FRotator::ZeroRotator);
if (!LCCActor)
{
return;
}
// 绝对路径加载
LCCActor->Load(TEXT("D:/Data/Tower/Tower.lcc"));
// 或相对 Content 目录加载
// LCCActor->Load(TEXT("Tower/Tower.lcc"));
}
等待加载完成后再操作。当前版本没有回调,只能轮询:
void AMyActor::LoadAndConfigure()
{
LCCActor->Load(TEXT("D:/Data/Tower/Tower.lcc"));
// 每 0.2 秒检查一次是否就绪
GetWorld()->GetTimerManager().SetTimer(
LoadCheckTimer, this, &AMyActor::OnCheckLoaded, 0.2f, true);
}
void AMyActor::OnCheckLoaded()
{
ULCCComponentBase* Component = LCCActor->GetLCCComponent();
if (!Component || !Component->CheckIfLoaded())
{
return;
}
GetWorld()->GetTimerManager().ClearTimer(LoadCheckTimer);
// 元信息已就绪,可以安全配参数(Splat 数据仍在流式加载)
Component->SetRenderMode(ERenderMode::Splatting);
UE_LOG(LogTemp, Log, TEXT("Total splats: %d"), Component->GetSplatNumber());
}
文本蓝图:
[Event BeginPlay]
│
▼
[Spawn Actor from Class]
Class = LCCActor
Spawn Transform = (默认)
│ Return Value ──┐
▼ │
[Load] ◀────────────────┘
Target = (上一步的 Return Value)
String = "D:/Data/Tower/Tower.lcc"
UnLoad
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayName = "UnLoad", DisplayPriority = 2))
virtual void UnLoad();
卸载当前数据并释放资源,包括 GPU 缓冲、节点缓存和碰撞体。
用法要点:
- 带
CallInEditor,细节面板 Actions 分类下有一个 UnLoad 按钮,编辑器里可直接点。 - 卸载后 Actor 还在关卡里,只是不再渲染任何内容。再调
Load可以加载别的数据。 - 切换大场景时先
UnLoad再Load,避免两份数据同时占用显存。 - 不需要手动在
EndPlay里调用,Component 销毁时会自行清理。
C++ 示例,切换场景:
void AMyManager::SwitchScene(const FString& NewPath)
{
// 先释放旧数据,防止显存峰值叠加
LCCActor->UnLoad();
LCCActor->Load(NewPath);
}
Refresh
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayPriority = 3))
virtual void Refresh();
重新加载当前数据。内部实现是先 UnLoad() 再 Load(),所以开销与一次完整重新加载相当,不是轻量操作。
之所以能重新加载「当前」数据,是因为加载成功时会把路径同步写入 DefaultLoadPath,Refresh 取的就是这个值。
用法要点:
- 磁盘上的数据文件被替换后,用它重新读盘。
- 想让渲染结果重新计算一遍,不要用它。改属性走 Setter 会自动触发更新;确实需要推一帧的话用 Component 的
ForceUpdate(),那个才是轻量的。 - 重新加载后需要重新等待就绪,之前对 Component 设置的运行时参数需要视情况重设。
- 编辑器细节面板 Actions 分类下有对应按钮。
GetLCCComponent
UFUNCTION(BlueprintCallable, Category = "XGrids")
ULCCComponentBase* GetLCCComponent() const;
返回 Actor 内部的 Component 指针。这是访问全部渲染能力的入口。
返回值:ULCCComponentBase*。正常情况下非空,Actor 构造时就已创建。
用法要点:
- 需要子类专属能力时向下转型。
ALCCActor拿到的是ULCCComponent,ALCC2Actor与三种单文件 Actor 拿到的是ULCC2Component。 - 转型一律用
Cast<>,不要用 C 风格强转。类型不匹配时Cast返回nullptr,方便早退。
C++ 示例,改基类通用参数:
ULCCComponentBase* Component = LCCActor->GetLCCComponent();
if (Component)
{
Component->SetSplatScale(0.8f);
Component->SetGlobalAlpha(1.0f);
}
向下转型访问 LCC2 专属参数:
#include "LCC2Component.h"
ULCC2Component* LCC2Comp = Cast<ULCC2Component>(LCC2Actor->GetLCCComponent());
if (LCC2Comp)
{
LCC2Comp->SetSHBands(2);
LCC2Comp->NormalMode = ELCC2NormalGenerationMode::Hemispherical;
}
文本蓝图,蓝图里转型用 Cast To 节点:
[Get LCC Component]
Target = LCCActor
│ Return Value ──┐
▼ │
[Cast To LCC2Component] ◀┘
Object = (上一步的 Return Value)
│ Cast Succeeded
▼
[Set SH Bands]
Target = (Cast 输出的 As LCC2 Component)
In SH Bands = 2
SelectFile
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayName = "Load", DisplayPriority = 1))
virtual void SelectFile();
弹出系统文件选择对话框,选中后立即加载。
用法要点:
- 细节面板上显示为 Load 按钮,是编辑器里加载数据最常用的入口。
- 各子类重写了它,用于过滤对应扩展名:
ASogActor只列.sog,ASpzActor只列.spz,APlyActor只列.ply。 - 依赖编辑器的对话框能力,打包后的运行时不要用它,运行时请用 Load。
DebugNodeBound
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayName = "Debug Node Bound", DisplayPriority = 4))
void DebugNodeBound();
开关八叉树节点边界的可视化,用线框盒画出当前加载的节点。
盒体颜色对应节点 Level:红、橙、黄、绿、蓝、紫依次递进,红色是最低 Level(细节最高),白色是最高 Level(细节最低)。
用法要点:
- 排查 LOD 问题时用。看到远处还在渲染红色盒子,说明
LevelFactor偏小或StartLevel设得太低,性能被浪费了。 - 反过来,近处全是高 Level 的冷色盒子,说明细节被压得过狠,画面会发糊。
- 再调用一次关闭,是开关式的。
- 只在编辑器与开发构建下有效。
Stats
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayPriority = 5))
void Stats();
开关渲染统计面板,等价于控制台执行 stat XGrids。
面板给出当前渲染的 Splat 数量、加载中的节点数、各阶段耗时。调性能参数时对着它看效果,比凭感觉调靠谱。
ShowCollision
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayName = "Show Collision", DisplayPriority = 6))
void ShowCollision();
开关碰撞线框可视化,等价于控制台执行 r.xgrids.DrawCollision。
用法要点:
- 前提是数据本身带碰撞,且 Component 的
bEnableCollision已打开,否则什么都不会显示。 - 角色穿模、射线打不中的时候先开这个,确认碰撞体到底有没有加载出来。
- 碰撞是按距离流式加载的,远处没有线框属于正常表现,受
Performance.CollisionLoadMaxDistance控制。
ShowFPS
UFUNCTION(BlueprintCallable, CallInEditor, Category = "Actions",
meta = (DisplayName = "Show FPS", DisplayPriority = 7))
void ShowFPS();
开关帧率显示,等价于控制台执行 stat fps。配合 Stats 一起用,一边看帧率一边看 Splat 数量。
Complete Example
运行时加载一个 LCC 场景,就绪后配置渲染参数:
// MyLCCLoader.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
#include "MyLCCLoader.generated.h"
UCLASS()
class AMyLCCLoader : public AActor
{
GENERATED_BODY()
public:
/** 要加载的数据路径,绝对路径或相对 Content 目录 */
UPROPERTY(EditAnywhere, Category = "MyLCC")
FString ScenePath = TEXT("Tower/Tower.lcc");
protected:
virtual void BeginPlay() override;
private:
void OnLoadCheck();
UPROPERTY()
TObjectPtr<class ALCCActor> LCCActor;
FTimerHandle LoadCheckTimer;
};
// MyLCCLoader.cpp
#include "MyLCCLoader.h"
#include "LCCActor.h"
#include "LCCComponentBase.h"
void AMyLCCLoader::BeginPlay()
{
Super::BeginPlay();
LCCActor = GetWorld()->SpawnActor<ALCCActor>(
ALCCActor::StaticClass(), GetActorTransform());
if (!LCCActor)
{
return;
}
LCCActor->Load(ScenePath);
GetWorld()->GetTimerManager().SetTimer(
LoadCheckTimer, this, &AMyLCCLoader::OnLoadCheck, 0.2f, true);
}
void AMyLCCLoader::OnLoadCheck()
{
ULCCComponentBase* Component = LCCActor ? LCCActor->GetLCCComponent() : nullptr;
if (!Component || !Component->CheckIfLoaded())
{
return;
}
GetWorld()->GetTimerManager().ClearTimer(LoadCheckTimer);
// 元信息已就绪,按需要配置
Component->SetRenderMode(ERenderMode::Splatting);
Component->SetSplatScale(0.9f);
Component->SetMaxDistance(200); // 最远渲染 200 米
Component->SetMaxSplatNum(1500); // 每帧最多 1500 万个 Splat
Component->SetLCCCollisionEnable(true);
UE_LOG(LogTemp, Log, TEXT("LCC ready, splat number: %d"),
Component->GetSplatNumber());
}
See Also
- ULCCComponentBase:渲染与性能参数都在这里
- SOG / SPZ / PLY Actors:三种单文件格式的专用 Actor
- ULCCUtilLibrary:加载前的路径与格式校验