35 KiB
Raw Permalink Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
Untitled 2025-06-03 10:19:25

前言

  1. ShaderWorldPCGInterop
  2. ShaderWorld
  3. ShaderWorldCore

ShaderWorld

USWorldSubsystem

主要管理:

  • TArray<USWContextBase*> SW_Contexts
  • 渲染相关变量RT_Ready、RenderThreadPoked、RenderThreadResponded处理。

ASWorld

AShaderWorldActor

大致启动流程

大致启动流程:

  • BeginPlay()
    1. 设置SWCamera Location。
    2. 清空所有数据。
    3. InitiateWorld()
      1. 更新相关变量GenerateCollision_last、VerticalRangeMeters_last、WorldHasBounds_OnRebuild。Segmented_InitializedbExportPhysicalMaterialID_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
void AShaderWorldActor::ReadbacksManagement()
{
	if (!bProcessingHeightRetrieval.IsValid())
		bProcessingHeightRetrieval = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
	if (!bProcessingHeightRetrievalRT.IsValid())
		bProcessingHeightRetrievalRT = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
	
	//PointsPendingReadBacks存储从GPU回读的采样坐标位置;在RetrieveHeightAt()调用HeightReadBackFence.BeginFence(true)开启渲染栅栏,不开启这段逻辑不会执行。
	if ((*bProcessingHeightRetrieval.Get()) && (*bProcessingHeightRetrievalRT.Get()) && HeightReadBackFence.IsFenceComplete() && PointsPendingReadBacks.IsValid())
	{
		// ReadBackHeightData uin8 贴图数据
		if (!ReadBackHeightData.IsValid())
			return;
		
		//For now we do Compute samples on a rendertarget 5x5, therefore 25 positions evaluated per request.
		//每次请求可以读取25个点。
		const int NumOfVertex = 25;

		PositionsOfReadBacks.Empty();
		PositionsOfReadBacks.AddUninitialized(25);

		uint8* ReadData8 = (uint8*)ReadBackHeightData->ReadData.GetData();
			uint16 MaterialIndice = 0;
		for (int32 k = 0; k < NumOfVertex; k++)
		{
			FVector3f& PositionSample = PositionsOfReadBacks[k];

			if (RendererAPI == EGeoRenderingAPI::OpenGL)
			{
				int X = k % 5;
				int Y = k / 5;

				int index = X + Y * 5;

				Y = (5 - 1) - Y;

				index = X + Y * 5;

				PositionSample = FVector3f(PointsPendingReadBacks->PositionsXY[2 * k], PointsPendingReadBacks->PositionsXY[2 * k + 1], GetHeightFromGPURead(&ReadData8[index * 4], MaterialIndice) / HeightScale);
			}
			else
				PositionSample = FVector3f(PointsPendingReadBacks->PositionsXY[2 * k], PointsPendingReadBacks->PositionsXY[2 * k + 1], GetHeightFromGPURead(&ReadData8[k * 4], MaterialIndice) / HeightScale);

		}

		if(HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks))
		{
			//SW_LOG("HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks) PositionsOfReadBacks[0] %s",*PositionsOfReadBacks[0].ToString())
		}
		else
		{
			//SW_LOG("Fail HeightRetrieveDelegate.ExecuteIfBound(PositionsOfReadBacks)")
		}

		bProcessingHeightRetrieval->AtomicSet(false);
		bProcessingHeightRetrievalRT->AtomicSet(false);
	}

	//For now MinMax is here
	//处理FClipMapMeshElement的最大最小队列
	if (UseSegmented())
	{
		if(WorldHasBounds_OnRebuild)
		{
			FClipMapMeshElement& MeshEl = FarLOD_BoundedWorld;

			if (MeshEl.MinMaxQueue.Num() > 0)
			{
				MeshEl.ProcessMinMaxQueue(SWorldSubsystem);
			}
		}
		
		for (FClipMapMeshElement& MeshEl : Meshes)
		{

			if (MeshEl.MinMaxQueue.Num() > 0)
			{
				MeshEl.ProcessMinMaxQueue(SWorldSubsystem);
			}
		}
	}
}

CollisionManagement()

  • ThreadSafe
    • bool
      • bProcessingGroundCollision
      • bPreprocessingCollisionUpdate
      • EditRebuild传递Bool值给 rebuild。
    • FSWCollisionManagementShareableData
      • CollisionShareable
  • FRenderCommandFence
    • 1
  • bool
    • RedbuildCollisionContext
  • Array
    • CollisionReadToProcess
    • CollisionWorkQueue类型为FCollisionProcessingWork碰撞处理任务队列将回读的
  • FCollisionMeshElement
    • ReadBackCompletion碰撞数据GPU回读是否完成。
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()

设置相关变量。

bool AShaderWorldActor::SetupCollisions()
{
	SW_FCT_CYCLE()

	if (!bHadGeneratorAtRebuildTime)
		return false;

	if (!Shareable_ID.IsValid() || Meshes.Num() <= 0)
		return false;

	//初始化线程安全变量
	if (!bProcessingGroundCollision.IsValid())
		bProcessingGroundCollision = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
	if (!bPreprocessingCollisionUpdate.IsValid())
		bPreprocessingCollisionUpdate = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();

	if (EditRebuild)
	{
		EditRebuild.AtomicSet(false);
		rebuild = true;
	}

	if (rebuild)
		RedbuildCollisionContext = true;
	//重建相关变量初始化
	if (RedbuildCollisionContext)
	{
		if (!(*bProcessingGroundCollision.Get()) && !(*bPreprocessingCollisionUpdate.Get())
			&& CollisionProcess.IsFenceComplete()
			&& (CollisionMesh.Num() <= 0)
			&& (UsedCollisionMesh.Num() <= 0))
		{

			CollisionShareable = nullptr;

			CollisionWorkQueue.Empty();
			CollisionReadToProcess.Empty();

			RedbuildCollisionContext = false;

		}
	}

	if (RedbuildCollisionContext)
		return false;

	if (!GenerateCollision)
		return false;

	if (!CollisionShareable.IsValid())
		CollisionShareable = MakeShared<FSWCollisionManagementShareableData, ESPMode::ThreadSafe>(CollisionResolution, CollisionVerticesPerPatch);

	if ((*bProcessingGroundCollision.Get()) || (*bPreprocessingCollisionUpdate.Get()) || !CameraSet || (Meshes.Num() == 0))
		return false;

	//Let the data layer be computed before generating collisions and trying to extract material IDs
	if (bExportPhysicalMaterialID && (WorldCycle < 2))
		return false;


	if (CollisionVisibleChanged)
	{
		if (GetWorld())
		{
			for (auto& ColM : CollisionMesh)
			{
				if (auto& Mesh = ColM.Value.Mesh)
				{
					Mesh->SetMeshSectionVisible(0, CollisionVisible);
				}
			}
		}
		CollisionVisibleChanged.AtomicSet(false);
	}

	return true;
}

CollisionFinalizeWork()

结束碰撞生成任务true为完成false为任务超时未完成。

  • TMap<FName,FCollisionMeshElement> CollisionMesh碰撞Name => 碰撞Mesh Map。
    • FCollisionMeshElement管理单个碰撞数据的结构体。
  • UBodySetup虚幻引擎中一个重要的物理相关类主要用于定义和存储物体的物理属性和碰撞设置
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<FSWShareableVerticePositionBuffer, ESPMode::ThreadSafe>& 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<TArray<FVector>, ESPMode::ThreadSafe> Normals = SWComputeNormalsForPatch(PBuffer, IndexB);

				AsyncTask(ENamedThreads::GameThread, [WeakThis,PBuffer, Normals]()
				{
						if (WeakThis.IsValid())
						{
							if (UShaderWorldCollisionComponent* Comp = Cast<UShaderWorldCollisionComponent>(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<AShaderWorldActor>(GetOwner()))
			{
				{
					// Enqueue command to send to render thread
					FShaderWProceduralMeshSceneProxy* ProcMeshSceneProxy = (FShaderWProceduralMeshSceneProxy*)(SceneProxy && !IsRenderStateDirty() ? SceneProxy : nullptr);
					ENQUEUE_RENDER_COMMAND(FGeoCProcMeshSectionUpdate)
						([ProcMeshSceneProxy, SectionData](FRHICommandListImmediate& RHICmdList)
						{
							if(ProcMeshSceneProxy)
								ProcMeshSceneProxy->UpdateSection_RenderThread(SectionData);
						});
				}				
			}
			else
			{
				//SW_LOG("Update trimesh !owner")
			}
		}
		else
		{
			//SW_LOG("Update trimesh !(SceneProxy && !IsRenderStateDirty())")
		}

		MarkRenderTransformDirty();
	}
}

CollisionPreprocessGPU()

GPU生成碰撞 的预处理阶段。

bool AShaderWorldActor::CollisionPreprocessGPU()
{
	SCOPED_NAMED_EVENT_TEXT("AShaderWorldActor::CollisionPreprocessGPU()", FColor::Magenta);
	SW_FCT_CYCLE()

	for (int32 CollID = CollisionReadToProcess.Num() - 1; CollID >= 0; CollID--)
	{
		const FName& ElID = CollisionReadToProcess[CollID];


		if (!CollisionMesh.Find(ElID))
		{
			CollisionWorkQueue.Empty();
			CollisionReadToProcess.RemoveAt(CollID);
			continue;
		}

		//判断FCollisionMeshElement是否有效以及是否将碰撞数据回读完成如果完成则将数据添加到碰撞处理队列CollisionWorkQueue并且从碰撞回读队列CollisionReadToProcess
		FCollisionMeshElement& Mesh = *CollisionMesh.Find(ElID);
		if ((*Mesh.ReadBackCompletion.Get()))
		{
			ensure(Mesh.Mesh);

			if(FGeoCProcMeshSection* Section = Mesh.Mesh->GetProcMeshSection(0))
			{
				if(CollisionMesh.Contains(CollisionBufferHolder))
				{
					CollisionWorkQueue.Emplace(
						ElID
						, Mesh.HeightData
						, (*CollisionMesh.Find(CollisionBufferHolder)).Mesh->VerticesTemplate
						, (*CollisionMesh.Find(CollisionBufferHolder)).Mesh->TrianglesTemplate);
				}
				else
				{
					SW_LOG("!CollisionMesh.Contains(CollisionBufferHolder)")
				}

			}
			else
			{
				SW_LOG("CollisionPreprocessGPU :: Mesh.Mesh->GetProcMeshSection(0) = nullptr")
			}

			
			CollisionReadToProcess.RemoveAt(CollID);
		}
	}

	if (!CollisionReadToProcess.IsEmpty())
	{
		return false;
	}


	CollisionReadToProcess.Empty();

	if (CollisionWorkQueue.Num() > 0)
	{
		(*bProcessingGroundCollision.Get()) = true;

		AShaderWorldActor* SWContext = this;

		Async(EAsyncExecution::TaskGraph, [Completion = bProcessingGroundCollision, RenderAPI = RendererAPI, VerticesPerPatch = CollisionVerticesPerPatch, Work = CollisionWorkQueue]
			{

				ParallelFor(Work.Num(), [&](int32 WorkIndex)
					{						
						
						const FCollisionProcessingWork& WorkEl = Work[WorkIndex];


						if (!WorkEl.Read.IsValid() || !WorkEl.SourceB.IsValid() || !WorkEl.DestB.IsValid())
							return;

						const int NumOfVertex = WorkEl.SourceB->Positions.Num();

						WorkEl.DestB->Positions.SetNum(NumOfVertex);
						WorkEl.DestB->Positions3f.SetNum(NumOfVertex);
						WorkEl.DestB->MaterialIndices.SetNum(NumOfVertex);
						WorkEl.DestB->Bound = FBox(EForceInit::ForceInit);

						FVector LocationfVertice_WS(0);
						uint16 MaterialIndice = 0;

						uint8* ReadData8 = (uint8*)WorkEl.Read->ReadData.GetData();

						TSet<int32>& TrianglesAffectedByHoles = WorkEl.DestB->TrianglesAffectedByHoles;
						TrianglesAffectedByHoles.Empty();

						//#TODO ISPC slower ?
#if 0 // INTEL_ISPC
							if (RenderAPI == EGeoRenderingAPI::OpenGL)
							{
								ispc::ShaderWorld_HeightFromGPUReadOpenGL(NumOfVertex, VerticesPerPatch, ReadData8,(ispc::FVector*)WorkEl.SourceB->Positions.GetData(), (ispc::FVector*)WorkEl.DestB->Positions.GetData(), (ispc::FVector3f*)WorkEl.DestB->Positions3f.GetData(), WorkEl.DestB->MaterialIndices.GetData());
								
							}
							else
							{
								ispc::ShaderWorld_HeightFromGPURead(NumOfVertex, VerticesPerPatch, ReadData8, (ispc::FVector3f*)WorkEl.SourceB->Positions3f.GetData(), (ispc::FVector*)WorkEl.DestB->Positions.GetData(), (ispc::FVector3f*)WorkEl.DestB->Positions3f.GetData(), WorkEl.DestB->MaterialIndices.GetData());
							}
#else

						
						for (int32 k = 0; k < NumOfVertex; k++)
							{

								if (RenderAPI == EGeoRenderingAPI::OpenGL)
								{
									const int index = k % VerticesPerPatch + (VerticesPerPatch - 1 - (k / VerticesPerPatch)) * VerticesPerPatch;

									LocationfVertice_WS = FVector(WorkEl.SourceB->Positions[k].X, WorkEl.SourceB->Positions[k].Y, GetHeightFromGPURead(&ReadData8[4 * index], MaterialIndice));
								}
								else
									LocationfVertice_WS = FVector(WorkEl.SourceB->Positions[k].X, WorkEl.SourceB->Positions[k].Y, GetHeightFromGPURead(&ReadData8[4 * k], MaterialIndice));

								WorkEl.DestB->Positions[k] = LocationfVertice_WS;
								WorkEl.DestB->Positions3f[k] = FVector3f(LocationfVertice_WS);
								WorkEl.DestB->MaterialIndices[k] = MaterialIndice;

								
								/*
								 * Height below -7km means terrain hole
								 */
								if(WorkEl.DestB->Positions[k].Z < -700000.0)
								{
									/*
									 * Find triangles including this vertex and add them to removed triangles
									 */

									if(WorkEl.SourceB->PositionToTriangle.Contains(k))
									{
										TrianglesAffectedByHoles.Append(*WorkEl.SourceB->PositionToTriangle.Find(k));
									}
								}
								else
								{
									WorkEl.DestB->Bound += WorkEl.DestB->Positions[k];
								}
							}

#endif
						/*
						 * If the terrain has holes, create a custom index buffer with the related triangles removed
						 */
						if(TrianglesAffectedByHoles.Num() > 0)
						{
							WorkEl.DestB->FilteredTriangles = MakeShared<FSWShareableIndexBuffer, ESPMode::ThreadSafe>();

							for(int32 Triangle = 0; Triangle < WorkEl.TriangleTemplate->Indices.Num()/3; Triangle++)
							{
								if(!TrianglesAffectedByHoles.Contains(Triangle))
								{
									WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3]);
									WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3 + 1]);
									WorkEl.DestB->FilteredTriangles->Indices.Add(WorkEl.TriangleTemplate->Indices[Triangle * 3 + 2]);

									WorkEl.DestB->FilteredTriangles->Triangles_CollisionOnly.Add(WorkEl.TriangleTemplate->Triangles_CollisionOnly[Triangle]);
								}
							}
						}
						else
						{
							WorkEl.DestB->FilteredTriangles.Reset();
						}


						//WorkEl.DestB->Bound = FBox(WorkEl.DestB->Positions);
					}
				);

				if (Completion.IsValid())
					Completion->AtomicSet(false);

			});
	}

	return true;
}

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
    • #InitializeReadBackDependencies
    • 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<FReadBackTask> PendingReads;
      2. 之后会在USWorldSubsystem::Tick()中调用ShaderWorld::GSWReadbackManager.TickReadBack(),不断检查是否可回读,并进行最终回读。
  • 调用顺序Tick() -> CollisionManagement() -> CollisionGPU() -> UpdateCollisionMeshData()
namespace ShaderWorld
{
	FORCEINLINE void AsyncReadPixelsFromRT(UShaderWorldRT2D* InRT, TSharedPtr<FSWColorRead, ESPMode::ThreadSafe> Destination, TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe> Completion)
	{

		ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)(
			[InRT, HeightData = Destination, Completion = Completion](FRHICommandListImmediate& RHICmdList)
			{
				check(IsInRenderingThread());

				if (HeightData.IsValid() && InRT->GetResource())
				{
					FRDGBuilder GraphBuilder(RHICmdList);
					TSharedPtr<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(TEXT("SWGPUTextureReadback"));
					FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture"));
					AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture);
					GraphBuilder.Execute();

					ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));
				}

			});	
	}

InitializeReadBackDependencies

  • 作用初始化几个GPU数据回读用的RT。
  • 调用顺序BeginPlay() -> InitiateWorld() -> InitializeReadBackDependencies()
  1. 初始化3个RTReadRequestLocation、ReadRequestLocationHeightmap、GeneratorDynamicForReadBack。
  2. 会设置TObjectPtr < UMaterialInstanceDynamic> GeneratorDynamicForReadBack各种变量
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);
  1. 设置随机种子相关Shader Parameter。

InitiateMaterials

作用:初始化TArray<FClipMapMeshElement> 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
  • UShaderWorldRT2DUTextureRenderTarget2D
    • ReadRequestLocationRTF_RG32f初始化于InitializeReadBackDependencies() <- InitiateWorld()
    • ReadRequestLocationHeightmapRTF_RGBA8初始化于InitializeReadBackDependencies() <- InitiateWorld()

代码

bool AShaderWorldActor::RetrieveHeightAt(const TArray<FVector>& Origin, const FSWHeightRetrievalDelegate& Callback)
{	
	if (!GeneratorDynamicForReadBack || !SWorldSubsystem)
		return false;

	if (!bProcessingHeightRetrieval.IsValid())
	{
		bProcessingHeightRetrieval = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
		bProcessingHeightRetrieval->AtomicSet(false);
	}
	if (!bProcessingHeightRetrievalRT.IsValid())
	{
		bProcessingHeightRetrievalRT = MakeShared<FThreadSafeBool, ESPMode::ThreadSafe>();
		bProcessingHeightRetrievalRT->AtomicSet(false);
	}


	if (!(*bProcessingHeightRetrieval.Get()) && ReadRequestLocation && ReadRequestLocationHeightmap && GeneratorDynamicForReadBack)
	{
		bProcessingHeightRetrieval->AtomicSet(true);
		bProcessingHeightRetrievalRT->AtomicSet(false);
		HeightRetrieveDelegate = Callback;

		//初始化采样点数组结构体FSWShareableSamplePoints
		PointsPendingReadBacks = MakeShared<FSWShareableSamplePoints, ESPMode::ThreadSafe>();
		TSharedPtr<FSWShareableSamplePoints>& Samples = PointsPendingReadBacks;

		FBox BoundingBoxRead(Origin);

		Samples->PositionsXY.SetNum(25 * 2);
		for (int i = 0; i < 25; i++)
		{
			if (i < Origin.Num())
			{
				Samples->PositionsXY[i * 2] = Origin[i].X;
				Samples->PositionsXY[i * 2 + 1] = Origin[i].Y;
			}
			else
			{
				Samples->PositionsXY[i * 2] = 0.f;
				Samples->PositionsXY[i * 2 + 1] = 0.f;
			}
		}

		if (USWorldSubsystem* ShaderWorldSubsystem = SWorldSubsystem)
		{
			//从渲染线程
			ShaderWorldSubsystem->LoadSampleLocationsInRT(ReadRequestLocation, Samples);

#if SW_COMPUTE_GENERATION
			ShaderWorldSubsystem->DrawMaterialToRenderTarget(
			{   false,
				 false,
				GetWorld()->Scene,
				(float)GetWorld()->TimeSeconds,
				false,
				true,
				ReadRequestLocationHeightmap->SizeX,
				10,
				FVector(0.f),
				true,
				ReadRequestLocation,
				GeneratorDynamicForReadBack,
				ReadRequestLocationHeightmap
			});
#else
			UKismetRenderingLibrary::DrawMaterialToRenderTarget(this, ReadRequestLocationHeightmap, GeneratorDynamicForReadBack);
#endif

			int32 Size_RT_Readback = ReadRequestLocationHeightmap.Get()->SizeX;

			FVector Barycentre = BoundingBoxRead.GetCenter();
			FVector Extent = BoundingBoxRead.GetExtent();
			float gridspacing = Extent.X * 2.0 / (Size_RT_Readback - 1);

			if (IsValid(BrushManager))
				BrushManager->ApplyBrushStackToHeightMap(this, 0, ReadRequestLocationHeightmap.Get(), Barycentre, gridspacing, Size_RT_Readback, true, true, ReadRequestLocation.Get());


			ReadBackHeightData = MakeShared<FSWColorRead, ESPMode::ThreadSafe>();
			ReadBackHeightData->ReadData.SetNum(25);

			ENQUEUE_RENDER_COMMAND(ReadGeoClipMapRTCmd)(
				[InRT = ReadRequestLocationHeightmap, HeightData = ReadBackHeightData, Completion = bProcessingHeightRetrievalRT](FRHICommandListImmediate& RHICmdList)
				{
					check(IsInRenderingThread());

					if (HeightData.IsValid() && InRT->GetResource())
					{
						FRDGBuilder GraphBuilder(RHICmdList);

						TSharedPtr<FRHIGPUTextureReadback> ReadBackStaging = MakeShared<FRHIGPUTextureReadback>(TEXT("SWGPUTextureReadback"));

						FRDGTextureRef RDGSourceTexture = RegisterExternalTexture(GraphBuilder, InRT->GetResource()->TextureRHI, TEXT("SWSourceTextureToReadbackTexture"));

						AddEnqueueCopyPass(GraphBuilder, ReadBackStaging.Get(), RDGSourceTexture);

						GraphBuilder.Execute();

						ShaderWorld::GSWReadbackManager.AddPendingReadBack(RHICmdList, GPixelFormats[RDGSourceTexture->Desc.Format].BlockBytes, RDGSourceTexture->Desc.Extent.X, RDGSourceTexture->Desc.Extent.Y, ReadBackStaging, const_cast<TSharedPtr<FSWColorRead, ESPMode::ThreadSafe>&>(HeightData), const_cast<TSharedPtr < FThreadSafeBool, ESPMode::ThreadSafe>&>(Completion));

					}

				});

			HeightReadBackFence.BeginFence(true);
		}

		return true;
	}

	return false;
}

RequestReadBackLoad

bool USWorldSubsystem::LoadSampleLocationsInRT(UShaderWorldRT2D* LocationsRequestedRT,
                                               TSharedPtr<FSWShareableSamplePoints>& Samples)
{
	if (!RenderThreadResponded)
		return false;

	const SWSampleRequestComputeData ReadBackData(LocationsRequestedRT, Samples);
	SWToolBox->RequestReadBackLoad(ReadBackData);
	return true;
}

SWShaderToolBox

RequestReadBackLoad

void SWShaderToolBox::RequestReadBackLoad(const SWSampleRequestComputeData& Data) const
{
	if (Data.CPU)
		return CPUTools.RequestReadBackLoad(Data);

	ENQUEUE_RENDER_COMMAND(ShaderTools_copy_rt)
		([this, Data](FRHICommandListImmediate& RHICmdList)
			{
				if (Data.SamplesXY && Data.SamplesXY->GetResource())
				RequestReadBackLoad_RT(RHICmdList,Data);
			}
	);

}

void SWShaderToolBox::RequestReadBackLoad_RT(FRHICommandListImmediate& RHICmdList, const SWSampleRequestComputeData& Data) const
{
	if (!(Data.SamplesXY && Data.SamplesXY->GetResource()))
		return;

	FRDGBuilder GraphBuilder(RHICmdList);

		{
			RDG_EVENT_SCOPE(GraphBuilder, "ShaderWorld  PositionReadBack");
			RDG_GPU_STAT_SCOPE(GraphBuilder, ShaderWorldReadBack);

			FIntVector GroupCount;
			GroupCount.X = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeX(), (float)SW_LoadReadBackLocations_GroupSizeX);
			GroupCount.Y = FMath::DivideAndRoundUp((float)Data.SamplesXY->GetResource()->GetSizeY(), (float)SW_LoadReadBackLocations_GroupSizeY);
			GroupCount.Z = 1;

			const FUnorderedAccessViewRHIRef RT_UAV = GraphBuilder.RHICmdList.CreateUnorderedAccessView(Data.SamplesXY->GetResource()->TextureRHI);

			const FRDGBufferRef LocationRequest = CreateUploadBuffer(
				GraphBuilder,
				TEXT("SWLoadSampleLocations"),
				sizeof(float),
				Data.SamplesSource->PositionsXY.Num(),
				Data.SamplesSource->PositionsXY.GetData(),
				Data.SamplesSource->PositionsXY.Num() * Data.SamplesSource->PositionsXY.GetTypeSize()
			);

			const FRDGBufferSRVRef LocationRequestSRV = GraphBuilder.CreateSRV(FRDGBufferSRVDesc(LocationRequest, PF_R32_FLOAT));;

			FLoadReadBackLocations_CS::FPermutationDomain PermutationVector;
			TShaderMapRef<FLoadReadBackLocations_CS> ComputeShader(GetGlobalShaderMap(GMaxRHIFeatureLevel), PermutationVector);
	
			FLoadReadBackLocations_CS::FParameters* PassParameters = GraphBuilder.AllocParameters<FLoadReadBackLocations_CS::FParameters>();
			PassParameters->SampleDim = Data.SamplesXY->GetResource()->GetSizeX();
			PassParameters->DestLocationsTex = RT_UAV;
			PassParameters->SourceLocationBuffer = LocationRequestSRV;


			GraphBuilder.AddPass(
				RDG_EVENT_NAME("ShaderWorld LoadReadBacklocations_CS"),
				PassParameters,
				ERDGPassFlags::Compute |
				ERDGPassFlags::NeverCull,
				[PassParameters, ComputeShader, GroupCount](FRHICommandList& RHICmdList)
				{
					FComputeShaderUtils::Dispatch(RHICmdList, ComputeShader, *PassParameters, GroupCount);
				});

		}

	GraphBuilder.Execute();
}

FLoadReadBackLocations_CS

uint SampleDim;
RWTexture2D<float2> DestLocationsTex;
Buffer<float> SourceLocationBuffer;

[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, THREADGROUP_SIZEZ)]
void SampleLocationLoaderCS(uint3 ThreadId : SV_DispatchThreadID)
{
    if (any(ThreadId.xy >= SampleDim.xx))
        return;

	uint IndexPixel = (ThreadId.x + ThreadId.y * SampleDim) * 2;
	
	DestLocationsTex[ThreadId.xy] = float2(SourceLocationBuffer[IndexPixel],SourceLocationBuffer[IndexPixel + 1]);
}

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()