--- title: Untitled date: 2025-06-03 10:19:25 excerpt: tags: rating: ⭐ --- # 前言 1. ShaderWorldPCGInterop: 2. ShaderWorld: 3. ShaderWorldCore: # ShaderWorld - Class - Actor - ShaderWorldActor.h:[[#AShaderWorldActor]] - SWorld.h:[[#ASWorld]] # USWorldSubsystem 主要管理: - TArray SW_Contexts - 渲染相关变量:RT_Ready、RenderThreadPoked、RenderThreadResponded处理。 # ASWorld # AShaderWorldActor ## 大致启动流程 大致启动流程: - BeginPlay() 1. 设置SWCamera Location。 2. 清空所有数据。 3. InitiateWorld() 1. 更新相关变量:GenerateCollision_last、VerticalRangeMeters_last、WorldHasBounds_OnRebuild。**Segmented_Initialized** 、**bExportPhysicalMaterialID_cached** 2. 设置高度生成用材质变量 Generator;bool bHadGeneratorAtRebuildTime = IsValid(Generator); 3. LOD_Num与WorldDimensionMeters计算。 4. 重新生成若干数组: 1. LODs_DimensionsMeters 2. ClipMapToUpdateAndMove 3. ClipMapToUpdate 4. NeedSegmentedUpdate 5. AdaptiveTopology 5. 相关线程安全变量: 1. Shareable_ID_FarLOD 2. UpdateHOverTime 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 - Tick() - 初始化 1. 取得USWorldSubsystem指针,并且进行初始化。 - ReadbacksManagement() - CollisionManagement() - SpawnablesManagement() - TerrainAndSpawnablesManagement() ### ReadbacksManagement() 主要用于回读RetrieveHeightAt()调用后GPU计算出来的Position信息。在**用于计算HeightReadBack的渲染线程、RHI、GPU都执行完成**后,从ReadBackHeightData的像素点(最多25个)中读取Z轴信息,之后和PointsPendingReadBacks中的X、Y轴信息合并成一个Vector3 Position。 - ThreadSafe - bool - **bProcessingHeightRetrieval** - **bProcessingHeightRetrievalRT** - FSWShareableSamplePoints - PointsPendingReadBacks - FSWColorRead - ReadBackHeightData - FRenderCommandFence - HeightReadBackFence ```c++ void AShaderWorldActor::ReadbacksManagement() { if (!bProcessingHeightRetrieval.IsValid()) bProcessingHeightRetrieval = MakeShared(); if (!bProcessingHeightRetrievalRT.IsValid()) bProcessingHeightRetrievalRT = MakeShared(); //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() - ThreadSafe - bool - bProcessingGroundCollision - bPreprocessingCollisionUpdate - EditRebuild:传递Bool值给 rebuild。 - FSWCollisionManagementShareableData - CollisionShareable - FRenderCommandFence - 1 - bool - RedbuildCollisionContext - Array - CollisionReadToProcess: - CollisionWorkQueue:类型为FCollisionProcessingWork,碰撞处理任务队列,将回读的 - FCollisionMeshElement - ReadBackCompletion:碰撞数据GPU回读是否完成。 ```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(); } ``` #### SetupCollisions() 设置相关变量。 ```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(); if (!bPreprocessingCollisionUpdate.IsValid()) bPreprocessingCollisionUpdate = MakeShared(); 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(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; } ``` #### CollisionFinalizeWork() 结束碰撞生成任务,true为完成,false为任务超时未完成。 - TMap CollisionMesh:碰撞Name => 碰撞Mesh Map。 - FCollisionMeshElement:管理单个碰撞数据的结构体。 - UBodySetup:虚幻引擎中一个重要的物理相关类,主要用于定义和存储物体的物理属性和碰撞设置 ```c++ bool AShaderWorldActor::CollisionFinalizeWork() { SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionFinalizeWork()", FColor::Magenta); SW_FCT_CYCLE() if (!CollisionReadToProcess.IsEmpty()) return true; const double GameThreadBudget_ms = SWGameThreadBudgetCollision_ms.GetValueOnGameThread(); double TimeStart = FPlatformTime::Seconds(); //判断处理队列是否为空 if(CollisionWorkQueue.Num()<=0) return true; { FScopeLock CollisionMeshArrayAccess(&CollisionMeshAccessLock); for (int i = CollisionWorkQueue.Num() - 1; i >= 0; i--) { //判断生成时间是否是否超过预设数值,如果超过就直接返回。默认为1 ms。 if ((FPlatformTime::Seconds() - TimeStart) * 1000.0 > GameThreadBudget_ms) return false; //将生成的碰撞数据赋予给对应的碰撞Mesh,最后移除任务队列中的任务。 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; } void UShaderWorldCollisionComponent::UpdateSectionTriMesh(TSharedPtr& Positions) { //当UBodySetup更新队列还有任务时,执行改函数会将FSWShareableVerticePositionBuffer加入到UpdatesReceivedDuringCompute数组中并且退出。 if (AsyncBodySetupQueue.Num() > 0) { #if SWDEBUG SW_LOG("Collision Update received during alreayd occuring computation %s",*GetName()) #endif UpdatesReceivedDuringCompute.Add(Positions); return; } UpdatesReceivedDuringCompute.Empty(); //使用FSWShareableVerticePositionBuffer数据来更新当前UShaderWorldCollisionComponent 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; Async(EAsyncExecution::TaskGraph, [WeakThis = MakeWeakObjectPtr(this), PBuffer = Positions, IndexB = ProcMeshSections[0].IndexBuffer] { TSharedPtr, ESPMode::ThreadSafe> Normals = SWComputeNormalsForPatch(PBuffer, IndexB); AsyncTask(ENamedThreads::GameThread, [WeakThis,PBuffer, Normals]() { if (WeakThis.IsValid()) { if (UShaderWorldCollisionComponent* Comp = Cast(WeakThis.Get())) { //塞入Normal if(IsValid(Comp)) Comp->ReceiveComputedNormals(PBuffer, Normals); } } }); }); } //Materials // Pass new positions to trimesh UpdateCollision();//1. 异步Cook UBodySetup 2. 删除原本的碰撞 3. UseBodySetup->CreatePhysicsMeshesAsync(),使用UBodySetup异步创建新的碰撞网格。 UpdateLocalBounds();//更新UShaderWorldCollisionComponent的LocalBoundingBox 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; //更新SceneProxy FShaderWProceduralMeshSceneProxy的NewPositionBuffer,也就是UpdateSection if(AShaderWorldActor* owner = Cast(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(); } } ``` #### 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& 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(); 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; } ``` ### SpawnablesManagement() ### TerrainAndSpawnablesManagement() ## PreEditChange() / PostEditChangeProperty() **PreEditChange()** 主要针对以下两个变量的设置: - PreventReRegistration:防止重新注册。 - RuntimePropertyEditing:实时属性修改。 **PostEditChangeProperty()** 里面比较关键的逻辑有: - EditRebuildVegetation.AtomicSet(true); - 在Setup()中清空Bioms数组。 - EditRebuild.AtomicSet(true); - 在SetupCollisions()设置rebuild = true。 如果RuntimePropertyEditing为true,在最后会将RuntimePropertyEditing设置为false。PreventReRegistration也会设置为false。 ## Rebuild变量 主要出现在: - Setup():调用RebuildCleanup()清空所有数据。 - SetupCollision(): RedbuildCollisionContext = true。 - ProcessSpawnablePending():如果处于重建状态就直接返回。 - InitiateClipMapMeshes():如果处于重建状态就直接返回。 - FinalizeAsyncWork():如果处于重建状态就直接返回。 ## DrawMaterialToRenderTarget USWorldSubsystem::DrawMaterialToRenderTarget => SWShaderToolBox::DrawMaterial => DrawMaterial_CS_RT 调用路径: - AShaderWorldActor::[[#RetrieveHeightAt]](好像没有引用):检索高度 - 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 - AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() ## Cache机制 AShaderWorldActor::ProcessSegmentedComputation() <- AShaderWorldActor::TerrainAndSpawnablesManagement() <- AShaderWorldActor::Tick() # 其他Bug ## SetTextureParameterValue相关逻辑排查 - AShaderWorldActor中的SetTextureParameterValue - ~~ExportCacheInBounds~~ - ~~AssignHeightMapToDynamicMaterial~~ - UpdateStaticDataFor - ComputeHeight_Segmented_MapForClipMap:似乎会设置 - ~~UpdateCollisionMeshData~~ - [x] [[#InitializeReadBackDependencies]] - [x] InitiateMaterials ### UpdateStaticDataFor ### ComputeHeight_Segmented_MapForClipMap - 作用: - 调用顺序:AShaderWorldActor::Tick() -> AShaderWorldActor::TerrainAndSpawnablesManagement() -> AShaderWorldActor::ProcessSegmentedComputation() -> ComputeHeight_Segmented_MapForClipMap >// 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 ### 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 PendingReads;`。 2. 之后会在USWorldSubsystem::Tick()中调用ShaderWorld::GSWReadbackManager.TickReadBack(),不断检查是否可回读,并进行最终回读。 - 调用顺序:Tick() -> CollisionManagement() -> CollisionGPU() -> UpdateCollisionMeshData() ```c++ namespace ShaderWorld { FORCEINLINE void AsyncReadPixelsFromRT(UShaderWorldRT2D* InRT, TSharedPtr 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 ReadBackStaging = MakeShared(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&>(HeightData), const_cast&>(Completion)); } }); } ``` ### InitializeReadBackDependencies - 作用:初始化几个GPU数据回读用的RT。 - 调用顺序:BeginPlay() -> InitiateWorld() -> InitializeReadBackDependencies() 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。 ### InitiateMaterials 作用:初始化`TArray Meshes;`的Material、`Producers` 调用顺序:BeginPlay() -> InitiateWorld() -> InitiateMaterials() 经过断点调试,会设置WorldSettings里的Material(地形Material)的HeightMap与NormalMap。 # SWorldSubsystem->DrawMaterialToRenderTarget # Rebuild逻辑 ## 重要函数 - AShaderWorldActor::BeginPlay() - AShaderWorldActor::Setup()(<- TerrainAndSpawnablesManagement(float& DeltaT) <- Tick()) ## Rebuild逻辑顺序 1. AShaderWorldActor::BeginPlay() 1. # Debug 1. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次 2. UpdateCollisionMeshData 3. AShaderWorldActor::ComputeHeight_Segmented_MapForClipMap 十多次 4. [[#RetrieveHeightAt]] 5. UpdateCollisionMeshData 3次 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& Origin, const FSWHeightRetrievalDelegate& Callback) { if (!GeneratorDynamicForReadBack || !SWorldSubsystem) return false; if (!bProcessingHeightRetrieval.IsValid()) { bProcessingHeightRetrieval = MakeShared(); bProcessingHeightRetrieval->AtomicSet(false); } if (!bProcessingHeightRetrievalRT.IsValid()) { bProcessingHeightRetrievalRT = MakeShared(); bProcessingHeightRetrievalRT->AtomicSet(false); } if (!(*bProcessingHeightRetrieval.Get()) && ReadRequestLocation && ReadRequestLocationHeightmap && GeneratorDynamicForReadBack) { bProcessingHeightRetrieval->AtomicSet(true); bProcessingHeightRetrievalRT->AtomicSet(false); HeightRetrieveDelegate = Callback; //初始化采样点数组结构体FSWShareableSamplePoints PointsPendingReadBacks = MakeShared(); TSharedPtr& 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(); 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 ReadBackStaging = MakeShared(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&>(HeightData), const_cast&>(Completion)); } }); HeightReadBackFence.BeginFence(true); } return true; } return false; } ``` ### RequestReadBackLoad ```c++ bool USWorldSubsystem::LoadSampleLocationsInRT(UShaderWorldRT2D* LocationsRequestedRT, TSharedPtr& 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 ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector); FLoadReadBackLocations_CS::FParameters* PassParameters = GraphBuilder.AllocParameters(); 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 DestLocationsTex; Buffer 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]); } ``` # 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); # - M_Blank_HeightLayer - MFC_WorldPositionNormal_Layer - M_Blank_HeightLayer(未连接) - MFC_Position_ForCache - 其他 - MF_CacheRead_Reference_Tessellation - MFC_CacheRead:没有其他引用 - MFC_CacheRead_Tessellationt - MFC_CacheReadNoVertexManipulation - MFC_ExternalCacheRead # 植被生成逻辑 Tick() => TerrainAndSpawnablesManagement() => UpdateSpawnables() => UpdateSpawnable()