2025-06-03 10:19:29 +08:00
|
|
|
|
---
|
|
|
|
|
title: Untitled
|
|
|
|
|
date: 2025-06-03 10:19:25
|
|
|
|
|
excerpt:
|
|
|
|
|
tags:
|
|
|
|
|
rating: ⭐
|
|
|
|
|
---
|
2025-06-03 10:50:23 +08:00
|
|
|
|
# 前言
|
|
|
|
|
1. ShaderWorldPCGInterop:
|
|
|
|
|
2. ShaderWorld:
|
2025-06-03 18:24:36 +08:00
|
|
|
|
3. ShaderWorldCore:
|
|
|
|
|
|
|
|
|
|
# ShaderWorld
|
|
|
|
|
- Class
|
|
|
|
|
- Actor
|
|
|
|
|
- ShaderWorldActor.h:[[#AShaderWorldActor]]
|
|
|
|
|
- SWorld.h:[[#ASWorld]]
|
|
|
|
|
|
2025-06-25 16:20:36 +08:00
|
|
|
|
# USWorldSubsystem
|
|
|
|
|
主要管理:
|
|
|
|
|
- TArray<USWContextBase*> SW_Contexts
|
|
|
|
|
- 渲染相关变量:RT_Ready、RenderThreadPoked、RenderThreadResponded处理。
|
|
|
|
|
# ASWorld
|
2025-06-03 18:24:36 +08:00
|
|
|
|
|
2025-06-25 16:20:36 +08:00
|
|
|
|
# AShaderWorldActor
|
2025-06-25 20:44:30 +08:00
|
|
|
|
## 大致启动流程
|
2025-06-25 16:20:36 +08:00
|
|
|
|
大致启动流程:
|
2025-06-25 17:48:04 +08:00
|
|
|
|
- BeginPlay()
|
|
|
|
|
1. 设置SWCamera Location。
|
|
|
|
|
2. 清空所有数据。
|
|
|
|
|
3. InitiateWorld()
|
2025-06-25 20:44:30 +08:00
|
|
|
|
1. 更新相关变量:GenerateCollision_last、VerticalRangeMeters_last、WorldHasBounds_OnRebuild。**Segmented_Initialized** 、**bExportPhysicalMaterialID_cached**
|
2025-06-25 18:25:35 +08:00
|
|
|
|
2. 设置高度生成用材质变量 Generator;bool bHadGeneratorAtRebuildTime = IsValid(Generator);
|
|
|
|
|
3. LOD_Num与WorldDimensionMeters计算。
|
2025-06-25 19:05:47 +08:00
|
|
|
|
4. 重新生成若干数组:
|
|
|
|
|
1. LODs_DimensionsMeters
|
|
|
|
|
2. ClipMapToUpdateAndMove
|
|
|
|
|
3. ClipMapToUpdate
|
|
|
|
|
4. NeedSegmentedUpdate
|
2025-06-25 20:44:30 +08:00
|
|
|
|
5. AdaptiveTopology
|
2025-06-25 19:05:47 +08:00
|
|
|
|
5. 相关线程安全变量:
|
|
|
|
|
1. Shareable_ID_FarLOD
|
|
|
|
|
2. UpdateHOverTime
|
2025-06-25 20:44:30 +08:00
|
|
|
|
6. ComputeOptimalVirtualCacheSize()
|
|
|
|
|
7. for (int32 i = (WorldHasBounds_OnRebuild ? -1 : 0); i < LOD_Num; i++)
|
|
|
|
|
8. Toggle Async creation of Clipmap Meshes
|
|
|
|
|
9. Async TaskGraph ClipMapMeshCreation
|
2025-06-25 17:48:04 +08:00
|
|
|
|
- Tick()
|
2025-06-25 20:44:30 +08:00
|
|
|
|
- 初始化
|
|
|
|
|
1. 取得USWorldSubsystem指针,并且进行初始化。
|
|
|
|
|
- ReadbacksManagement()
|
|
|
|
|
- CollisionManagement()
|
|
|
|
|
- SpawnablesManagement()
|
|
|
|
|
- TerrainAndSpawnablesManagement()
|
|
|
|
|
|
|
|
|
|
### ReadbacksManagement()
|
2025-06-26 23:33:30 +08:00
|
|
|
|
主要用于回读RetrieveHeightAt()调用后GPU计算出来的Position信息。在**用于计算HeightReadBack的渲染线程、RHI、GPU都执行完成**后,从ReadBackHeightData的像素点(最多25个)中读取Z轴信息,之后和PointsPendingReadBacks中的X、Y轴信息合并成一个Vector3 Position。
|
|
|
|
|
|
2025-06-25 23:32:48 +08:00
|
|
|
|
- ThreadSafe
|
|
|
|
|
- bool
|
|
|
|
|
- **bProcessingHeightRetrieval**
|
|
|
|
|
- **bProcessingHeightRetrievalRT**
|
|
|
|
|
- FSWShareableSamplePoints
|
|
|
|
|
- PointsPendingReadBacks
|
|
|
|
|
- FSWColorRead
|
|
|
|
|
- ReadBackHeightData
|
|
|
|
|
- FRenderCommandFence
|
|
|
|
|
- HeightReadBackFence
|
2025-06-25 20:44:30 +08:00
|
|
|
|
|
2025-06-25 23:32:48 +08:00
|
|
|
|
```c++
|
|
|
|
|
void AShaderWorldActor::ReadbacksManagement()
|
|
|
|
|
{
|
|
|
|
|
if (!bProcessingHeightRetrieval.IsValid())
|
|
|
|
|
bProcessingHeightRetrieval = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
if (!bProcessingHeightRetrievalRT.IsValid())
|
|
|
|
|
bProcessingHeightRetrievalRT = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
|
|
|
|
|
//PointsPendingReadBacks存储从GPU回读的采样坐标位置;在RetrieveHeightAt()调用HeightReadBackFence.BeginFence(true)开启渲染栅栏,不开启这段逻辑不会执行。
|
|
|
|
|
if ((*bProcessingHeightRetrieval.Get()) && (*bProcessingHeightRetrievalRT.Get()) && HeightReadBackFence.IsFenceComplete() && PointsPendingReadBacks.IsValid())
|
|
|
|
|
{
|
|
|
|
|
// ReadBackHeightData uin8 贴图数据
|
|
|
|
|
if (!ReadBackHeightData.IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
//For now we do Compute samples on a rendertarget 5x5, therefore 25 positions evaluated per request.
|
|
|
|
|
//每次请求可以读取25个点。
|
|
|
|
|
const int NumOfVertex = 25;
|
|
|
|
|
|
|
|
|
|
PositionsOfReadBacks.Empty();
|
|
|
|
|
PositionsOfReadBacks.AddUninitialized(25);
|
|
|
|
|
|
|
|
|
|
uint8* ReadData8 = (uint8*)ReadBackHeightData->ReadData.GetData();
|
|
|
|
|
uint16 MaterialIndice = 0;
|
|
|
|
|
for (int32 k = 0; k < NumOfVertex; k++)
|
|
|
|
|
{
|
|
|
|
|
FVector3f& PositionSample = PositionsOfReadBacks[k];
|
|
|
|
|
|
|
|
|
|
if (RendererAPI == EGeoRenderingAPI::OpenGL)
|
|
|
|
|
{
|
|
|
|
|
int X = k % 5;
|
|
|
|
|
int Y = k / 5;
|
|
|
|
|
|
|
|
|
|
int index = X + Y * 5;
|
|
|
|
|
|
|
|
|
|
Y = (5 - 1) - Y;
|
|
|
|
|
|
|
|
|
|
index = X + Y * 5;
|
|
|
|
|
|
|
|
|
|
PositionSample = FVector3f(PointsPendingReadBacks->PositionsXY[2 * k], PointsPendingReadBacks->PositionsXY[2 * k + 1], GetHeightFromGPURead(&ReadData8[index * 4], MaterialIndice) / HeightScale);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
PositionSample = FVector3f(PointsPendingReadBacks->PositionsXY[2 * k], PointsPendingReadBacks->PositionsXY[2 * k + 1], GetHeightFromGPURead(&ReadData8[k * 4], MaterialIndice) / HeightScale);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks))
|
|
|
|
|
{
|
|
|
|
|
//SW_LOG("HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks) PositionsOfReadBacks[0] %s",*PositionsOfReadBacks[0].ToString())
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//SW_LOG("Fail HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bProcessingHeightRetrieval->AtomicSet(false);
|
|
|
|
|
bProcessingHeightRetrievalRT->AtomicSet(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//For now MinMax is here
|
|
|
|
|
//处理FClipMapMeshElement的最大最小队列?
|
|
|
|
|
if (UseSegmented())
|
|
|
|
|
{
|
|
|
|
|
if(WorldHasBounds_OnRebuild)
|
|
|
|
|
{
|
|
|
|
|
FClipMapMeshElement& MeshEl = FarLOD_BoundedWorld;
|
|
|
|
|
|
|
|
|
|
if (MeshEl.MinMaxQueue.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
MeshEl.ProcessMinMaxQueue(SWorldSubsystem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (FClipMapMeshElement& MeshEl : Meshes)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (MeshEl.MinMaxQueue.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
MeshEl.ProcessMinMaxQueue(SWorldSubsystem);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### CollisionManagement()
|
2025-06-26 18:45:36 +08:00
|
|
|
|
- ThreadSafe
|
|
|
|
|
- bool
|
|
|
|
|
- bProcessingGroundCollision
|
|
|
|
|
- bPreprocessingCollisionUpdate
|
|
|
|
|
- EditRebuild:传递Bool值给 rebuild。
|
|
|
|
|
- FSWCollisionManagementShareableData
|
|
|
|
|
- CollisionShareable
|
|
|
|
|
- FRenderCommandFence
|
|
|
|
|
- 1
|
|
|
|
|
- bool
|
|
|
|
|
- RedbuildCollisionContext
|
|
|
|
|
- Array
|
2025-06-27 18:34:16 +08:00
|
|
|
|
- CollisionReadToProcess:
|
|
|
|
|
- CollisionWorkQueue:类型为FCollisionProcessingWork,碰撞处理任务队列,将回读的
|
|
|
|
|
- FCollisionMeshElement
|
|
|
|
|
- ReadBackCompletion:碰撞数据GPU回读是否完成。
|
2025-06-26 18:45:36 +08:00
|
|
|
|
|
|
|
|
|
|
2025-06-25 23:40:46 +08:00
|
|
|
|
```c++
|
|
|
|
|
void AShaderWorldActor::CollisionManagement(float& DeltaT)
|
|
|
|
|
{
|
|
|
|
|
SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionManagement", FColor::Magenta);
|
|
|
|
|
SW_FCT_CYCLE()
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Can we execute compute shader?
|
|
|
|
|
* Do we need to rebuild collision?
|
|
|
|
|
* Did ShaderWorld toggled collision generation On/Off?
|
|
|
|
|
* Are we pending a full rebuild of the Shader World?
|
|
|
|
|
*/
|
|
|
|
|
if (!SetupCollisions())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Using collision updates, update Collision meshes
|
|
|
|
|
*/
|
|
|
|
|
if (!CollisionFinalizeWork())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
if (CollisionProcess.IsFenceComplete())
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Convert Compute shader results to actionable collision updates
|
|
|
|
|
*/
|
|
|
|
|
if (!CollisionPreprocessGPU())
|
|
|
|
|
{
|
|
|
|
|
CollisionProcess.BeginFence(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Process GPU work queue by launching GPU tasks to evaluate the collision of new tiles
|
|
|
|
|
*/
|
|
|
|
|
CollisionGPU();
|
|
|
|
|
|
|
|
|
|
// Timer
|
|
|
|
|
{
|
|
|
|
|
CollisionUpdateTimeAcu += DeltaT;
|
|
|
|
|
|
|
|
|
|
if (CollisionUpdateTimeAcu <= 1.f / 10.f || CollisionWorkQueue.Num() > 0 || !CollisionProcess.IsFenceComplete())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
CollisionUpdateTimeAcu = 0.f;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Gather relevant collision tiles
|
|
|
|
|
* If an old tile is not relevant anymore, release it.
|
|
|
|
|
* If a new location needs to be computed: allocate a tile and add its relevant computation work to the GPU work queue
|
|
|
|
|
*/
|
|
|
|
|
CollisionCPU();
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-06-26 17:36:24 +08:00
|
|
|
|
|
2025-06-25 23:40:46 +08:00
|
|
|
|
#### SetupCollisions()
|
2025-06-27 18:34:16 +08:00
|
|
|
|
设置相关变量。
|
2025-06-26 18:45:36 +08:00
|
|
|
|
```c++
|
|
|
|
|
bool AShaderWorldActor::SetupCollisions()
|
|
|
|
|
{
|
|
|
|
|
SW_FCT_CYCLE()
|
|
|
|
|
|
|
|
|
|
if (!bHadGeneratorAtRebuildTime)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!Shareable_ID.IsValid() || Meshes.Num() <= 0)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
//初始化线程安全变量
|
|
|
|
|
if (!bProcessingGroundCollision.IsValid())
|
|
|
|
|
bProcessingGroundCollision = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
if (!bPreprocessingCollisionUpdate.IsValid())
|
|
|
|
|
bPreprocessingCollisionUpdate = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
|
|
|
|
|
if (EditRebuild)
|
|
|
|
|
{
|
|
|
|
|
EditRebuild.AtomicSet(false);
|
|
|
|
|
rebuild = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rebuild)
|
|
|
|
|
RedbuildCollisionContext = true;
|
|
|
|
|
//重建相关变量初始化
|
|
|
|
|
if (RedbuildCollisionContext)
|
|
|
|
|
{
|
|
|
|
|
if (!(*bProcessingGroundCollision.Get()) && !(*bPreprocessingCollisionUpdate.Get())
|
|
|
|
|
&& CollisionProcess.IsFenceComplete()
|
|
|
|
|
&& (CollisionMesh.Num() <= 0)
|
|
|
|
|
&& (UsedCollisionMesh.Num() <= 0))
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
CollisionShareable = nullptr;
|
|
|
|
|
|
|
|
|
|
CollisionWorkQueue.Empty();
|
|
|
|
|
CollisionReadToProcess.Empty();
|
|
|
|
|
|
|
|
|
|
RedbuildCollisionContext = false;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (RedbuildCollisionContext)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!GenerateCollision)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!CollisionShareable.IsValid())
|
|
|
|
|
CollisionShareable = MakeShared<FSWCollisionManagementShareableData, ESPMode::ThreadSafe>(CollisionResolution, CollisionVerticesPerPatch);
|
|
|
|
|
|
|
|
|
|
if ((*bProcessingGroundCollision.Get()) || (*bPreprocessingCollisionUpdate.Get()) || !CameraSet || (Meshes.Num() == 0))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
//Let the data layer be computed before generating collisions and trying to extract material IDs
|
|
|
|
|
if (bExportPhysicalMaterialID && (WorldCycle < 2))
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (CollisionVisibleChanged)
|
|
|
|
|
{
|
|
|
|
|
if (GetWorld())
|
|
|
|
|
{
|
|
|
|
|
for (auto& ColM : CollisionMesh)
|
|
|
|
|
{
|
|
|
|
|
if (auto& Mesh = ColM.Value.Mesh)
|
|
|
|
|
{
|
|
|
|
|
Mesh->SetMeshSectionVisible(0, CollisionVisible);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CollisionVisibleChanged.AtomicSet(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-06-25 23:32:48 +08:00
|
|
|
|
|
2025-06-26 23:34:25 +08:00
|
|
|
|
#### CollisionFinalizeWork()
|
2025-06-27 11:02:45 +08:00
|
|
|
|
结束碰撞生成任务,true为完成,false为任务超时未完成。
|
|
|
|
|
- TMap<FName,FCollisionMeshElement> CollisionMesh:碰撞Name => 碰撞Mesh Map。
|
|
|
|
|
- FCollisionMeshElement:管理单个碰撞数据的结构体。
|
2025-06-27 11:49:20 +08:00
|
|
|
|
- UBodySetup:虚幻引擎中一个重要的物理相关类,主要用于定义和存储物体的物理属性和碰撞设置
|
2025-06-27 11:02:45 +08:00
|
|
|
|
|
2025-06-26 23:34:25 +08:00
|
|
|
|
```c++
|
|
|
|
|
bool AShaderWorldActor::CollisionFinalizeWork()
|
|
|
|
|
{
|
|
|
|
|
SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionFinalizeWork()", FColor::Magenta);
|
|
|
|
|
SW_FCT_CYCLE()
|
2025-06-27 11:02:45 +08:00
|
|
|
|
|
2025-06-26 23:34:25 +08:00
|
|
|
|
if (!CollisionReadToProcess.IsEmpty())
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
const double GameThreadBudget_ms = SWGameThreadBudgetCollision_ms.GetValueOnGameThread();
|
|
|
|
|
|
|
|
|
|
double TimeStart = FPlatformTime::Seconds();
|
|
|
|
|
|
2025-06-27 11:02:45 +08:00
|
|
|
|
//判断处理队列是否为空
|
2025-06-26 23:34:25 +08:00
|
|
|
|
if(CollisionWorkQueue.Num()<=0)
|
|
|
|
|
return true;
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
FScopeLock CollisionMeshArrayAccess(&CollisionMeshAccessLock);
|
|
|
|
|
for (int i = CollisionWorkQueue.Num() - 1; i >= 0; i--)
|
|
|
|
|
{
|
2025-06-27 11:02:45 +08:00
|
|
|
|
//判断生成时间是否是否超过预设数值,如果超过就直接返回。默认为1 ms。
|
2025-06-26 23:34:25 +08:00
|
|
|
|
if ((FPlatformTime::Seconds() - TimeStart) * 1000.0 > GameThreadBudget_ms)
|
|
|
|
|
return false;
|
|
|
|
|
|
2025-06-27 11:02:45 +08:00
|
|
|
|
//将生成的碰撞数据赋予给对应的碰撞Mesh,最后移除任务队列中的任务。
|
2025-06-26 23:34:25 +08:00
|
|
|
|
FCollisionProcessingWork& Work = CollisionWorkQueue[i];
|
|
|
|
|
ensure(CollisionMesh.Find(Work.MeshID));
|
|
|
|
|
FCollisionMeshElement& Mesh = CollisionMesh[Work.MeshID];
|
|
|
|
|
Mesh.Mesh->UpdateSectionTriMesh(Work.DestB);
|
|
|
|
|
CollisionWorkQueue.RemoveAt(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
CollisionWorkQueue.Empty();
|
|
|
|
|
return true;
|
2025-06-27 11:02:45 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void UShaderWorldCollisionComponent::UpdateSectionTriMesh(TSharedPtr<FSWShareableVerticePositionBuffer, ESPMode::ThreadSafe>& Positions)
|
|
|
|
|
{
|
2025-06-27 18:34:16 +08:00
|
|
|
|
//当UBodySetup更新队列还有任务时,执行改函数会将FSWShareableVerticePositionBuffer加入到UpdatesReceivedDuringCompute数组中并且退出。
|
2025-06-27 11:02:45 +08:00
|
|
|
|
if (AsyncBodySetupQueue.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
#if SWDEBUG
|
|
|
|
|
SW_LOG("Collision Update received during alreayd occuring computation %s",*GetName())
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
UpdatesReceivedDuringCompute.Add(Positions);
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
UpdatesReceivedDuringCompute.Empty();
|
|
|
|
|
|
2025-06-27 18:34:16 +08:00
|
|
|
|
//使用FSWShareableVerticePositionBuffer数据来更新当前UShaderWorldCollisionComponent
|
2025-06-27 11:02:45 +08:00
|
|
|
|
bool EnsureSameBuffers = ProcMeshSections.Num() > 0 && ProcMeshSections[0].PositionBuffer.IsValid() && (ProcMeshSections[0].PositionBuffer->Positions3f.Num() == 0 || ProcMeshSections[0].PositionBuffer->Positions3f.Num() == Positions->Positions.Num());
|
|
|
|
|
if(!EnsureSameBuffers)
|
|
|
|
|
{
|
|
|
|
|
#if SWDEBUG
|
|
|
|
|
UE_LOG(LogTemp,Warning,TEXT("Error UpdateSectionTriMesh : buffers incompatible"));
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ProcMeshSections[0].PositionBuffer.Reset();
|
|
|
|
|
ProcMeshSections[0].Normals.Reset();
|
|
|
|
|
|
|
|
|
|
ProcMeshSections[0].PositionBuffer = Positions;
|
|
|
|
|
ProcMeshSections[0].SectionLocalBox = Positions->Bound;
|
|
|
|
|
ProcMeshSections[0].bEnableCollision = true;
|
2025-06-26 23:34:25 +08:00
|
|
|
|
|
2025-06-27 11:02:45 +08:00
|
|
|
|
Async(EAsyncExecution::TaskGraph, [WeakThis = MakeWeakObjectPtr(this), PBuffer = Positions, IndexB = ProcMeshSections[0].IndexBuffer]
|
|
|
|
|
{
|
|
|
|
|
TSharedPtr<TArray<FVector>, ESPMode::ThreadSafe> Normals = SWComputeNormalsForPatch(PBuffer, IndexB);
|
|
|
|
|
|
|
|
|
|
AsyncTask(ENamedThreads::GameThread, [WeakThis,PBuffer, Normals]()
|
|
|
|
|
{
|
|
|
|
|
if (WeakThis.IsValid())
|
|
|
|
|
{
|
|
|
|
|
if (UShaderWorldCollisionComponent* Comp = Cast<UShaderWorldCollisionComponent>(WeakThis.Get()))
|
|
|
|
|
{
|
2025-06-27 18:34:16 +08:00
|
|
|
|
//塞入Normal
|
2025-06-27 11:02:45 +08:00
|
|
|
|
if(IsValid(Comp))
|
|
|
|
|
Comp->ReceiveComputedNormals(PBuffer, Normals);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//Materials
|
|
|
|
|
// Pass new positions to trimesh
|
2025-06-27 18:34:16 +08:00
|
|
|
|
UpdateCollision();//1. 异步Cook UBodySetup 2. 删除原本的碰撞 3. UseBodySetup->CreatePhysicsMeshesAsync(),使用UBodySetup异步创建新的碰撞网格。
|
|
|
|
|
UpdateLocalBounds();//更新UShaderWorldCollisionComponent的LocalBoundingBox
|
2025-06-27 11:02:45 +08:00
|
|
|
|
UpdateNavigation();
|
|
|
|
|
|
|
|
|
|
if (ProcMeshSections.Num() > 0 && (ProcMeshSections[0].bSectionVisible || (GetWorld() && !GetWorld()->IsGameWorld())))
|
|
|
|
|
{
|
|
|
|
|
// If we have a valid proxy and it is not pending recreation
|
|
|
|
|
if (SceneProxy && !IsRenderStateDirty())
|
|
|
|
|
{
|
|
|
|
|
//SW_LOG("Update trimesh SceneProxy && !IsRenderStateDirty()")
|
|
|
|
|
// Create data to update section
|
|
|
|
|
FShaderWColProcMeshSectionUpdateData* SectionData = new FShaderWColProcMeshSectionUpdateData;
|
|
|
|
|
SectionData->TargetSection = 0;
|
|
|
|
|
SectionData->NewPositionBuffer = ProcMeshSections[0].PositionBuffer;
|
|
|
|
|
|
2025-06-27 18:34:16 +08:00
|
|
|
|
//更新SceneProxy FShaderWProceduralMeshSceneProxy的NewPositionBuffer,也就是UpdateSection
|
2025-06-27 11:02:45 +08:00
|
|
|
|
if(AShaderWorldActor* owner = Cast<AShaderWorldActor>(GetOwner()))
|
|
|
|
|
{
|
|
|
|
|
{
|
|
|
|
|
// Enqueue command to send to render thread
|
|
|
|
|
FShaderWProceduralMeshSceneProxy* ProcMeshSceneProxy = (FShaderWProceduralMeshSceneProxy*)(SceneProxy && !IsRenderStateDirty() ? SceneProxy : nullptr);
|
|
|
|
|
ENQUEUE_RENDER_COMMAND(FGeoCProcMeshSectionUpdate)
|
|
|
|
|
([ProcMeshSceneProxy, SectionData](FRHICommandListImmediate& RHICmdList)
|
|
|
|
|
{
|
|
|
|
|
if(ProcMeshSceneProxy)
|
|
|
|
|
ProcMeshSceneProxy->UpdateSection_RenderThread(SectionData);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//SW_LOG("Update trimesh !owner")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
//SW_LOG("Update trimesh !(SceneProxy && !IsRenderStateDirty())")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
MarkRenderTransformDirty();
|
|
|
|
|
}
|
2025-06-26 23:34:25 +08:00
|
|
|
|
}
|
|
|
|
|
```
|
2025-06-27 18:34:16 +08:00
|
|
|
|
|
|
|
|
|
#### CollisionPreprocessGPU()
|
|
|
|
|
GPU生成碰撞 的预处理阶段。
|
|
|
|
|
```c++
|
|
|
|
|
bool AShaderWorldActor::CollisionPreprocessGPU()
|
|
|
|
|
{
|
|
|
|
|
SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionPreprocessGPU()", FColor::Magenta);
|
|
|
|
|
SW_FCT_CYCLE()
|
|
|
|
|
|
|
|
|
|
for (int32 CollID = CollisionReadToProcess.Num() - 1; CollID >= 0; CollID--)
|
|
|
|
|
{
|
|
|
|
|
const FName& ElID = CollisionReadToProcess[CollID];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!CollisionMesh.Find(ElID))
|
|
|
|
|
{
|
|
|
|
|
CollisionWorkQueue.Empty();
|
|
|
|
|
CollisionReadToProcess.RemoveAt(CollID);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//判断FCollisionMeshElement是否有效,以及是否将碰撞数据回读完成,如果完成,则将数据添加到碰撞处理队列CollisionWorkQueue,并且从碰撞回读队列CollisionReadToProcess
|
|
|
|
|
FCollisionMeshElement& Mesh = *CollisionMesh.Find(ElID);
|
|
|
|
|
if ((*Mesh.ReadBackCompletion.Get()))
|
|
|
|
|
{
|
|
|
|
|
ensure(Mesh.Mesh);
|
|
|
|
|
|
|
|
|
|
if(FGeoCProcMeshSection* Section = Mesh.Mesh->GetProcMeshSection(0))
|
|
|
|
|
{
|
|
|
|
|
if(CollisionMesh.Contains(CollisionBufferHolder))
|
|
|
|
|
{
|
|
|
|
|
CollisionWorkQueue.Emplace(
|
|
|
|
|
ElID
|
|
|
|
|
, Mesh.HeightData
|
|
|
|
|
, (*CollisionMesh.Find(CollisionBufferHolder)).Mesh->VerticesTemplate
|
|
|
|
|
, (*CollisionMesh.Find(CollisionBufferHolder)).Mesh->TrianglesTemplate);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SW_LOG("!CollisionMesh.Contains(CollisionBufferHolder)")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
SW_LOG("CollisionPreprocessGPU :: Mesh.Mesh->GetProcMeshSection(0) = nullptr")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CollisionReadToProcess.RemoveAt(CollID);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!CollisionReadToProcess.IsEmpty())
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
CollisionReadToProcess.Empty();
|
|
|
|
|
|
|
|
|
|
if (CollisionWorkQueue.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
(*bProcessingGroundCollision.Get()) = true;
|
|
|
|
|
|
|
|
|
|
AShaderWorldActor* SWContext = this;
|
|
|
|
|
|
|
|
|
|
Async(EAsyncExecution::TaskGraph, [Completion = bProcessingGroundCollision, RenderAPI = RendererAPI, VerticesPerPatch = CollisionVerticesPerPatch, Work = CollisionWorkQueue]
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
ParallelFor(Work.Num(), [&](int32 WorkIndex)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
const FCollisionProcessingWork& WorkEl = Work[WorkIndex];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!WorkEl.Read.IsValid() || !WorkEl.SourceB.IsValid() || !WorkEl.DestB.IsValid())
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
const int NumOfVertex = WorkEl.SourceB->Positions.Num();
|
|
|
|
|
|
|
|
|
|
WorkEl.DestB->Positions.SetNum(NumOfVertex);
|
|
|
|
|
WorkEl.DestB->Positions3f.SetNum(NumOfVertex);
|
|
|
|
|
WorkEl.DestB->MaterialIndices.SetNum(NumOfVertex);
|
|
|
|
|
WorkEl.DestB->Bound = FBox(EForceInit::ForceInit);
|
|
|
|
|
|
|
|
|
|
FVector LocationfVertice_WS(0);
|
|
|
|
|
uint16 MaterialIndice = 0;
|
|
|
|
|
|
|
|
|
|
uint8* ReadData8 = (uint8*)WorkEl.Read->ReadData.GetData();
|
|
|
|
|
|
|
|
|
|
TSet<int32>& TrianglesAffectedByHoles = WorkEl.DestB->TrianglesAffectedByHoles;
|
|
|
|
|
TrianglesAffectedByHoles.Empty();
|
|
|
|
|
|
|
|
|
|
//#TODO ISPC slower ?
|
|
|
|
|
#if 0 // INTEL_ISPC
|
|
|
|
|
if (RenderAPI == EGeoRenderingAPI::OpenGL)
|
|
|
|
|
{
|
|
|
|
|
ispc::ShaderWorld_HeightFromGPUReadOpenGL(NumOfVertex, VerticesPerPatch, ReadData8,(ispc::FVector*)WorkEl.SourceB->Positions.GetData(), (ispc::FVector*)WorkEl.DestB->Positions.GetData(), (ispc::FVector3f*)WorkEl.DestB->Positions3f.GetData(), WorkEl.DestB->MaterialIndices.GetData());
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
ispc::ShaderWorld_HeightFromGPURead(NumOfVertex, VerticesPerPatch, ReadData8, (ispc::FVector3f*)WorkEl.SourceB->Positions3f.GetData(), (ispc::FVector*)WorkEl.DestB->Positions.GetData(), (ispc::FVector3f*)WorkEl.DestB->Positions3f.GetData(), WorkEl.DestB->MaterialIndices.GetData());
|
|
|
|
|
}
|
|
|
|
|
#else
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int32 k = 0; k < NumOfVertex; k++)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
if (RenderAPI == EGeoRenderingAPI::OpenGL)
|
|
|
|
|
{
|
|
|
|
|
const int index = k % VerticesPerPatch + (VerticesPerPatch - 1 - (k / VerticesPerPatch)) * VerticesPerPatch;
|
|
|
|
|
|
|
|
|
|
LocationfVertice_WS = FVector(WorkEl.SourceB->Positions[k].X, WorkEl.SourceB->Positions[k].Y, GetHeightFromGPURead(&ReadData8[4 * index], MaterialIndice));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
LocationfVertice_WS = FVector(WorkEl.SourceB->Positions[k].X, WorkEl.SourceB->Positions[k].Y, GetHeightFromGPURead(&ReadData8[4 * k], MaterialIndice));
|
|
|
|
|
|
|
|
|
|
WorkEl.DestB->Positions[k] = LocationfVertice_WS;
|
|
|
|
|
WorkEl.DestB->Positions3f[k] = FVector3f(LocationfVertice_WS);
|
|
|
|
|
WorkEl.DestB->MaterialIndices[k] = MaterialIndice;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* Height below -7km means terrain hole
|
|
|
|
|
*/
|
|
|
|
|
if(WorkEl.DestB->Positions[k].Z < -700000.0)
|
|
|
|
|
{
|
|
|
|
|
/*
|
|
|
|
|
* Find triangles including this vertex and add them to removed triangles
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
if(WorkEl.SourceB->PositionToTriangle.Contains(k))
|
|
|
|
|
{
|
|
|
|
|
TrianglesAffectedByHoles.Append(*WorkEl.SourceB->PositionToTriangle.Find(k));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WorkEl.DestB->Bound += WorkEl.DestB->Positions[k];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
/*
|
|
|
|
|
* If the terrain has holes, create a custom index buffer with the related triangles removed
|
|
|
|
|
*/
|
|
|
|
|
if(TrianglesAffectedByHoles.Num() > 0)
|
|
|
|
|
{
|
|
|
|
|
WorkEl.DestB->FilteredTriangles = MakeShared<FSWShareableIndexBuffer, ESPMode::ThreadSafe>();
|
|
|
|
|
|
|
|
|
|
for(int32 Triangle = 0; Triangle < WorkEl.TriangleTemplate->Indices.Num()/3; Triangle++)
|
|
|
|
|
{
|
|
|
|
|
if(!TrianglesAffectedByHoles.Contains(Triangle))
|
|
|
|
|
{
|
|
|
|
|
WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3]);
|
|
|
|
|
WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3 + 1]);
|
|
|
|
|
WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3 + 2]);
|
|
|
|
|
|
|
|
|
|
WorkEl.DestB->FilteredTriangles->Triangles_CollisionOnly.Add(WorkEl.TriangleTemplate->Triangles_CollisionOnly[Triangle]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
WorkEl.DestB->FilteredTriangles.Reset();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
//WorkEl.DestB->Bound = FBox(WorkEl.DestB->Positions);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if (Completion.IsValid())
|
|
|
|
|
Completion->AtomicSet(false);
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2025-06-25 23:32:48 +08:00
|
|
|
|
### SpawnablesManagement()
|
|
|
|
|
|
|
|
|
|
### TerrainAndSpawnablesManagement()
|
2025-06-25 16:20:36 +08:00
|
|
|
|
|
|
|
|
|
## PreEditChange() / PostEditChangeProperty()
|
2025-06-25 14:59:46 +08:00
|
|
|
|
**PreEditChange()** 主要针对以下两个变量的设置:
|
|
|
|
|
- PreventReRegistration:防止重新注册。
|
|
|
|
|
- RuntimePropertyEditing:实时属性修改。
|
|
|
|
|
|
|
|
|
|
**PostEditChangeProperty()**
|
|
|
|
|
里面比较关键的逻辑有:
|
|
|
|
|
- EditRebuildVegetation.AtomicSet(true);
|
|
|
|
|
- 在Setup()中清空Bioms数组。
|
|
|
|
|
- EditRebuild.AtomicSet(true);
|
|
|
|
|
- 在SetupCollisions()设置rebuild = true。
|
|
|
|
|
|
|
|
|
|
如果RuntimePropertyEditing为true,在最后会将RuntimePropertyEditing设置为false。PreventReRegistration也会设置为false。
|
|
|
|
|
|
2025-06-25 16:20:36 +08:00
|
|
|
|
## Rebuild变量
|
2025-06-25 14:59:46 +08:00
|
|
|
|
主要出现在:
|
|
|
|
|
- Setup():调用RebuildCleanup()清空所有数据。
|
|
|
|
|
- SetupCollision(): RedbuildCollisionContext = true。
|
|
|
|
|
- ProcessSpawnablePending():如果处于重建状态就直接返回。
|
|
|
|
|
- InitiateClipMapMeshes():如果处于重建状态就直接返回。
|
|
|
|
|
- FinalizeAsyncWork():如果处于重建状态就直接返回。
|
|
|
|
|
|
2025-06-09 16:42:14 +08:00
|
|
|
|
## DrawMaterialToRenderTarget
|
|
|
|
|
USWorldSubsystem::DrawMaterialToRenderTarget
|
|
|
|
|
=>
|
|
|
|
|
SWShaderToolBox::DrawMaterial
|
|
|
|
|
=>
|
|
|
|
|
DrawMaterial_CS_RT
|
|
|
|
|
|
|
|
|
|
调用路径:
|
2025-06-13 11:57:17 +08:00
|
|
|
|
- AShaderWorldActor::[[#RetrieveHeightAt]](好像没有引用):检索高度
|
2025-06-09 16:42:14 +08:00
|
|
|
|
- AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap
|
|
|
|
|
- AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick()
|
|
|
|
|
- AShaderWorldActor::ComputeHeightMapForClipMap
|
|
|
|
|
- AShaderWorldActor::UpdateClipMap() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick()
|
|
|
|
|
- AShaderWorldActor::ComputeDataLayersForClipMap
|
|
|
|
|
- AShaderWorldActor::UpdateClipMap() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick()
|
|
|
|
|
- AShaderWorldActor::UpdateCollisionMeshData:更新碰撞模型数据。
|
|
|
|
|
- AShaderWorldActor::CollisionGPU() <- AShaderWorldActor::CollisionManagement() <- AShaderWorldActor::Tick()
|
|
|
|
|
- FSpawnableMesh::UpdateSpawnableData
|
2025-06-09 17:32:09 +08:00
|
|
|
|
- AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## Cache机制
|
|
|
|
|
AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick()
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 其他Bug
|
2025-06-10 18:47:39 +08:00
|
|
|
|
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
|
|
|
|
## SetTextureParameterValue相关逻辑排查
|
|
|
|
|
- AShaderWorldActor中的SetTextureParameterValue
|
|
|
|
|
- ~~ExportCacheInBounds~~
|
|
|
|
|
- ~~AssignHeightMapToDynamicMaterial~~
|
|
|
|
|
- UpdateStaticDataFor
|
|
|
|
|
- ComputeHeight_Segmented_MapForClipMap:似乎会设置
|
2025-06-10 18:47:39 +08:00
|
|
|
|
- ~~UpdateCollisionMeshData~~
|
2025-06-10 17:02:58 +08:00
|
|
|
|
- [x] [[#InitializeReadBackDependencies]]
|
|
|
|
|
- [x] InitiateMaterials
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
2025-06-10 18:47:39 +08:00
|
|
|
|
### UpdateStaticDataFor
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
2025-06-10 17:02:58 +08:00
|
|
|
|
|
2025-06-10 14:59:07 +08:00
|
|
|
|
### ComputeHeight_Segmented_MapForClipMap
|
2025-06-10 20:14:47 +08:00
|
|
|
|
- 作用:
|
|
|
|
|
- 调用顺序:AShaderWorldActor::Tick() -> AShaderWorldActor::TerrainAndSpawnablesManagement() -> AShaderWorldActor::ProcessSegmentedComputation() -> ComputeHeight_Segmented_MapForClipMap
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
2025-06-10 20:14:47 +08:00
|
|
|
|
|
|
|
|
|
>// 1) Intersect clipmap with grid quad
|
|
|
|
|
// 2) Gather non computed quads
|
|
|
|
|
// 3) Allocated Compute element to missing Quad
|
|
|
|
|
// 4) Update the indirection data to the new elements
|
|
|
|
|
// 5) Update the Clipmap Heightmap with the grid data
|
2025-06-10 18:47:39 +08:00
|
|
|
|
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
2025-06-10 18:47:39 +08:00
|
|
|
|
### UpdateCollisionMeshData
|
|
|
|
|
- 作用:
|
|
|
|
|
1. 判断DynCollisionMat是否有效,无效就使用`Generator`(高度数据生成材质)来创建。
|
|
|
|
|
2. 设置材质参数NoMargin、TexelPerSide、PatchFullSize、MeshScale。
|
|
|
|
|
3. 设置随机种子相关的材质参数。
|
|
|
|
|
4. 设置材质参数PatchLocation。
|
|
|
|
|
5. 生成碰撞数据到`CollisionRT`。
|
|
|
|
|
6. 笔刷功能逻辑:ApplyBrushStackToHeightMap()。
|
|
|
|
|
7. ExportPhysicalMaterialID逻辑。
|
|
|
|
|
8. GPU碰撞数据回读:ShaderWorld::AsyncReadPixelsFromRT()。
|
|
|
|
|
1. ShaderWorld::GSWReadbackManager.AddPendingReadBack(),将回读Task增加`TArray<FReadBackTask> PendingReads;`。
|
|
|
|
|
2. 之后会在USWorldSubsystem::Tick()中调用ShaderWorld::GSWReadbackManager.TickReadBack(),不断检查是否可回读,并进行最终回读。
|
|
|
|
|
- 调用顺序:Tick() -> CollisionManagement() -> CollisionGPU() -> UpdateCollisionMeshData()
|
|
|
|
|
|
|
|
|
|
```c++
|
|
|
|
|
namespace ShaderWorld
|
|
|
|
|
{
|
|
|
|
|
FORCEINLINE void AsyncReadPixelsFromRT(UShaderWorldRT2D* InRT, TSharedPtr<FSWColorRead, ESPMode::ThreadSafe> Destination, TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe> Completion)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)(
|
|
|
|
|
[InRT, HeightData = Destination, Completion = Completion](FRHICommandListImmediate& RHICmdList)
|
|
|
|
|
{
|
|
|
|
|
check(IsInRenderingThread());
|
|
|
|
|
|
|
|
|
|
if (HeightData.IsValid() && InRT->GetResource())
|
|
|
|
|
{
|
|
|
|
|
FRDGBuilder GraphBuilder(RHICmdList);
|
|
|
|
|
TSharedPtr<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(TEXT("SWGPUTextureReadback"));
|
|
|
|
|
FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture"));
|
|
|
|
|
AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture);
|
|
|
|
|
GraphBuilder.Execute();
|
|
|
|
|
|
|
|
|
|
ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
```
|
2025-06-10 14:59:07 +08:00
|
|
|
|
|
2025-06-10 17:02:58 +08:00
|
|
|
|
|
|
|
|
|
### InitializeReadBackDependencies
|
2025-06-10 18:47:39 +08:00
|
|
|
|
- 作用:初始化几个GPU数据回读用的RT。
|
|
|
|
|
- 调用顺序:BeginPlay() -> InitiateWorld() -> InitializeReadBackDependencies()
|
2025-06-10 17:02:58 +08:00
|
|
|
|
|
|
|
|
|
1. 初始化3个RT:ReadRequestLocation、ReadRequestLocationHeightmap、GeneratorDynamicForReadBack。
|
|
|
|
|
2. 会设置`TObjectPtr < UMaterialInstanceDynamic> GeneratorDynamicForReadBack`各种变量
|
|
|
|
|
```c++
|
|
|
|
|
GeneratorDynamicForReadBack->SetScalarParameterValue("HeightReadBack", 1.f);
|
|
|
|
|
GeneratorDynamicForReadBack->SetTextureParameterValue("SpecificLocationsRT", ReadRequestLocation);
|
|
|
|
|
GeneratorDynamicForReadBack->SetScalarParameterValue("NoMargin", 0.f);
|
|
|
|
|
GeneratorDynamicForReadBack->SetScalarParameterValue("N", N);
|
|
|
|
|
GeneratorDynamicForReadBack->SetScalarParameterValue("NormalMapSelect", 0.f);
|
|
|
|
|
GeneratorDynamicForReadBack->SetScalarParameterValue("HeightMapToggle", 1.f);
|
|
|
|
|
```
|
|
|
|
|
3. 设置随机种子相关Shader Parameter。
|
2025-06-10 14:59:07 +08:00
|
|
|
|
### InitiateMaterials
|
2025-06-10 17:02:58 +08:00
|
|
|
|
作用:初始化`TArray<FClipMapMeshElement> Meshes;`的Material、`Producers`
|
|
|
|
|
调用顺序:BeginPlay() -> InitiateWorld() -> InitiateMaterials()
|
|
|
|
|
|
2025-06-10 21:54:10 +08:00
|
|
|
|
经过断点调试,会设置WorldSettings里的Material(地形Material)的HeightMap与NormalMap。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
SWorldSubsystem->DrawMaterialToRenderTarget
|
|
|
|
|
|
|
|
|
|
|
2025-06-11 12:17:42 +08:00
|
|
|
|
# Rebuild逻辑
|
2025-06-11 12:49:33 +08:00
|
|
|
|
## 重要函数
|
|
|
|
|
- AShaderWorldActor::BeginPlay()
|
|
|
|
|
- AShaderWorldActor::Setup()(<- TerrainAndSpawnablesManagement(float& DeltaT) <- Tick())
|
2025-06-11 13:25:51 +08:00
|
|
|
|
|
|
|
|
|
## Rebuild逻辑顺序
|
|
|
|
|
1. AShaderWorldActor::BeginPlay()
|
|
|
|
|
1.
|
|
|
|
|
|
2025-06-12 12:22:31 +08:00
|
|
|
|
|
|
|
|
|
# Debug
|
2025-06-12 13:45:32 +08:00
|
|
|
|
1. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次
|
|
|
|
|
2. UpdateCollisionMeshData
|
|
|
|
|
3. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次
|
2025-06-13 11:57:17 +08:00
|
|
|
|
4. [[#RetrieveHeightAt]]
|
2025-06-12 13:45:32 +08:00
|
|
|
|
5. UpdateCollisionMeshData 3次
|
2025-06-13 11:57:17 +08:00
|
|
|
|
6. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次
|
|
|
|
|
|
|
|
|
|
# RetrieveHeightAt
|
|
|
|
|
可能存在bug待排查的:
|
|
|
|
|
- ShaderWorldSubsystem->LoadSampleLocationsInRT()
|
|
|
|
|
- ShaderWorldSubsystem->DrawMaterialToRenderTarget()
|
|
|
|
|
|
|
|
|
|
## 相关变量
|
|
|
|
|
- FThreadSafeBool
|
|
|
|
|
- bProcessingHeightRetrieval
|
|
|
|
|
- bProcessingHeightRetrievalRT
|
|
|
|
|
- MID
|
|
|
|
|
- GeneratorDynamicForReadBack
|
|
|
|
|
- UShaderWorldRT2D(`UTextureRenderTarget2D`)
|
|
|
|
|
- ReadRequestLocation:RTF_RG32f,初始化于`InitializeReadBackDependencies() <- InitiateWorld()`
|
|
|
|
|
- ReadRequestLocationHeightmap:RTF_RGBA8,初始化于`InitializeReadBackDependencies() <- InitiateWorld()`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 代码
|
|
|
|
|
```c++
|
|
|
|
|
bool AShaderWorldActor::RetrieveHeightAt(const TArray<FVector>& Origin, const FSWHeightRetrievalDelegate& Callback)
|
|
|
|
|
{
|
|
|
|
|
if (!GeneratorDynamicForReadBack || !SWorldSubsystem)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
if (!bProcessingHeightRetrieval.IsValid())
|
|
|
|
|
{
|
|
|
|
|
bProcessingHeightRetrieval = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
bProcessingHeightRetrieval->AtomicSet(false);
|
|
|
|
|
}
|
|
|
|
|
if (!bProcessingHeightRetrievalRT.IsValid())
|
|
|
|
|
{
|
|
|
|
|
bProcessingHeightRetrievalRT = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
|
|
|
|
|
bProcessingHeightRetrievalRT->AtomicSet(false);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (!(*bProcessingHeightRetrieval.Get()) && ReadRequestLocation && ReadRequestLocationHeightmap && GeneratorDynamicForReadBack)
|
|
|
|
|
{
|
|
|
|
|
bProcessingHeightRetrieval->AtomicSet(true);
|
|
|
|
|
bProcessingHeightRetrievalRT->AtomicSet(false);
|
|
|
|
|
HeightRetrieveDelegate = Callback;
|
|
|
|
|
|
|
|
|
|
//初始化采样点数组结构体FSWShareableSamplePoints
|
|
|
|
|
PointsPendingReadBacks = MakeShared<FSWShareableSamplePoints, ESPMode::ThreadSafe>();
|
|
|
|
|
TSharedPtr<FSWShareableSamplePoints>& Samples = PointsPendingReadBacks;
|
|
|
|
|
|
|
|
|
|
FBox BoundingBoxRead(Origin);
|
|
|
|
|
|
|
|
|
|
Samples->PositionsXY.SetNum(25 * 2);
|
|
|
|
|
for (int i = 0; i < 25; i++)
|
|
|
|
|
{
|
|
|
|
|
if (i < Origin.Num())
|
|
|
|
|
{
|
|
|
|
|
Samples->PositionsXY[i * 2] = Origin[i].X;
|
|
|
|
|
Samples->PositionsXY[i * 2 + 1] = Origin[i].Y;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Samples->PositionsXY[i * 2] = 0.f;
|
|
|
|
|
Samples->PositionsXY[i * 2 + 1] = 0.f;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (USWorldSubsystem* ShaderWorldSubsystem = SWorldSubsystem)
|
|
|
|
|
{
|
|
|
|
|
//从渲染线程
|
|
|
|
|
ShaderWorldSubsystem->LoadSampleLocationsInRT(ReadRequestLocation, Samples);
|
|
|
|
|
|
|
|
|
|
#if SW_COMPUTE_GENERATION
|
|
|
|
|
ShaderWorldSubsystem->DrawMaterialToRenderTarget(
|
|
|
|
|
{ false,
|
|
|
|
|
false,
|
|
|
|
|
GetWorld()->Scene,
|
|
|
|
|
(float)GetWorld()->TimeSeconds,
|
|
|
|
|
false,
|
|
|
|
|
true,
|
|
|
|
|
ReadRequestLocationHeightmap->SizeX,
|
|
|
|
|
10,
|
|
|
|
|
FVector(0.f),
|
|
|
|
|
true,
|
|
|
|
|
ReadRequestLocation,
|
|
|
|
|
GeneratorDynamicForReadBack,
|
|
|
|
|
ReadRequestLocationHeightmap
|
|
|
|
|
});
|
|
|
|
|
#else
|
|
|
|
|
UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, ReadRequestLocationHeightmap, GeneratorDynamicForReadBack);
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
int32 Size_RT_Readback = ReadRequestLocationHeightmap.Get()->SizeX;
|
|
|
|
|
|
|
|
|
|
FVector Barycentre = BoundingBoxRead.GetCenter();
|
|
|
|
|
FVector Extent = BoundingBoxRead.GetExtent();
|
|
|
|
|
float gridspacing = Extent.X * 2.0 / (Size_RT_Readback - 1);
|
|
|
|
|
|
|
|
|
|
if (IsValid(BrushManager))
|
|
|
|
|
BrushManager->ApplyBrushStackToHeightMap(this, 0, ReadRequestLocationHeightmap.Get(), Barycentre, gridspacing, Size_RT_Readback, true, true, ReadRequestLocation.Get());
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ReadBackHeightData = MakeShared<FSWColorRead, ESPMode::ThreadSafe>();
|
|
|
|
|
ReadBackHeightData->ReadData.SetNum(25);
|
|
|
|
|
|
|
|
|
|
ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)(
|
|
|
|
|
[InRT = ReadRequestLocationHeightmap, HeightData = ReadBackHeightData, Completion = bProcessingHeightRetrievalRT](FRHICommandListImmediate& RHICmdList)
|
|
|
|
|
{
|
|
|
|
|
check(IsInRenderingThread());
|
|
|
|
|
|
|
|
|
|
if (HeightData.IsValid() && InRT->GetResource())
|
|
|
|
|
{
|
|
|
|
|
FRDGBuilder GraphBuilder(RHICmdList);
|
|
|
|
|
|
|
|
|
|
TSharedPtr<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(TEXT("SWGPUTextureReadback"));
|
|
|
|
|
|
|
|
|
|
FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture"));
|
|
|
|
|
|
|
|
|
|
AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture);
|
|
|
|
|
|
|
|
|
|
GraphBuilder.Execute();
|
|
|
|
|
|
|
|
|
|
ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
HeightReadBackFence.BeginFence(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### RequestReadBackLoad
|
|
|
|
|
```c++
|
|
|
|
|
bool USWorldSubsystem::LoadSampleLocationsInRT(UShaderWorldRT2D* LocationsRequestedRT,
|
|
|
|
|
TSharedPtr<FSWShareableSamplePoints>& Samples)
|
|
|
|
|
{
|
|
|
|
|
if (!RenderThreadResponded)
|
|
|
|
|
return false;
|
|
|
|
|
|
|
|
|
|
const SWSampleRequestComputeData ReadBackData(LocationsRequestedRT, Samples);
|
|
|
|
|
SWToolBox->RequestReadBackLoad(ReadBackData);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
# SWShaderToolBox
|
|
|
|
|
## RequestReadBackLoad
|
|
|
|
|
```c++
|
|
|
|
|
void SWShaderToolBox::RequestReadBackLoad(const SWSampleRequestComputeData& Data) const
|
|
|
|
|
{
|
|
|
|
|
if (Data.CPU)
|
|
|
|
|
return CPUTools.RequestReadBackLoad(Data);
|
|
|
|
|
|
|
|
|
|
ENQUEUE_RENDER_COMMAND(ShaderTools_copy_rt)
|
|
|
|
|
([this, Data](FRHICommandListImmediate& RHICmdList)
|
|
|
|
|
{
|
|
|
|
|
if (Data.SamplesXY && Data.SamplesXY->GetResource())
|
|
|
|
|
RequestReadBackLoad_RT(RHICmdList,Data);
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SWShaderToolBox::RequestReadBackLoad_RT(FRHICommandListImmediate& RHICmdList, const SWSampleRequestComputeData& Data) const
|
|
|
|
|
{
|
|
|
|
|
if (!(Data.SamplesXY && Data.SamplesXY->GetResource()))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
FRDGBuilder GraphBuilder(RHICmdList);
|
|
|
|
|
|
|
|
|
|
{
|
|
|
|
|
RDG_EVENT_SCOPE(GraphBuilder, "ShaderWorld PositionReadBack");
|
|
|
|
|
RDG_GPU_STAT_SCOPE(GraphBuilder, ShaderWorldReadBack);
|
|
|
|
|
|
|
|
|
|
FIntVector GroupCount;
|
|
|
|
|
GroupCount.X = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeX(), (float)SW_LoadReadBackLocations_GroupSizeX);
|
|
|
|
|
GroupCount.Y = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeY(), (float)SW_LoadReadBackLocations_GroupSizeY);
|
|
|
|
|
GroupCount.Z = 1;
|
|
|
|
|
|
|
|
|
|
const FUnorderedAccessViewRHIRef RT_UAV = GraphBuilder.RHICmdList.CreateUnorderedAccessView(Data.SamplesXY->GetResource()->TextureRHI);
|
|
|
|
|
|
|
|
|
|
const FRDGBufferRef LocationRequest = CreateUploadBuffer(
|
|
|
|
|
GraphBuilder,
|
|
|
|
|
TEXT("SWLoadSampleLocations"),
|
|
|
|
|
sizeof(float),
|
|
|
|
|
Data.SamplesSource->PositionsXY.Num(),
|
|
|
|
|
Data.SamplesSource->PositionsXY.GetData(),
|
|
|
|
|
Data.SamplesSource->PositionsXY.Num() * Data.SamplesSource->PositionsXY.GetTypeSize()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const FRDGBufferSRVRef LocationRequestSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(LocationRequest, PF_R32_FLOAT));;
|
|
|
|
|
|
|
|
|
|
FLoadReadBackLocations_CS::FPermutationDomain PermutationVector;
|
|
|
|
|
TShaderMapRef<FLoadReadBackLocations_CS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);
|
|
|
|
|
|
|
|
|
|
FLoadReadBackLocations_CS::FParameters* PassParameters = GraphBuilder.AllocParameters<FLoadReadBackLocations_CS::FParameters>();
|
|
|
|
|
PassParameters->SampleDim = Data.SamplesXY->GetResource()->GetSizeX();
|
|
|
|
|
PassParameters->DestLocationsTex = RT_UAV;
|
|
|
|
|
PassParameters->SourceLocationBuffer = LocationRequestSRV;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
GraphBuilder.AddPass(
|
|
|
|
|
RDG_EVENT_NAME("ShaderWorld LoadReadBacklocations_CS"),
|
|
|
|
|
PassParameters,
|
|
|
|
|
ERDGPassFlags::Compute |
|
|
|
|
|
ERDGPassFlags::NeverCull,
|
|
|
|
|
[PassParameters, ComputeShader, GroupCount](FRHICommandList& RHICmdList)
|
|
|
|
|
{
|
|
|
|
|
FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, GroupCount);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GraphBuilder.Execute();
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
FLoadReadBackLocations_CS
|
|
|
|
|
```c++
|
|
|
|
|
uint SampleDim;
|
|
|
|
|
RWTexture2D<float2> DestLocationsTex;
|
|
|
|
|
Buffer<float> SourceLocationBuffer;
|
|
|
|
|
|
|
|
|
|
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, THREADGROUP_SIZEZ)]
|
|
|
|
|
void SampleLocationLoaderCS(uint3 ThreadId : SV_DispatchThreadID)
|
|
|
|
|
{
|
|
|
|
|
if (any(ThreadId.xy >= SampleDim.xx))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
uint IndexPixel = (ThreadId.x + ThreadId.y * SampleDim) * 2;
|
|
|
|
|
|
|
|
|
|
DestLocationsTex[ThreadId.xy] = float2(SourceLocationBuffer[IndexPixel],SourceLocationBuffer[IndexPixel + 1]);
|
|
|
|
|
}
|
2025-06-13 17:47:07 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
2025-06-13 18:51:25 +08:00
|
|
|
|
# FSWDrawMaterial_SL_CS
|
|
|
|
|
IMPLEMENT_MATERIAL_SHADER_TYPE(template<>, FSWDrawMaterial_SL_CS, TEXT("/ShaderWorld/ShaderWorldUtilities.usf"), TEXT("DrawMaterialCS"), SF_Compute);
|
|
|
|
|
|
|
|
|
|
FMeshMaterialShader::ModifyCompilationEnvironment(Parameters, OutEnvironment);
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("SW_DRAW_WITH_HEIGHTNORMAL"), 0);
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("SW_DRAWMATERIAL"), 1);
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("SW_SPECIFIC_LOCATION_DRAW"), 1);
|
|
|
|
|
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEX"), bIsMobileRenderer ? SW_MobileLowSharedMemory_GroupSizeX : FComputeShaderUtils::kGolden2DGroupSize);
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEY"), bIsMobileRenderer ? SW_MobileLowSharedMemory_GroupSizeY : FComputeShaderUtils::kGolden2DGroupSize);
|
|
|
|
|
OutEnvironment.SetDefine(TEXT("THREADGROUP_SIZEZ"), 1);
|
2025-06-13 17:47:07 +08:00
|
|
|
|
|
|
|
|
|
#
|
|
|
|
|
- M_Blank_HeightLayer
|
|
|
|
|
- MFC_WorldPositionNormal_Layer
|
|
|
|
|
- M_Blank_HeightLayer(未连接)
|
|
|
|
|
- MFC_Position_ForCache
|
|
|
|
|
- 其他
|
|
|
|
|
- MF_CacheRead_Reference_Tessellation
|
|
|
|
|
- MFC_CacheRead:没有其他引用
|
|
|
|
|
- MFC_CacheRead_Tessellationt
|
|
|
|
|
- MFC_CacheReadNoVertexManipulation
|
2025-06-13 18:51:25 +08:00
|
|
|
|
- MFC_ExternalCacheRead
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2025-06-17 17:42:27 +08:00
|
|
|
|
# 植被生成逻辑
|
2025-06-25 12:29:10 +08:00
|
|
|
|
Tick() => TerrainAndSpawnablesManagement() => UpdateSpawnables() => UpdateSpawnable()
|