--- 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回读是否完成。 相关条件 if ((*bPreprocessingCollisionUpdate.Get()) || CollisionShareable->CollisionMeshToUpdate.Num() > 0 || CollisionShareable->CollisionMeshToRenameMoveUpdate.Num() > 0 || CollisionShareable->CollisionMeshToCreate.Num() > 0) return; if (CollisionUpdateTimeAcu <= 1.f / 10.f || CollisionWorkQueue.Num() > 0 || !CollisionProcess.IsFenceComplete()) ```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 // 碰撞生成与Update计时 { CollisionUpdateTimeAcu += DeltaT; //CollisionUpdateTimeAcu如果超过0.1s,或者CollisionWorkQueue的任务都完成了。CollisionProcess已经完成。 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; //创建全线程碰撞生成相关数据结构体CollisionShareable 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() 笔记为3.8.6版本非最新 结束碰撞生成任务,true为完成,false为任务超时未完成。**主要的逻辑是将之前的数据设置为脏,并且更新FShaderWProceduralMeshSceneProxy,5.4 Finaly版本里这里已经移除了碰撞相关逻辑** - 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生成碰撞 的预处理阶段。 将从GPU回读的碰撞顶点Z轴数据***uint8* ReadData8*** 反序列化成double,之后再还原成***FCollisionProcessingWork.DestB***的Positions、Normals、Positions3f、MaterialIndices、Bound。 ```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; //异步并行 反序列化Ground碰撞数据 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; //取得从GPU回读的序列化数据 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 //从对序列化碰撞Z轴数据进行反序列化,还原成double,再之后还原成FCollisionProcessingWork.DestB 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); } ); //执行完成后将Completion(bProcessingGroundCollision)设置为false代表处理完成。 if (Completion.IsValid()) Completion->AtomicSet(false); }); } return true; } ``` #### CollisionGPU() ```c++ void AShaderWorldActor::CollisionGPU() { SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionGPU()", FColor::Magenta); SW_FCT_CYCLE() double CurrentTime = FPlatformTime::Seconds(); if (!(!(*bPreprocessingCollisionUpdate.Get()) && ( CollisionShareable->CollisionMeshToUpdate.Num() > 0 || CollisionShareable->CollisionMeshToRenameMoveUpdate.Num() > 0 || CollisionShareable->CollisionMeshToCreate.Num() > 0))) { /* * No collision Preprocessing running, we can safely access the data in the shared pointer without risking race condition * 确保没有PreprocessingCollision任务在运行 */ if(!(*bPreprocessingCollisionUpdate.Get())) { FScopeLock CollisionMeshArrayAccess(CollisionMeshAccessLock.Get()); TArray NameToRemove; //遍历AvailableCollisionMesh for (const FName& AvailableID : CollisionShareable->AvailableCollisionMesh) { FCollisionMeshElement& Elem = *CollisionMesh.Find(AvailableID); //更新时间戳 if (Elem.SleepTime < 0.0) Elem.SleepTime = CurrentTime; else if ((abs(CurrentTime - Elem.SleepTime) >= 1.0) && (CollisionBufferHolder == "" || AvailableID != CollisionBufferHolder) && (!Elem.Mesh || (Elem.Mesh && !Elem.Mesh->HasAsyncWorkPending())))//如果生成超时,则销毁对应变量 { if (IsValid(Elem.Mesh)) Elem.Mesh->DestroyComponent(); Elem.Mesh = nullptr; DecountRTAllocatedMemory(Elem.CollisionRT) DecountRTAllocatedMemory(Elem.CollisionRT_Duplicate) Elem.CollisionRT = nullptr; Elem.CollisionRT_Duplicate = nullptr; Elem.DynCollisionCompute = nullptr; Elem.LayerComputeForPhysicalMaterial.Empty(); CollisionMesh.Remove(AvailableID); if (UsedCollisionMesh.Contains(AvailableID)) { UsedCollisionMesh.Remove(AvailableID); } CollisionShareable->CollisionMeshData.Remove(AvailableID); CollisionShareable->UsedCollisionMesh.Remove(AvailableID); NameToRemove.Add(AvailableID); } else { } } for (auto& TR : NameToRemove) { CollisionShareable->AvailableCollisionMesh.Remove(TR); } } return; } UWorld* World = GetWorld(); bool RequireRenderFence = false; int32 CollisionDrawCallCount = 0; UsedCollisionMesh = CollisionShareable->UsedCollisionMesh; /* * Async task created proxy CollisionMesh and associated IDs to them, create actual GameThread collision using the provided IDs * 异步任务创建了代理碰撞网格,并将相关 ID 与它们关联起来,使用提供的 ID 在游戏线程中创建实际的碰撞。 */ for (FName& ID : CollisionShareable->CollisionMeshToCreate) { //使用需要创建碰撞的ID(Name)来获取碰撞MeshData. const FSWCollisionMeshElemData& Mesh_Shareable = *CollisionShareable->CollisionMeshData.Find(ID); FCollisionMeshElement& Mesh = GetACollisionMesh(ID);//GetACollisionMesh()取得现有FCollisionMeshElement或者创建新的,代码贼长。 Mesh.Location = Mesh_Shareable.Location; Mesh.MeshLocation = Mesh_Shareable.MeshLocation; Mesh.Mesh->SetWorldTransform(FTransform(Mesh_Shareable.MeshLocation)); Mesh.Mesh->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName); Mesh.Mesh->Mobility = EComponentMobility::Static; } //清空创建列表。 CollisionShareable->CollisionMeshToCreate.Empty(); //处理CollisionMeshToUpdate 碰撞数据更新队列 for (int i = CollisionShareable->CollisionMeshToUpdate.Num() - 1; i >= 0; i--) { FName& CollisionIDToUpdate = CollisionShareable->CollisionMeshToUpdate[i]; FCollisionMeshElement& El = *CollisionMesh.Find(CollisionIDToUpdate); UpdateCollisionMeshData(El);//核心函数 CollisionShareable->CollisionMeshToUpdate.RemoveAt(i); RequireRenderFence = true; CollisionDrawCallCount++; if (CollisionDrawCallCount >= CollisionMaxDrawCallPerFrame) break; } //处理CollisionMeshToRenameMoveUpdate 碰撞数据重命名&移动更新队列 for (int i = CollisionShareable->CollisionMeshToRenameMoveUpdate.Num() - 1; i >= 0; i--) { FName& CollisionIDToUpdate = CollisionShareable->CollisionMeshToRenameMoveUpdate[i]; ensure(CollisionMesh.Find(CollisionIDToUpdate) != nullptr); ensure(CollisionShareable->CollisionMeshData.Find(CollisionIDToUpdate) != nullptr); FCollisionMeshElement& El = *CollisionMesh.Find(CollisionIDToUpdate); const FSWCollisionMeshElemData& El_Shareable = *CollisionShareable->CollisionMeshData.Find(CollisionIDToUpdate); El.Location = El_Shareable.Location; El.MeshLocation = El_Shareable.MeshLocation; El.Mesh->SetLocationOnPhysicCookComplete(El.MeshLocation); UpdateCollisionMeshData(El); CollisionShareable->CollisionMeshToRenameMoveUpdate.RemoveAt(i); RequireRenderFence = true; CollisionDrawCallCount++; if (CollisionDrawCallCount >= CollisionMaxDrawCallPerFrame) break; } //开启CollisionProcess,需要等到执行完之后,才能执行CollisionPreprocessGPU() if (RequireRenderFence) { CollisionProcess.BeginFence(); } } ``` ```c++ FCollisionMeshElement& AShaderWorldActor::GetACollisionMesh(FName Name/*, FGuid AlreadyCreatedID*/) { UWorld* World = GetWorld(); //存在可用CollisionMesh时,使用ID去寻找,如果找到则直接返回。并将AvailableCollisionMesh中的ID 转移到 UsedCollisionMesh if (CollisionShareable.IsValid() && CollisionShareable->AvailableCollisionMesh.Num() > 0 ) { if(FName* FoundName = CollisionShareable->AvailableCollisionMesh.Find(Name)) { if (FCollisionMeshElement* Elem = CollisionMesh.Find(Name)) { UsedCollisionMesh.Add(Name); CollisionShareable->UsedCollisionMesh.Add(Name); CollisionShareable->AvailableCollisionMesh.Remove(Name); Elem->SleepTime = -1.0; return *Elem; } } } FScopeLock CollisionMeshArrayAccess(CollisionMeshAccessLock.Get()); //往CollisionMesh Map添加新元素(FCollisionMeshElement)并设置其属性。 CollisionMesh.Add(Name, {}); FCollisionMeshElement& NewElem = *CollisionMesh.Find(Name); NewElem.ID = Name; uint32 SizeT = (uint32)CollisionVerticesPerPatch; NewElem.CollisionVerticesPerPatch = SizeT; NewElem.HeightData = MakeShared(); NewElem.HeightData->ReadData.SetNum(CollisionVerticesPerPatch * CollisionVerticesPerPatch); NewElem.Mesh = NewObject(this, Name, RF_Transient | RF_TextExportTransient); NewElem.Mesh->SetUsedPhysicalMaterial(RootComp->PhysicalMaterials); NewElem.Mesh->bUseAsyncCooking = true; NewElem.Mesh->SetSWWorldVersion(Shareable_ID); if (RootComp) { NewElem.Mesh->ComponentTags = RootComp->ComponentTags; NewElem.Mesh->BodyInstance.CopyBodyInstancePropertiesFrom(&RootComp->BodyInstance); NewElem.Mesh->ComponentTags = RootComp->ComponentTags; } else { UE_LOG(LogTemp, Warning, TEXT("UShaderWorldCollisionComponent Creation : invalid RootComp")) } NewElem.Mesh->SetCollisionObjectType(CollisionChannel); NewElem.Mesh->SetGenerateOverlapEvents(true); NewElem.Mesh->SetupAttachment(RootComponent); //NewElem.Mesh->SetRelativeLocation(FVector(0.f, 0.f, 0.f)); NewElem.Mesh->bUseComplexAsSimpleCollision = true; /* NewElem.Mesh->SetUsingAbsoluteLocation(true); NewElem.Mesh->SetUsingAbsoluteRotation(true); */ //NewElem.Mesh->bTraceComplexOnMove = false; NewElem.MeshLocation = NewElem.Mesh->GetComponentLocation(); #if WITH_EDITORONLY_DATA NewElem.Mesh->bConsiderForActorPlacementWhenHidden = true; #endif NewElem.Mesh->RegisterComponent(); TSharedPtr Vertices = MakeShared(); TSharedPtr Triangles = MakeShared(); float Spacing = CollisionResolution; if(CollisionBufferHolder.IsValid() && (!CollisionMesh.Find(CollisionBufferHolder) || (CollisionMesh.Find(CollisionBufferHolder) && !CollisionMesh.Find(CollisionBufferHolder)->Mesh))) CollisionBufferHolder = ""; //符合条件则重新创建碰撞GPU计算用RT。 if ( CollisionBufferHolder == "" || (CollisionBufferHolder != "" && CollisionBufferHolder == Name)) { CollisionBufferHolder = Name; SW_RT(NewElem.CollisionRT, World, Name.ToString() + FString::FromInt(GetUniqueID()), SizeT, TF_Nearest, RTF_RGBA8) RTAllocatedMemory(NewElem.CollisionRT) if (bExportPhysicalMaterialID_cached && LayerStoringMaterialID != "") { for (FSWProducers& layer : Producers) { if (layer.LayerName == "Height" || layer.LayerName == "height") { continue; } if (layer.LayerName != "" && layer.MaterialToGenerateLayer) { NewElem.LayerOrder.Add(layer.LayerName); UShaderWorldRT2D* LayerRT = nullptr; SW_RT(LayerRT, World, layer.LayerName + "_PhyMat_" + FString::FromInt(GetUniqueID()), SizeT, layer.LayerFiltering, layer.LayerFormat) RTAllocatedMemory(LayerRT) NewElem.LayerComputeForPhysicalMaterial.Add(layer.LayerName, { LayerRT ,nullptr }); } if (LayerStoringMaterialID == layer.LayerName) break; } } SW_RT(NewElem.CollisionRT_Duplicate, World, "RT_Temp_" + Name.ToString() + FString::FromInt(GetUniqueID()), SizeT, TF_Nearest, RTF_RGBA8) RTAllocatedMemory(NewElem.CollisionRT_Duplicate) TArray UV; USWBlueprintFunctionLibrary::CreateGridMeshWelded(CollisionVerticesPerPatch, CollisionVerticesPerPatch, Triangles, Vertices, UV, Spacing); { const int32 NumTriangles = Triangles->Indices.Num() / 3; Triangles->Triangles_CollisionOnly.Empty(); Triangles->Triangles_CollisionOnly.Reserve(NumTriangles); for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++) { FTriIndices& Triangle = Triangles->Triangles_CollisionOnly.AddDefaulted_GetRef(); Triangle.v0 = Triangles->Indices[(TriIdx * 3) + 0] /* + VertexBase */; Triangle.v1 = Triangles->Indices[(TriIdx * 3) + 1] /* + VertexBase */; Triangle.v2 = Triangles->Indices[(TriIdx * 3) + 2] /* + VertexBase */; } } /* * The data inside the Vertices shared pointer can and will change over time, while VerticesTemplate is immutable, * therefore we are making a persistent copy instead of using Vertices directly */ NewElem.Mesh->VerticesTemplate = MakeShared(); NewElem.Mesh->VerticesTemplate->Positions = Vertices->Positions; NewElem.Mesh->VerticesTemplate->Positions3f = Vertices->Positions3f; NewElem.Mesh->VerticesTemplate->MaterialIndices = Vertices->MaterialIndices; NewElem.Mesh->VerticesTemplate->PositionToTriangle = Vertices->PositionToTriangle; NewElem.Mesh->TrianglesTemplate = Triangles; } else if(CollisionBufferHolder != "" && CollisionMesh.Find(CollisionBufferHolder) && !CollisionMesh.Find(CollisionBufferHolder)->Mesh->VerticesTemplate.IsValid()) { /* * #TODO Should never occur, remove */ #if SWDEBUG SW_LOG("Recreating Collision Vertice template") #endif const FCollisionMeshElement& BufferHolder = *CollisionMesh.Find(CollisionBufferHolder); TArray UV; USWBlueprintFunctionLibrary::CreateGridMeshWelded(CollisionVerticesPerPatch, CollisionVerticesPerPatch, Triangles, Vertices, UV, Spacing); { const int32 NumTriangles = Triangles->Indices.Num() / 3; Triangles->Triangles_CollisionOnly.Empty(); Triangles->Triangles_CollisionOnly.Reserve(NumTriangles); for (int32 TriIdx = 0; TriIdx < NumTriangles; TriIdx++) { FTriIndices& Triangle = Triangles->Triangles_CollisionOnly.AddDefaulted_GetRef(); Triangle.v0 = Triangles->Indices[(TriIdx * 3) + 0] /* + VertexBase */; Triangle.v1 = Triangles->Indices[(TriIdx * 3) + 1] /* + VertexBase */; Triangle.v2 = Triangles->Indices[(TriIdx * 3) + 2] /* + VertexBase */; } } BufferHolder.Mesh->VerticesTemplate = MakeShared(); BufferHolder.Mesh->VerticesTemplate->Positions = Vertices->Positions; BufferHolder.Mesh->VerticesTemplate->Positions3f = Vertices->Positions3f; BufferHolder.Mesh->VerticesTemplate->MaterialIndices = Vertices->MaterialIndices; BufferHolder.Mesh->VerticesTemplate->PositionToTriangle = Vertices->PositionToTriangle; BufferHolder.Mesh->TrianglesTemplate = Triangles; Vertices->Positions = BufferHolder.Mesh->VerticesTemplate->Positions; Vertices->Positions3f = BufferHolder.Mesh->VerticesTemplate->Positions3f; Vertices->MaterialIndices = BufferHolder.Mesh->VerticesTemplate->MaterialIndices; Triangles = BufferHolder.Mesh->TrianglesTemplate; SW_RT(NewElem.CollisionRT, World, Name.ToString() + FString::FromInt(GetUniqueID()), SizeT, TF_Nearest, RTF_RGBA8) RTAllocatedMemory(NewElem.CollisionRT) if (bExportPhysicalMaterialID_cached && LayerStoringMaterialID != "") { for (FSWProducers& layer : Producers) { if (layer.LayerName == "Height" || layer.LayerName == "height") { continue; } if (layer.LayerName != "" && layer.MaterialToGenerateLayer) { NewElem.LayerOrder.Add(layer.LayerName); UShaderWorldRT2D* LayerRT = nullptr; SW_RT(LayerRT, World, layer.LayerName + "_PhyMat_" + FString::FromInt(GetUniqueID()), SizeT, layer.LayerFiltering, layer.LayerFormat) RTAllocatedMemory(LayerRT) NewElem.LayerComputeForPhysicalMaterial.Add(layer.LayerName, { LayerRT ,nullptr }); } if (LayerStoringMaterialID == layer.LayerName) break; } } } else { check(CollisionMesh.Contains(CollisionBufferHolder)) const FCollisionMeshElement& BufferHolder = *CollisionMesh.Find(CollisionBufferHolder); check(BufferHolder.Mesh->VerticesTemplate.IsValid()) /* * No point copying irrelevant data that will be overriden once the computation is complete. */ Triangles = BufferHolder.Mesh->TrianglesTemplate; NewElem.CollisionRT = BufferHolder.CollisionRT; NewElem.LayerOrder = BufferHolder.LayerOrder; NewElem.LayerComputeForPhysicalMaterial = BufferHolder.LayerComputeForPhysicalMaterial; } NewElem.Mesh->CreateMeshSection(0, Vertices, Triangles, false); /* * If not in editor mode, only make collision blue mesh visible if we asked for collisions to be visible * The renderthread will otherwise completely skip the collision meshes */ NewElem.Mesh->SetMeshSectionVisible(0, CollisionVisible); if (!CollisionMat) { #if SWDEBUG SW_LOG("Material for Collision mesh not available for Shader World %s", *GetName()) #endif } else { #if WITH_EDITOR if ((CollisionBufferHolder != Name) && CollisionMesh.Contains(CollisionBufferHolder) && (*CollisionMesh.Find(CollisionBufferHolder)).Mesh) { NewElem.Mesh->SetMaterialFromOwner(0, (*CollisionMesh.Find(CollisionBufferHolder)).Mesh->GetMaterial(0)); } else { UMaterialInstanceDynamic* DynColMat = SWTakeCareOfMID(CollisionMat.Get(), this); SWorldSubsystem->SetScalarParameterValue(DynColMat, "MakeCollisionVisible", 1.0); SWorldSubsystem->SetScalarParameterValue(DynColMat, "CollisionResolution", CollisionResolution); NewElem.Mesh->SetMaterialFromOwner(0, DynColMat); } #endif } UsedCollisionMesh.Add(Name); CollisionShareable->UsedCollisionMesh.Add(Name); //if (CollisionShareable->CollisionMeshData.Num() < CollisionMesh.Num()) { if(!CollisionShareable->CollisionMeshData.Contains(Name)) CollisionShareable->CollisionMeshData.Add(Name, NewElem); } return *CollisionMesh.Find(Name); //return CollisionMesh[CollisionMesh.Num() - 1]; } ``` ```c++ void AShaderWorldActor::UpdateCollisionMeshData(FCollisionMeshElement& Mesh) { FVector MesgLoc = FVector((Mesh.Location * CollisionResolution * (CollisionVerticesPerPatch - 1) + FIntVector(0.f, 0.f, 1) * HeightOnStart)); if (!Generator) { #if SWDEBUG SW_LOG("Generator Material not available for Shader World %s", *GetName()) #endif } else { UWorld* World = GetWorld(); //OPTION A : Compute collision form GPU readback UMaterialInstanceDynamic* DynCollisionMat = Mesh.DynCollisionCompute; //如果生成高度材质无效,这里将会进行初始化。 if (!DynCollisionMat) { DynCollisionMat = SWTakeCareOfMID(Generator.Get(), this); Mesh.DynCollisionCompute = DynCollisionMat; SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "NoMargin", 1.f); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "TexelPerSide", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "PatchFullSize", CollisionResolution * (CollisionVerticesPerPatch - 1)); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "MeshScale", CollisionResolution * (CollisionVerticesPerPatch <= 1 ? 1 : CollisionVerticesPerPatch)); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "N", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "CacheRes", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, "LocalGridScaling", CollisionResolution); if (bExportPhysicalMaterialID_cached && LayerStoringMaterialID != "") { for (FSWProducers& Layer : Producers) { if (Layer.LayerName == "Height" || Layer.LayerName == "height") { continue; } if (Layer.LayerName != "" && Layer.MaterialToGenerateLayer) { check(Mesh.LayerComputeForPhysicalMaterial.Contains(Layer.LayerName)) UMaterialInstanceDynamic* LayerPartitionDynMat = SWTakeCareOfMID(Layer.MaterialToGenerateLayer, this); FSWCollisionLayerData& CLayerData = *Mesh.LayerComputeForPhysicalMaterial.Find(Layer.LayerName); CLayerData.DynMat = LayerPartitionDynMat; SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "PatchFullSize", CollisionResolution* (CollisionVerticesPerPatch - 1)); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "TexelPerSide", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "NoMargin", 1.f); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "MeshScale", CollisionResolution * (CollisionVerticesPerPatch <= 1 ? 1 : CollisionVerticesPerPatch)); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "N", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "CacheRes", CollisionVerticesPerPatch); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "LocalGridScaling", CollisionResolution); SWorldSubsystem->SetScalarParameterValue(LayerPartitionDynMat, "NormalMapSelect", 1.f); } if (LayerStoringMaterialID == Layer.LayerName) break; } } if (bUseMaterialCollection) { if (UShaderWorld_Material_Collection* MC = GetValid(MaterialCollection)) { for (auto& IDMat : MC->MaterialNameToID) { SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, FName(IDMat.Key), IDMat.Value); for (auto& CollisionLayerC : Mesh.LayerComputeForPhysicalMaterial) { if (CollisionLayerC.Value.DynMat) SWorldSubsystem->SetScalarParameterValue(CollisionLayerC.Value.DynMat, FName(IDMat.Key), IDMat.Value); } } } } else { for (auto& IDMat : MaterialNameToID) { SWorldSubsystem->SetScalarParameterValue(DynCollisionMat, FName(IDMat.Key), IDMat.Value); for (auto& CollisionLayerC : Mesh.LayerComputeForPhysicalMaterial) { if (CollisionLayerC.Value.DynMat) SWorldSubsystem->SetScalarParameterValue(CollisionLayerC.Value.DynMat, FName(IDMat.Key), IDMat.Value); } } } TSet UsedNames; for (FInstancedStruct& Seed : CurrentSeedsArray.SeedsArray) { if (Seed.IsValid()) { const UScriptStruct* Type = Seed.GetScriptStruct(); CA_ASSUME(Type); if (Type->IsChildOf(FTextureSeed::StaticStruct())) { FTextureSeed& TypedSeed = Seed.GetMutable(); if (!UsedNames.Contains(TypedSeed.SeedName)) { UsedNames.Add(TypedSeed.SeedName); SWorldSubsystem->SetTextureParameterValue(Mesh.DynCollisionCompute, TypedSeed.SeedName, TypedSeed.Value); for (auto& LayerEl : Mesh.LayerComputeForPhysicalMaterial) { SWorldSubsystem->SetTextureParameterValue(LayerEl.Value.DynMat, TypedSeed.SeedName, TypedSeed.Value); } } } else if (Type->IsChildOf(FLinearColorSeed::StaticStruct())) { FLinearColorSeed& TypedSeed = Seed.GetMutable(); if (!UsedNames.Contains(TypedSeed.SeedName)) { UsedNames.Add(TypedSeed.SeedName); SWorldSubsystem->SetVectorParameterValue(Mesh.DynCollisionCompute, TypedSeed.SeedName, TypedSeed.Value); for (auto& LayerEl : Mesh.LayerComputeForPhysicalMaterial) { SWorldSubsystem->SetVectorParameterValue(LayerEl.Value.DynMat, TypedSeed.SeedName, TypedSeed.Value); } } } else if (Type->IsChildOf(FScalarSeed::StaticStruct())) { FScalarSeed& TypedSeed = Seed.GetMutable(); if (!UsedNames.Contains(TypedSeed.SeedName)) { UsedNames.Add(TypedSeed.SeedName); SWorldSubsystem->SetScalarParameterValue(Mesh.DynCollisionCompute, TypedSeed.SeedName, TypedSeed.Value); for (auto& LayerEl : Mesh.LayerComputeForPhysicalMaterial) { SWorldSubsystem->SetScalarParameterValue(LayerEl.Value.DynMat, TypedSeed.SeedName, TypedSeed.Value); } } } else { #if SWDEBUG SW_LOG("Invalid Seed type found: '%s'", *GetPathNameSafe(Type)); #endif } } } } SWorldSubsystem->SetVectorParameterValue(DynCollisionMat,"PatchLocation", MesgLoc); //UKismetRenderingLibrary::ClearRenderTarget2D(this, Mesh.CollisionRT, FLinearColor::Black); #if SW_COMPUTE_GENERATION //使用CS计算碰撞数据,最后写入FCollisionMeshElement.CollisionRT SWorldSubsystem->DrawMaterialToRenderTarget( { false, false, GetWorld()->Scene, (float)GetWorld()->TimeSeconds, false, true, Mesh.CollisionRT->SizeX, (int32)(CollisionVerticesPerPatch - 1) * CollisionResolution, FVector(MesgLoc), FIntPoint(0), FIntPoint(Mesh.CollisionVerticesPerPatch,Mesh.CollisionVerticesPerPatch), false, ReadRequestLocation, DynCollisionMat, Mesh.CollisionRT }); #else UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, Mesh.CollisionRT, DynCollisionMat); #endif if (IsValid(BrushManager)) BrushManager->ApplyBrushStackToHeightMap(this, 0, Mesh.CollisionRT, Mesh.CollisionVerticesPerPatch, MesgLoc, CollisionResolution, CollisionVerticesPerPatch, true /*Collision heightmap have no border*/); //输出物理材质ID缓存,bExportPhysicalMaterialID_cached默认为false。 if (bExportPhysicalMaterialID_cached && (LayerStoringMaterialID != "")) { { int32 DrawCallCount = 0; TArray PreviousLayer; PreviousLayer.Reserve(Mesh.LayerOrder.Num()); int32 LayerIndice = 0; for (int k = 0; k < Mesh.LayerOrder.Num(); k++) { FString LayerName = Mesh.LayerOrder[k]; check(Mesh.LayerComputeForPhysicalMaterial.Contains(LayerName)) FSWCollisionLayerData& CLayerData = *Mesh.LayerComputeForPhysicalMaterial.Find(LayerName); if (!CLayerData.DynMat) { UE_LOG(LogTemp, Warning, TEXT("ERROR drawing layers for Physical material: !CLayerData.DynMat[%d]"), k); continue; } if (UShaderWorldRT2D* Layer_RT_To_Produce = CLayerData.RT) { SWorldSubsystem->SetVectorParameterValue(CLayerData.DynMat, "PatchLocation", MesgLoc); SWorldSubsystem->SetTextureParameterValue(CLayerData.DynMat, "HeightMap", Mesh.CollisionRT); SWorldSubsystem->SetTextureParameterValue(CLayerData.DynMat, "NormalMap", Mesh.CollisionRT);//#TODO Support normal map for collision ? for (int u = 0; u < LayerIndice; u++) { SWorldSubsystem->SetTextureParameterValue(CLayerData.DynMat, FName(*Mesh.LayerOrder[u]), PreviousLayer[u]); } #if SW_COMPUTE_GENERATION SWorldSubsystem->DrawMaterialToRenderTarget( { false, false, GetWorld()->Scene, (float)GetWorld()->TimeSeconds, false, true, Layer_RT_To_Produce->SizeX, (int32)(CollisionVerticesPerPatch - 1) * CollisionResolution, MesgLoc, FIntPoint(0), FIntPoint(Mesh.CollisionVerticesPerPatch,Mesh.CollisionVerticesPerPatch), false, ReadRequestLocation, CLayerData.DynMat, Layer_RT_To_Produce, Mesh.CollisionRT, Mesh.CollisionRT//#TODO Support normal map for collision ? }); #else UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, Layer_RT_To_Produce, Elem.LayerPartitionMatDyn[k]); #endif DrawCallCount++; if (IsValid(BrushManager)) DrawCallCount += BrushManager->ApplyBrushStackToLayer(this, 0, Layer_RT_To_Produce, Mesh.CollisionVerticesPerPatch, MesgLoc, CollisionResolution, CollisionVerticesPerPatch, LayerName); PreviousLayer.Add(Layer_RT_To_Produce); LayerIndice++; if (LayerStoringMaterialID == LayerName) { float PatchSize = (CollisionVerticesPerPatch - 1) * CollisionResolution; SWorldSubsystem->CopyAtoB(Mesh.CollisionRT, (*CollisionMesh.Find(CollisionBufferHolder)).CollisionRT_Duplicate); uint8 channel = (static_cast(LayerChannelStoringID)) + 1; SWorldSubsystem->CopyAtoB(Layer_RT_To_Produce, Mesh.CollisionRT, (*CollisionMesh.Find(CollisionBufferHolder)).CollisionRT_Duplicate, 0, channel, FVector2D(MesgLoc), FVector2D(MesgLoc), PatchSize, PatchSize, SWCopyConfig::AlphaChannelCombine); break; } } } } } if (false && bExportPhysicalMaterialID_cached && (LayerStoringMaterialID != "") && GetMeshNum() > 0 && CollisionMesh.Num() > 0 && (*CollisionMesh.Find(CollisionBufferHolder)).CollisionRT_Duplicate) { if (USWorldSubsystem* ShaderWorldSubsystem = SWorldSubsystem) { bool LayerIsRelevant = false; FString LayerSource = LayerStoringMaterialID.ToString(); for (auto& La : Producers) { if (La.LayerName == "Height" || La.LayerName == "height") continue; if (La.LayerName == LayerSource) { LayerIsRelevant = true; break; } } if (LayerIsRelevant) { FVector2D Location_Mesh(MesgLoc); FVector2D Extent = CollisionResolution * (CollisionVerticesPerPatch - 1) / 2.f * FVector2D(1.f, 1.f);//Margin FBox2D LocalMeshBox(Location_Mesh - Extent, Location_Mesh + Extent); int LOD_Candidate = -1; /////////////////////////////// for (int k = 0; k < GetMeshNum(); k++) { FClipMapMeshElement& Elem_Local = GetMesh(k); FIntVector ClipMapLocation = Elem_Local.Location - GetWorld()->OriginLocation; FVector2D Location_Elem_Local(ClipMapLocation.X, ClipMapLocation.Y); FVector2D Extent_Elem_Local = Elem_Local.GridSpacing * ((Elem_Local.N - 1) / 2) * FVector2D(1.f, 1.f); FBox2D Elem_Local_Footprint(Location_Elem_Local - Extent_Elem_Local, Location_Elem_Local + Extent_Elem_Local); if (Elem_Local_Footprint.IsInside(LocalMeshBox) && (Elem_Local.IsSectionVisible(0) || Elem_Local.IsSectionVisible(1))) { LOD_Candidate = k; } else { break; } } if (LOD_Candidate >= 0) { FClipMapMeshElement& Elem_Local = GetMesh(LOD_Candidate); float PatchSize = (Elem_Local.N - 1) * Elem_Local.GridSpacing; for (int k = 0; k < Elem_Local.LandLayers.Num(); k++) { if (Elem_Local.LandLayers_names[k] == LayerStoringMaterialID) { ShaderWorldSubsystem->CopyAtoB(Mesh.CollisionRT, (*CollisionMesh.Find(CollisionBufferHolder)).CollisionRT_Duplicate); uint8 channel = (static_cast(LayerChannelStoringID)) + 1; ShaderWorldSubsystem->CopyAtoB(Elem_Local.LandLayers[k], Mesh.CollisionRT, (*CollisionMesh.Find(CollisionBufferHolder)).CollisionRT_Duplicate, 0, channel, FVector2D(FVector(Elem_Local.Location)), FVector2D(MesgLoc), PatchSize, CollisionResolution * (CollisionVerticesPerPatch - 1), SWCopyConfig::AlphaChannelCombine); break; } } } else { //UE_LOG(LogTemp, Warning, TEXT("Non Admissible LOD_Candidate %d"), LOD_Candidate); } } else { UE_LOG(LogTemp, Warning, TEXT("No relevant data layer")); } } } if (!Mesh.HeightData.IsValid()) { Mesh.HeightData = MakeShared(); Mesh.HeightData->ReadData.SetNum(CollisionVerticesPerPatch * CollisionVerticesPerPatch); } if (!Mesh.ReadBackCompletion.IsValid()) { Mesh.ReadBackCompletion = MakeShared(); } //设置 碰撞GPU数据回读完成 变量为false Mesh.ReadBackCompletion->AtomicSet(false); //ShaderWorld::ReadPixelsFromRT(Mesh.CollisionRT, Mesh); //ShaderWorld::AsyncReadPixelsFromRT(Mesh.CollisionRT, Mesh.HeightData, Mesh.ReadBackCompletion); //从FCollisionMeshElement.CollisionRT读取数据并且写入FCollisionMeshElement.HeightData。 SWorldSubsystem->AsyncReadPixelsFromRT(Mesh.CollisionRT, Mesh.HeightData, Mesh.ReadBackCompletion); //将ID添加到CollisionReadToProcess 碰撞数据回读队列, CollisionReadToProcess.Add(Mesh.ID); return; } return; } ``` ### CollisionCPU ```c++ void AShaderWorldActor::CollisionCPU() { SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionCPU()", FColor::Magenta); SW_FCT_CYCLE() if ((*bPreprocessingCollisionUpdate.Get()) || CollisionShareable->CollisionMeshToUpdate.Num() > 0 || CollisionShareable->CollisionMeshToRenameMoveUpdate.Num() > 0 || CollisionShareable->CollisionMeshToCreate.Num() > 0) return; UWorld* World = GetWorld(); //SW Brush相关 bool bBrushManagerAskedRedraw = BrushManager && (BrushManagerRedrawScopes_collision.Num() > 0); FVector LocalActorLocation = GetActorLocation(); FIntVector LocalOriginLocation = GetWorld()->OriginLocation; TArray BrushRedrawScope; if (bBrushManagerAskedRedraw) { BrushRedrawScope = MoveTemp(BrushManagerRedrawScopes_collision); BrushManagerRedrawScopes_collision.Empty(); } bool bExternalCollisionRebuildRequest = !CollisionUpdateRequest.IsEmpty(); FBox2D BPCollisionRebuildRequest(ForceInit); if (bExternalCollisionRebuildRequest) CollisionUpdateRequest.Dequeue(BPCollisionRebuildRequest); TArray VisitorLocations; // Use latest camera we know if current array is empty if (CameraLocations.Num() <= 0) VisitorLocations.Add(CamLocation); VisitorLocations.Append(CameraLocations); (*bPreprocessingCollisionUpdate.Get()) = true; Async(EAsyncExecution::TaskGraph, [Completion = bPreprocessingCollisionUpdate, CollData = CollisionShareable, VisitorLocations, bBrushManagerAskedRedraw, BrushScope = MoveTemp(BrushRedrawScope), bExternalCollisionRebuildRequest, BPRecomputeScope = BPCollisionRebuildRequest, ColRingCount = CollisionGridMaxRingNumber, LocalActorLocation, LocalOriginLocation, Height0 = HeightOnStart, Bounded = WorldHasBounds_OnRebuild, GBounds = WorldBoundsOnRebuild, Lock = CollisionMeshAccessLock] { if (!CollData.IsValid()) { if (Completion.IsValid()) (*Completion.Get()) = false; return; } FScopeLock CollisionMeshArrayAccess(Lock.Get()); double CollisionWidth = CollData->CollisionResolution * (CollData->VerticePerPatch - 1); TMap BrushRedraws; for (const FBox2d& B : BrushScope) { const int32 MinBoxX_local = FMath::FloorToInt(((double)(B.Min.X + LocalOriginLocation.X)) / CollisionWidth + 0.45); const int32 MinBoxY_local = FMath::FloorToInt(((double)(B.Min.Y + LocalOriginLocation.Y)) / CollisionWidth + 0.45); const int32 MaxBoxX_local = FMath::FloorToInt(((double)(B.Max.X + LocalOriginLocation.X)) / CollisionWidth + 0.55); const int32 MaxBoxY_local = FMath::FloorToInt(((double)(B.Max.Y + LocalOriginLocation.Y)) / CollisionWidth + 0.55); for (int32 X_iter = MinBoxX_local; X_iter <= MaxBoxX_local; X_iter++) { for (int32 Y_iter = MinBoxY_local; Y_iter <= MaxBoxY_local; Y_iter++) { BrushRedraws.Add(FIntVector(X_iter, Y_iter, 0)); } } } /* * Get World Bounds in centimeters */ FBox SWorldBounds = GBounds; SWorldBounds.Min *= 100.0; SWorldBounds.Max *= 100.0; TSet ExternalCollisionsBounds; for (const ShaderWorld::FVisitor& SingleCamLoc : VisitorLocations) { if (SingleCamLoc.Bounds.IsValid) { ExternalCollisionsBounds.Append(USWBlueprintFunctionLibrary::GetCollisionMeshesLayout(CollisionWidth, SingleCamLoc.Bounds, SWorldBounds)); } } CollData->MultipleCamera.Empty(); CollData->LocRefs.Empty(); for (const ShaderWorld::FVisitor& SingleCamLoc : VisitorLocations) { if (SingleCamLoc.Bounds.IsValid) continue; double Cam_X_local = SingleCamLoc.Location.X + LocalOriginLocation.X; double Cam_Y_local = SingleCamLoc.Location.Y + LocalOriginLocation.Y; if (Bounded) { Cam_X_local = FMath::Max(FMath::Min(Cam_X_local, GBounds.Max.X * 100.0), GBounds.Min.X * 100.0); Cam_Y_local = FMath::Max(FMath::Min(Cam_Y_local, GBounds.Max.Y * 100.0), GBounds.Min.Y * 100.0); } const int CamX_local = FMath::RoundToInt(Cam_X_local / CollisionWidth); const int CamY_local = FMath::RoundToInt(Cam_Y_local / CollisionWidth); FIntVector LocRef_local = FIntVector(CamX_local, CamY_local, 0.f) * CollisionWidth + FIntVector(0.f, 0.f, 1) * Height0 - LocalOriginLocation; int32 LocalRingCount = FMath::Clamp(FMath::Floor(FMath::Abs(SingleCamLoc.Range * 100.0) / CollisionWidth), 1, ColRingCount); CollData->LocRefs.Add(LocRef_local, LocalRingCount); CollData->MultipleCamera.Add(FIntVector(CamX_local, CamY_local, 0), LocalRingCount); } TArray NameToRemove; for (const FName& CollID : CollData->UsedCollisionMesh) { FSWCollisionMeshElemData& El = *CollData->CollisionMeshData.Find(CollID); bool BeyondCriteria_local = !ExternalCollisionsBounds.Contains(El.Location); for (auto& Elem : CollData->LocRefs) { FIntVector& SingleLocRef = Elem.Key; int32& LocalRingCount = Elem.Value; const FVector ToCompLocal = FVector(FIntVector(El.MeshLocation) - SingleLocRef) / CollisionWidth; BeyondCriteria_local = BeyondCriteria_local && (FMath::Abs(ToCompLocal.X) > LocalRingCount + .1f || FMath::Abs(ToCompLocal.Y) > LocalRingCount + .1f); if (!BeyondCriteria_local) break; } if (BeyondCriteria_local) { CollData->AvailableCollisionMesh.Add(CollID); NameToRemove.Add(CollID); for (auto It = CollData->GroundCollisionLayout.CreateConstIterator(); It; ++It) { if(It->Value == CollID) { CollData->GroundCollisionLayout.Remove(It->Key); break; } } } else { if (bBrushManagerAskedRedraw || bExternalCollisionRebuildRequest) { bool UpdateRequested = false; if (bBrushManagerAskedRedraw) { if (BrushRedraws.Contains(El.Location)) { CollData->CollisionMeshToUpdate.Add(CollID); UpdateRequested = true; } } if (!UpdateRequested && bExternalCollisionRebuildRequest) { const FVector Location_LocalOrigin = FVector(El.Location * CollisionWidth + FIntVector(0, 0, 1) * LocalActorLocation.Z - LocalOriginLocation); FVector2D Location_Mesh(Location_LocalOrigin.X, Location_LocalOrigin.Y); FVector2D Extent = CollisionWidth / 2.f * FVector2D(1.f, 1.f); FBox2D LocalCollisionMeshBox(Location_Mesh - Extent, Location_Mesh + Extent); if (BPRecomputeScope.Intersect(LocalCollisionMeshBox)) { CollData->CollisionMeshToUpdate.Add(CollID); } } } } } for (const FName& TR : NameToRemove) { CollData->UsedCollisionMesh.Remove(TR); } BrushRedraws.Empty(); FBox WorldBounds = GBounds; WorldBounds.Min *= 100.0; WorldBounds.Max *= 100.0; for (auto& ExternalBounds : ExternalCollisionsBounds) { FIntVector LocMeshInt = ExternalBounds; FIntVector MeshLoc = LocMeshInt * CollisionWidth + FIntVector(0.f, 0.f, 1) * Height0 - LocalOriginLocation; if (Bounded && !WorldBounds.IntersectXY(FBox((FVector(LocMeshInt) - FVector(0.5, 0.5, 0.0)) * CollisionWidth, (FVector(LocMeshInt) + FVector(0.5, 0.5, 0.0)) * CollisionWidth))) continue; if (!CollData->GroundCollisionLayout.Contains(LocMeshInt)) { const FString CollisionMeshString = "SW_Collision_X_" + FString::FromInt(LocMeshInt.X) + "_Y_" + FString::FromInt(LocMeshInt.Y) + "_Z_" + FString::FromInt(LocMeshInt.Z); const FName CollisionMeshName = FName(*CollisionMeshString); if (CollData->AvailableCollisionMesh.Num() > 0 && CollData->AvailableCollisionMesh.Contains(CollisionMeshName)) { FSWCollisionMeshElemData& ElemData = *CollData->CollisionMeshData.Find(CollisionMeshName); CollData->UsedCollisionMesh.Add(CollisionMeshName); CollData->AvailableCollisionMesh.Remove(CollisionMeshName); ElemData.Location = LocMeshInt; ElemData.MeshLocation = FVector(MeshLoc); CollData->CollisionMeshToRenameMoveUpdate.AddUnique(CollisionMeshName); CollData->GroundCollisionLayout.Add(LocMeshInt, CollisionMeshName); } else { FCollisionMeshElement NewElem; NewElem.ID = CollisionMeshName; NewElem.Location = LocMeshInt; NewElem.MeshLocation = FVector(MeshLoc); CollData->UsedCollisionMesh.Add(CollisionMeshName); CollData->CollisionMeshData.Add(CollisionMeshName, NewElem); CollData->CollisionMeshToCreate.AddUnique(CollisionMeshName); CollData->CollisionMeshToRenameMoveUpdate.AddUnique(CollisionMeshName); CollData->GroundCollisionLayout.Add(LocMeshInt, CollisionMeshName); } } } for (auto& Elem : CollData->MultipleCamera) { FIntVector& SingleCam = Elem.Key; int32& LocalRingCount = Elem.Value; for (int r = LocalRingCount; r >= 0; r--) { for (int i = -r; i <= r; i++) { for (int j = -r; j <= r; j++) { if (abs(j) != r && abs(i) != r) continue; FIntVector LocMeshInt = FIntVector(SingleCam.X + i, SingleCam.Y + j, 0); FIntVector MeshLoc = LocMeshInt * CollisionWidth + FIntVector(0.f, 0.f, 1) * Height0 - LocalOriginLocation; if (Bounded && !WorldBounds.IntersectXY(FBox((FVector(LocMeshInt) - FVector(0.5, 0.5, 0.0)) * CollisionWidth, (FVector(LocMeshInt) + FVector(0.5, 0.5, 0.0)) * CollisionWidth))) continue; if (!CollData->GroundCollisionLayout.Contains(LocMeshInt)) { const FString CollisionMeshString = "SW_Collision_X_" + FString::FromInt(LocMeshInt.X) + "_Y_" + FString::FromInt(LocMeshInt.Y) + "_Z_" + FString::FromInt(LocMeshInt.Z); const FName CollisionMeshName = FName(*CollisionMeshString); if(CollData->AvailableCollisionMesh.Num() > 0 && CollData->AvailableCollisionMesh.Contains(CollisionMeshName)) { FSWCollisionMeshElemData& ElemData = *CollData->CollisionMeshData.Find(CollisionMeshName); CollData->UsedCollisionMesh.Add(CollisionMeshName); CollData->AvailableCollisionMesh.Remove(CollisionMeshName); ElemData.Location = LocMeshInt; ElemData.MeshLocation = FVector(MeshLoc); CollData->CollisionMeshToRenameMoveUpdate.AddUnique(CollisionMeshName); CollData->GroundCollisionLayout.Add(LocMeshInt, CollisionMeshName); } else { FCollisionMeshElement NewElem; NewElem.ID = CollisionMeshName; NewElem.Location = LocMeshInt; NewElem.MeshLocation = FVector(MeshLoc); CollData->UsedCollisionMesh.Add(CollisionMeshName); CollData->CollisionMeshData.Add(CollisionMeshName, NewElem); CollData->CollisionMeshToCreate.AddUnique(CollisionMeshName); CollData->CollisionMeshToRenameMoveUpdate.AddUnique(CollisionMeshName); CollData->GroundCollisionLayout.Add(LocMeshInt, CollisionMeshName); } } } } } } if (Completion.IsValid()) (*Completion.Get()) = false; } ); } ``` ### 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() # 碰撞Python逻辑 ```python def TickCheckWorldReady(self, dt: float) -> bool: is_shader_world_ready = self.ShaderWorldActor.WorldAndCollisionGeneratedReady() if not is_shader_world_ready: return True is_ready = self.ShaderWorldActor.CheckCollisionGeneratedReady(self.AvatarBornPosition) print("LBK -------- TickCheckWorldReady", self.FailedCheckCnt, is_ready) if is_ready: self.OnWorldMeshReady() ue.RemoveTicker(self.worldTickHander) # 等待地形物理加载完毕 if self.OtherLoadFinshed: GlobalData.TimerSystem.AddTimer(1, self.ReallyStartDriving) else: self.OtherLoadFinshed = True print('LBK -------------- DrivingModeSystem: TickCheckWorldReady', GlobalData.p.pos, self.AvatarBornPosition) return False self.FailedCheckCnt += 1 if self.FailedCheckCnt > self.FailedAttemptCnt: #self.FailedAttemptCnt为40 self.ShaderWorldActor.RebuildWorld() self.FailedCheckCnt = 0 return True # 通过LineTraceSingleForObjects检测碰撞高度是否超过5,来判断碰撞是否生成。 def CheckCollisionGeneratedReady(self, checkPosition: ue.Vector) -> bool: HitStartLocation = checkPosition + ue.Vector(0, 0, 50000.0) HitEndLocation = checkPosition + ue.Vector(0, 0, -50000.0) hitState, hitResult = ue.KismetSystemLibrary.LineTraceSingleForObjects( self.GetWorld(), HitStartLocation, HitEndLocation, [ue.EObjectTypeQuery.ObjectTypeQuery1, ue.EObjectTypeQuery.ObjectTypeQuery13], # Ground False, # 是否追踪复杂碰撞 [], ue.EDrawDebugTrace.ForDuration if GlobalData.IsDebugMode else ue.EDrawDebugTrace.NONE, # 调试可视化 True, # 忽略自身 ue.LinearColor(1.0, 0.0, 0.0, 1.0), # 红色 ue.LinearColor(0.0, 1.0, 0.0, 1.0), # 绿色 20.0 # 调试线条持续时间 ) if not hitState: return False # TODO LBK 这个射线检测的方式不太准确,那个物理生成,会从平面开始 return hitResult.Location.Z > 5.0 ``` ```c++ bool AShaderWorldActor::WorldAndCollisionGeneratedReady() { if (!WorldGeneratedAndReady()) return false; if (!CollisionShareable.IsValid()) return false; FScopeLock CollisionMeshArrayAccess(CollisionMeshAccessLock.Get()); // 1. 必须有碰撞布局 if (CollisionShareable->GroundCollisionLayout.Num() == 0) return false; // 2. 检查所有Patch都ready for (const auto& Pair : CollisionShareable->GroundCollisionLayout) { const FIntVector& Loc = Pair.Key; const FName& CollisionName = Pair.Value; // Patch必须存在 if (!CollisionMesh.Contains(CollisionName)) return false; // Patch必须在已用集合 if (!CollisionShareable->UsedCollisionMesh.Contains(CollisionName)) return false; // Patch必须ReadBack完成 const FCollisionMeshElement& CollisionElement = *CollisionMesh.Find(CollisionName); if (!CollisionElement.ReadBackCompletion.IsValid() || !(*CollisionElement.ReadBackCompletion.Get())) return false; // Patch不能在待处理队列 if (CollisionReadToProcess.Contains(CollisionName)) return false; if (!UsedCollisionMesh.Contains(CollisionName)) return false; // Patch不能在工作队列 for (const auto& WQ : CollisionWorkQueue) if (WQ.MeshID == CollisionName) return false; } return true; } ```