// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. /*============================================================================= LandscapeRender.cpp: New terrain rendering =============================================================================*/ #include "Landscape.h" #include "Materials/MaterialExpressionTextureCoordinate.h" #include "Materials/MaterialExpressionLandscapeLayerCoords.h" #include "ShaderParameters.h" #include "ShaderParameterUtils.h" #include "RHIStaticStates.h" #include "LandscapeRender.h" #include "LandscapeEdit.h" #include "LevelUtils.h" #include "MaterialCompiler.h" #include "LandscapeMaterialInstanceConstant.h" #include "RawIndexBuffer.h" #include "Engine/LevelStreaming.h" #include "Engine/ShadowMapTexture2D.h" #include "Engine/Engine.h" #include "EngineGlobals.h" #include "UnrealEngine.h" IMPLEMENT_UNIFORM_BUFFER_STRUCT(FLandscapeUniformShaderParameters, TEXT("LandscapeParameters")); #define LANDSCAPE_LOD_DISTANCE_FACTOR 2.f #define LANDSCAPE_MAX_COMPONENT_SIZE 255 #define LANDSCAPE_LOD_SQUARE_ROOT_FACTOR 1.5f /*------------------------------------------------------------------------------ Forsyth algorithm for cache optimizing index buffers. ------------------------------------------------------------------------------*/ // Forsyth algorithm to optimize post-transformed vertex cache namespace { // code for computing vertex score was taken, as much as possible // directly from the original publication. float ComputeVertexCacheScore(int32 CachePosition, uint32 VertexCacheSize) { const float FindVertexScoreCacheDecayPower = 1.5f; const float FindVertexScoreLastTriScore = 0.75f; float Score = 0.0f; if (CachePosition < 0) { // Vertex is not in FIFO cache - no score. } else { if (CachePosition < 3) { // This vertex was used in the last triangle, // so it has a fixed score, whichever of the three // it's in. Otherwise, you can get very different // answers depending on whether you add // the triangle 1,2,3 or 3,1,2 - which is silly. Score = FindVertexScoreLastTriScore; } else { check(CachePosition < (int32)VertexCacheSize); // Points for being high in the cache. const float Scaler = 1.0f / (VertexCacheSize - 3); Score = 1.0f - (CachePosition - 3) * Scaler; Score = FMath::Pow(Score, FindVertexScoreCacheDecayPower); } } return Score; } float ComputeVertexValenceScore(uint32 numActiveFaces) { const float FindVertexScoreValenceBoostScale = 2.0f; const float FindVertexScoreValenceBoostPower = 0.5f; float Score = 0.f; // Bonus points for having a low number of tris still to // use the vert, so we get rid of lone verts quickly. float ValenceBoost = FMath::Pow(float(numActiveFaces), -FindVertexScoreValenceBoostPower); Score += FindVertexScoreValenceBoostScale * ValenceBoost; return Score; } const uint32 MaxVertexCacheSize = 64; const uint32 MaxPrecomputedVertexValenceScores = 64; float VertexCacheScores[MaxVertexCacheSize + 1][MaxVertexCacheSize]; float VertexValenceScores[MaxPrecomputedVertexValenceScores]; bool bVertexScoresComputed = false; //ComputeVertexScores(); bool ComputeVertexScores() { for (uint32 CacheSize = 0; CacheSize <= MaxVertexCacheSize; ++CacheSize) { for (uint32 CachePos = 0; CachePos < CacheSize; ++CachePos) { VertexCacheScores[CacheSize][CachePos] = ComputeVertexCacheScore(CachePos, CacheSize); } } for (uint32 Valence = 0; Valence < MaxPrecomputedVertexValenceScores; ++Valence) { VertexValenceScores[Valence] = ComputeVertexValenceScore(Valence); } return true; } inline float FindVertexCacheScore(uint32 CachePosition, uint32 MaxSizeVertexCache) { return VertexCacheScores[MaxSizeVertexCache][CachePosition]; } inline float FindVertexValenceScore(uint32 NumActiveTris) { return VertexValenceScores[NumActiveTris]; } float FindVertexScore(uint32 NumActiveFaces, uint32 CachePosition, uint32 VertexCacheSize) { check(bVertexScoresComputed); if (NumActiveFaces == 0) { // No tri needs this vertex! return -1.0f; } float Score = 0.f; if (CachePosition < VertexCacheSize) { Score += VertexCacheScores[VertexCacheSize][CachePosition]; } if (NumActiveFaces < MaxPrecomputedVertexValenceScores) { Score += VertexValenceScores[NumActiveFaces]; } else { Score += ComputeVertexValenceScore(NumActiveFaces); } return Score; } struct OptimizeVertexData { float Score; uint32 ActiveFaceListStart; uint32 ActiveFaceListSize; uint32 CachePos0; uint32 CachePos1; OptimizeVertexData() : Score(0.f), ActiveFaceListStart(0), ActiveFaceListSize(0), CachePos0(0), CachePos1(0) { } }; //----------------------------------------------------------------------------- // OptimizeFaces //----------------------------------------------------------------------------- // Parameters: // InIndexList // input index list // OutIndexList // a pointer to a preallocated buffer the same size as indexList to // hold the optimized index list // LRUCacheSize // the size of the simulated post-transform cache (max:64) //----------------------------------------------------------------------------- template void OptimizeFaces(const TArray& InIndexList, TArray& OutIndexList, uint16 LRUCacheSize) { uint32 VertexCount = 0; const uint32 IndexCount = InIndexList.Num(); // compute face count per vertex for (uint32 i = 0; i < IndexCount; ++i) { uint32 Index = InIndexList[i]; VertexCount = FMath::Max(Index, VertexCount); } VertexCount++; TArray VertexDataList; VertexDataList.Empty(VertexCount); for (uint32 i = 0; i < VertexCount; i++) { VertexDataList.Add(OptimizeVertexData()); } OutIndexList.Empty(IndexCount); OutIndexList.AddZeroed(IndexCount); // compute face count per vertex for (uint32 i = 0; i < IndexCount; ++i) { uint32 Index = InIndexList[i]; OptimizeVertexData& VertexData = VertexDataList[Index]; VertexData.ActiveFaceListSize++; } TArray ActiveFaceList; const uint32 EvictedCacheIndex = TNumericLimits::Max(); { // allocate face list per vertex uint32 CurActiveFaceListPos = 0; for (uint32 i = 0; i < VertexCount; ++i) { OptimizeVertexData& VertexData = VertexDataList[i]; VertexData.CachePos0 = EvictedCacheIndex; VertexData.CachePos1 = EvictedCacheIndex; VertexData.ActiveFaceListStart = CurActiveFaceListPos; CurActiveFaceListPos += VertexData.ActiveFaceListSize; VertexData.Score = FindVertexScore(VertexData.ActiveFaceListSize, VertexData.CachePos0, LRUCacheSize); VertexData.ActiveFaceListSize = 0; } ActiveFaceList.Empty(CurActiveFaceListPos); ActiveFaceList.AddZeroed(CurActiveFaceListPos); } // fill out face list per vertex for (uint32 i = 0; i < IndexCount; i += 3) { for (uint32 j = 0; j < 3; ++j) { uint32 Index = InIndexList[i + j]; OptimizeVertexData& VertexData = VertexDataList[Index]; ActiveFaceList[VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize] = i; VertexData.ActiveFaceListSize++; } } TArray ProcessedFaceList; ProcessedFaceList.Empty(IndexCount); ProcessedFaceList.AddZeroed(IndexCount); uint32 VertexCacheBuffer[(MaxVertexCacheSize + 3) * 2]; uint32* Cache0 = VertexCacheBuffer; uint32* Cache1 = VertexCacheBuffer + (MaxVertexCacheSize + 3); uint32 EntriesInCache0 = 0; uint32 BestFace = 0; float BestScore = -1.f; const float MaxValenceScore = FindVertexScore(1, EvictedCacheIndex, LRUCacheSize) * 3.f; for (uint32 i = 0; i < IndexCount; i += 3) { if (BestScore < 0.f) { // no verts in the cache are used by any unprocessed faces so // search all unprocessed faces for a new starting point for (uint32 j = 0; j < IndexCount; j += 3) { if (ProcessedFaceList[j] == 0) { uint32 Face = j; float FaceScore = 0.f; for (uint32 k = 0; k < 3; ++k) { uint32 Index = InIndexList[Face + k]; OptimizeVertexData& VertexData = VertexDataList[Index]; check(VertexData.ActiveFaceListSize > 0); check(VertexData.CachePos0 >= LRUCacheSize); FaceScore += VertexData.Score; } if (FaceScore > BestScore) { BestScore = FaceScore; BestFace = Face; check(BestScore <= MaxValenceScore); if (BestScore >= MaxValenceScore) { break; } } } } check(BestScore >= 0.f); } ProcessedFaceList[BestFace] = 1; uint32 EntriesInCache1 = 0; // add bestFace to LRU cache and to newIndexList for (uint32 V = 0; V < 3; ++V) { INDEX_TYPE Index = InIndexList[BestFace + V]; OutIndexList[i + V] = Index; OptimizeVertexData& VertexData = VertexDataList[Index]; if (VertexData.CachePos1 >= EntriesInCache1) { VertexData.CachePos1 = EntriesInCache1; Cache1[EntriesInCache1++] = Index; if (VertexData.ActiveFaceListSize == 1) { --VertexData.ActiveFaceListSize; continue; } } check(VertexData.ActiveFaceListSize > 0); uint32 FindIndex; for (FindIndex = VertexData.ActiveFaceListStart; FindIndex < VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize; FindIndex++) { if (ActiveFaceList[FindIndex] == BestFace) { break; } } check(FindIndex != VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize); if (FindIndex != VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize - 1) { uint32 SwapTemp = ActiveFaceList[FindIndex]; ActiveFaceList[FindIndex] = ActiveFaceList[VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize - 1]; ActiveFaceList[VertexData.ActiveFaceListStart + VertexData.ActiveFaceListSize - 1] = SwapTemp; } --VertexData.ActiveFaceListSize; VertexData.Score = FindVertexScore(VertexData.ActiveFaceListSize, VertexData.CachePos1, LRUCacheSize); } // move the rest of the old verts in the cache down and compute their new scores for (uint32 C0 = 0; C0 < EntriesInCache0; ++C0) { uint32 Index = Cache0[C0]; OptimizeVertexData& VertexData = VertexDataList[Index]; if (VertexData.CachePos1 >= EntriesInCache1) { VertexData.CachePos1 = EntriesInCache1; Cache1[EntriesInCache1++] = Index; VertexData.Score = FindVertexScore(VertexData.ActiveFaceListSize, VertexData.CachePos1, LRUCacheSize); } } // find the best scoring triangle in the current cache (including up to 3 that were just evicted) BestScore = -1.f; for (uint32 C1 = 0; C1 < EntriesInCache1; ++C1) { uint32 Index = Cache1[C1]; OptimizeVertexData& VertexData = VertexDataList[Index]; VertexData.CachePos0 = VertexData.CachePos1; VertexData.CachePos1 = EvictedCacheIndex; for (uint32 j = 0; j < VertexData.ActiveFaceListSize; ++j) { uint32 Face = ActiveFaceList[VertexData.ActiveFaceListStart + j]; float FaceScore = 0.f; for (uint32 V = 0; V < 3; V++) { uint32 FaceIndex = InIndexList[Face + V]; OptimizeVertexData& FaceVertexData = VertexDataList[FaceIndex]; FaceScore += FaceVertexData.Score; } if (FaceScore > BestScore) { BestScore = FaceScore; BestFace = Face; } } } uint32* SwapTemp = Cache0; Cache0 = Cache1; Cache1 = SwapTemp; EntriesInCache0 = FMath::Min(EntriesInCache1, (uint32)LRUCacheSize); } } } // namespace struct FLandscapeDebugOptions { FLandscapeDebugOptions() : bShowPatches(false) , bDisableStatic(false) , bDisableCombine(false) , PatchesConsoleCommand( TEXT("Landscape.Patches"), TEXT("Show/hide Landscape patches"), FConsoleCommandDelegate::CreateRaw(this, &FLandscapeDebugOptions::Patches)) , StaticConsoleCommand( TEXT("Landscape.Static"), TEXT("Enable/disable Landscape static drawlists"), FConsoleCommandDelegate::CreateRaw(this, &FLandscapeDebugOptions::Static)) , CombineConsoleCommand( TEXT("Landscape.Combine"), TEXT("Enable/disable Landscape component combining"), FConsoleCommandDelegate::CreateRaw(this, &FLandscapeDebugOptions::Combine)) { } bool bShowPatches; bool bDisableStatic; bool bDisableCombine; private: FAutoConsoleCommand PatchesConsoleCommand; FAutoConsoleCommand StaticConsoleCommand; FAutoConsoleCommand CombineConsoleCommand; void Patches() { bShowPatches = !bShowPatches; UE_LOG(LogLandscape, Display, TEXT("Landscape.Patches: %s"), bShowPatches ? TEXT("Show") : TEXT("Hide")); } void Static() { bDisableStatic = !bDisableStatic; UE_LOG(LogLandscape, Display, TEXT("Landscape.Static: %s"), bDisableStatic ? TEXT("Disabled") : TEXT("Enabled")); } void Combine() { bDisableCombine = !bDisableCombine; UE_LOG(LogLandscape, Display, TEXT("Landscape.Combine: %s"), bDisableCombine ? TEXT("Disabled") : TEXT("Enabled")); } }; FLandscapeDebugOptions GLandscapeDebugOptions; #if WITH_EDITOR LANDSCAPE_API bool GLandscapeEditModeActive = false; LANDSCAPE_API ELandscapeViewMode::Type GLandscapeViewMode = ELandscapeViewMode::Normal; LANDSCAPE_API int32 GLandscapeEditRenderMode = ELandscapeEditRenderMode::None; LANDSCAPE_API int32 GLandscapePreviewMeshRenderMode = 0; UMaterial* GLayerDebugColorMaterial = nullptr; UMaterialInstanceConstant* GSelectionColorMaterial = nullptr; UMaterialInstanceConstant* GSelectionRegionMaterial = nullptr; UMaterialInstanceConstant* GMaskRegionMaterial = nullptr; UTexture2D* GLandscapeBlackTexture = nullptr; // Game thread update void FLandscapeEditToolRenderData::Update(UMaterialInterface* InToolMaterial) { ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( UpdateEditToolRenderData, FLandscapeEditToolRenderData*, LandscapeEditToolRenderData, this, UMaterialInterface*, NewToolMaterial, InToolMaterial, { LandscapeEditToolRenderData->ToolMaterial = NewToolMaterial; }); } void FLandscapeEditToolRenderData::UpdateGizmo(UMaterialInterface* InGizmoMaterial) { ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( UpdateEditToolRenderData, FLandscapeEditToolRenderData*, LandscapeEditToolRenderData, this, UMaterialInterface*, NewGizmoMaterial, InGizmoMaterial, { LandscapeEditToolRenderData->GizmoMaterial = NewGizmoMaterial; }); } // Allows game thread to queue the deletion by the render thread void FLandscapeEditToolRenderData::Cleanup() { ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER( CleanupEditToolRenderData, FLandscapeEditToolRenderData*, LandscapeEditToolRenderData, this, { delete LandscapeEditToolRenderData; } ); } void FLandscapeEditToolRenderData::UpdateDebugColorMaterial() { if (!LandscapeComponent) { return; } // Debug Color Rendering Material.... DebugChannelR = INDEX_NONE, DebugChannelG = INDEX_NONE, DebugChannelB = INDEX_NONE; LandscapeComponent->GetLayerDebugColorKey(DebugChannelR, DebugChannelG, DebugChannelB); } void FLandscapeEditToolRenderData::UpdateSelectionMaterial(int32 InSelectedType) { if (!LandscapeComponent) { return; } // Check selection if (SelectedType != InSelectedType && (SelectedType & ST_REGION) && !(InSelectedType & ST_REGION)) { // Clear Select textures... if (DataTexture) { FLandscapeEditDataInterface LandscapeEdit(LandscapeComponent->GetLandscapeInfo()); LandscapeEdit.ZeroTexture(DataTexture); } } ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( UpdateSelectionMaterial, FLandscapeEditToolRenderData*, LandscapeEditToolRenderData, this, int32, InSelectedType, InSelectedType, { LandscapeEditToolRenderData->SelectedType = InSelectedType; }); } #endif // // FLandscapeComponentSceneProxy // TMapFLandscapeComponentSceneProxy::SharedBuffersMap; TMapFLandscapeComponentSceneProxy::SharedAdjacencyIndexBufferMap; TMap > FLandscapeNeighborInfo::SharedSceneProxyMap; FLandscapeComponentSceneProxy::FLandscapeComponentSceneProxy(ULandscapeComponent* InComponent, FLandscapeEditToolRenderData* InEditToolRenderData) : FPrimitiveSceneProxy(InComponent) , FLandscapeNeighborInfo(InComponent->GetWorld(), InComponent->GetLandscapeProxy()->GetLandscapeGuid(), InComponent->GetSectionBase() / InComponent->ComponentSizeQuads, InComponent->HeightmapTexture, InComponent->ForcedLOD, InComponent->LODBias) , MaxLOD(FMath::CeilLogTwo(InComponent->SubsectionSizeQuads + 1) - 1) , FirstLOD(0) , LastLOD(FMath::CeilLogTwo(InComponent->SubsectionSizeQuads + 1) - 1) , NumSubsections(InComponent->NumSubsections) , SubsectionSizeQuads(InComponent->SubsectionSizeQuads) , SubsectionSizeVerts(InComponent->SubsectionSizeQuads + 1) , ComponentSizeQuads(InComponent->ComponentSizeQuads) , ComponentSizeVerts(InComponent->ComponentSizeQuads + 1) , StaticLightingLOD(InComponent->GetLandscapeProxy()->StaticLightingLOD) , SectionBase(InComponent->GetSectionBase()) , WeightmapScaleBias(InComponent->WeightmapScaleBias) , WeightmapSubsectionOffset(InComponent->WeightmapSubsectionOffset) , WeightmapTextures(InComponent->WeightmapTextures) , NumWeightmapLayerAllocations(InComponent->WeightmapLayerAllocations.Num()) , NormalmapTexture(InComponent->HeightmapTexture) , BaseColorForGITexture(InComponent->GIBakedBaseColorTexture) , HeightmapScaleBias(InComponent->HeightmapScaleBias) , XYOffsetmapTexture(InComponent->XYOffsetmapTexture) , SharedBuffersKey(0) , SharedBuffers(nullptr) , VertexFactory(nullptr) , MaterialInterface(InComponent->MaterialInstance) , EditToolRenderData(InEditToolRenderData) , ComponentLightInfo(nullptr) , LandscapeComponent(InComponent) , LODFalloff(InComponent->GetLandscapeProxy()->LODFalloff) { if (!IsComponentLevelVisible()) { bNeedsLevelAddedToWorldNotification = true; } LevelColor = FLinearColor(1.f, 1.f, 1.f); const auto FeatureLevel = GetScene().GetFeatureLevel(); if (FeatureLevel <= ERHIFeatureLevel::ES3_1) { HeightmapTexture = nullptr; HeightmapSubsectionOffsetU = 0; HeightmapSubsectionOffsetV = 0; } else { HeightmapSubsectionOffsetU = ((float)(InComponent->SubsectionSizeQuads + 1) / (float)HeightmapTexture->GetSizeX()); HeightmapSubsectionOffsetV = ((float)(InComponent->SubsectionSizeQuads + 1) / (float)HeightmapTexture->GetSizeY()); } LODBias = FMath::Clamp(LODBias, -MaxLOD, MaxLOD); if (InComponent->GetLandscapeProxy()->MaxLODLevel >= 0) { MaxLOD = FMath::Min(MaxLOD, InComponent->GetLandscapeProxy()->MaxLODLevel); } FirstLOD = (ForcedLOD >= 0) ? FMath::Min(ForcedLOD, MaxLOD) : FMath::Max(LODBias, 0); LastLOD = (ForcedLOD >= 0) ? FirstLOD : MaxLOD; // we always need to go to MaxLOD regardless of LODBias as we could need the lowest LODs due to streaming. float LODDistanceFactor; switch (LODFalloff) { case ELandscapeLODFalloff::SquareRoot: LODDistanceFactor = FMath::Square(FMath::Min(LANDSCAPE_LOD_SQUARE_ROOT_FACTOR * InComponent->GetLandscapeProxy()->LODDistanceFactor, MAX_LANDSCAPE_LOD_DISTANCE_FACTOR)); break; case ELandscapeLODFalloff::Linear: default: LODDistanceFactor = InComponent->GetLandscapeProxy()->LODDistanceFactor; break; } LODDistance = FMath::Sqrt(2.f * FMath::Square((float)SubsectionSizeQuads)) * LANDSCAPE_LOD_DISTANCE_FACTOR / LODDistanceFactor; // vary in 0...1 DistDiff = -FMath::Sqrt(2.f * FMath::Square(0.5f*(float)SubsectionSizeQuads)); if (InComponent->StaticLightingResolution > 0.f) { StaticLightingResolution = InComponent->StaticLightingResolution; } else { StaticLightingResolution = InComponent->GetLandscapeProxy()->StaticLightingResolution; } ComponentLightInfo = MakeUnique(InComponent); check(ComponentLightInfo); const bool bHasStaticLighting = InComponent->LightMap != nullptr || InComponent->ShadowMap != nullptr; // Check material usage if (MaterialInterface == nullptr || !MaterialInterface->CheckMaterialUsage(MATUSAGE_Landscape) || (bHasStaticLighting && !MaterialInterface->CheckMaterialUsage(MATUSAGE_StaticLighting))) { MaterialInterface = UMaterial::GetDefaultMaterial(MD_Surface); } MaterialRelevance = MaterialInterface->GetRelevance(FeatureLevel); #if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) || (UE_BUILD_SHIPPING && WITH_EDITOR) if (GIsEditor) { ALandscapeProxy* Proxy = InComponent->GetLandscapeProxy(); // Try to find a color for level coloration. if (Proxy) { ULevel* Level = Proxy->GetLevel(); ULevelStreaming* LevelStreaming = FLevelUtils::FindStreamingLevel(Level); if (LevelStreaming) { LevelColor = LevelStreaming->LevelColor; } } } #endif bRequiresAdjacencyInformation = RequiresAdjacencyInformation(MaterialInterface, XYOffsetmapTexture == nullptr ? &FLandscapeVertexFactory::StaticType : &FLandscapeXYOffsetVertexFactory::StaticType, InComponent->GetWorld()->FeatureLevel); SharedBuffersKey = (SubsectionSizeQuads & 0xffff) | ((NumSubsections & 0xf) << 16) | (FeatureLevel <= ERHIFeatureLevel::ES3_1 ? 0 : 1 << 20) | (XYOffsetmapTexture == nullptr ? 0 : 1 << 31); bSupportsHeightfieldRepresentation = true; } void FLandscapeComponentSceneProxy::CreateRenderThreadResources() { check(HeightmapTexture != nullptr); if (IsComponentLevelVisible()) { RegisterNeighbors(); } auto FeatureLevel = GetScene().GetFeatureLevel(); SharedBuffers = FLandscapeComponentSceneProxy::SharedBuffersMap.FindRef(SharedBuffersKey); if (SharedBuffers == nullptr) { SharedBuffers = new FLandscapeSharedBuffers(SharedBuffersKey, SubsectionSizeQuads, NumSubsections, FeatureLevel, bRequiresAdjacencyInformation); FLandscapeComponentSceneProxy::SharedBuffersMap.Add(SharedBuffersKey, SharedBuffers); if (!XYOffsetmapTexture) { FLandscapeVertexFactory* LandscapeVertexFactory = new FLandscapeVertexFactory(); LandscapeVertexFactory->Data.PositionComponent = FVertexStreamComponent(SharedBuffers->VertexBuffer, 0, sizeof(FLandscapeVertex), VET_Float4); LandscapeVertexFactory->InitResource(); SharedBuffers->VertexFactory = LandscapeVertexFactory; } else { FLandscapeXYOffsetVertexFactory* LandscapeXYOffsetVertexFactory = new FLandscapeXYOffsetVertexFactory(); LandscapeXYOffsetVertexFactory->Data.PositionComponent = FVertexStreamComponent(SharedBuffers->VertexBuffer, 0, sizeof(FLandscapeVertex), VET_Float4); LandscapeXYOffsetVertexFactory->InitResource(); SharedBuffers->VertexFactory = LandscapeXYOffsetVertexFactory; } } SharedBuffers->AddRef(); if (bRequiresAdjacencyInformation) { if (SharedBuffers->AdjacencyIndexBuffers == nullptr) { ensure(SharedBuffers->NumIndexBuffers > 0); if (SharedBuffers->IndexBuffers[0]) { // Recreate Index Buffers, this case happens only there are Landscape Components using different material (one uses tessellation, other don't use it) if (SharedBuffers->bUse32BitIndices && !((FRawStaticIndexBuffer16or32*)SharedBuffers->IndexBuffers[0])->Num()) { SharedBuffers->CreateIndexBuffers(FeatureLevel, bRequiresAdjacencyInformation); } else if (!((FRawStaticIndexBuffer16or32*)SharedBuffers->IndexBuffers[0])->Num()) { SharedBuffers->CreateIndexBuffers(FeatureLevel, bRequiresAdjacencyInformation); } } SharedBuffers->AdjacencyIndexBuffers = new FLandscapeSharedAdjacencyIndexBuffer(SharedBuffers); FLandscapeComponentSceneProxy::SharedAdjacencyIndexBufferMap.Add(SharedBuffersKey, SharedBuffers->AdjacencyIndexBuffers); } SharedBuffers->AdjacencyIndexBuffers->AddRef(); // Delayed Initialize for IndexBuffers for (int i = 0; i < SharedBuffers->NumIndexBuffers; i++) { SharedBuffers->IndexBuffers[i]->InitResource(); } } // Assign vertex factory VertexFactory = SharedBuffers->VertexFactory; // Assign LandscapeUniformShaderParameters LandscapeUniformShaderParameters.InitResource(); } void FLandscapeComponentSceneProxy::OnLevelAddedToWorld() { RegisterNeighbors(); } FLandscapeComponentSceneProxy::~FLandscapeComponentSceneProxy() { UnregisterNeighbors(); // Free the subsection uniform buffer LandscapeUniformShaderParameters.ReleaseResource(); if (SharedBuffers) { check(SharedBuffers == FLandscapeComponentSceneProxy::SharedBuffersMap.FindRef(SharedBuffersKey)); if (SharedBuffers->Release() == 0) { FLandscapeComponentSceneProxy::SharedBuffersMap.Remove(SharedBuffersKey); } SharedBuffers = nullptr; } } int32 GAllowLandscapeShadows = 1; static FAutoConsoleVariableRef CVarAllowLandscapeShadows( TEXT("r.AllowLandscapeShadows"), GAllowLandscapeShadows, TEXT("Allow Landscape Shadows") ); bool FLandscapeComponentSceneProxy::CanBeOccluded() const { return !MaterialRelevance.bDisableDepthTest; } FPrimitiveViewRelevance FLandscapeComponentSceneProxy::GetViewRelevance(const FSceneView* View) { FPrimitiveViewRelevance Result; Result.bDrawRelevance = IsShown(View) && View->Family->EngineShowFlags.Landscape; auto FeatureLevel = View->GetFeatureLevel(); #if WITH_EDITOR if (!GLandscapeEditModeActive) { // No tools to render, just use the cached material relevance. #endif MaterialRelevance.SetPrimitiveViewRelevance(Result); #if WITH_EDITOR } else { // Also add the tool material(s)'s relevance to the MaterialRelevance FMaterialRelevance ToolRelevance = MaterialRelevance; // Tool brushes and Gizmo if (EditToolRenderData) { if (EditToolRenderData->ToolMaterial) { Result.bDynamicRelevance = true; ToolRelevance |= EditToolRenderData->ToolMaterial->GetRelevance_Concurrent(FeatureLevel); } if (EditToolRenderData->GizmoMaterial) { Result.bDynamicRelevance = true; ToolRelevance |= EditToolRenderData->GizmoMaterial->GetRelevance_Concurrent(FeatureLevel); } } // Region selection if (EditToolRenderData && EditToolRenderData->SelectedType) { if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::SelectRegion) && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_REGION) && !(GLandscapeEditRenderMode & ELandscapeEditRenderMode::Mask) && GSelectionRegionMaterial) { Result.bDynamicRelevance = true; ToolRelevance |= GSelectionRegionMaterial->GetRelevance_Concurrent(FeatureLevel); } if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::SelectComponent) && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_COMPONENT) && GSelectionColorMaterial) { Result.bDynamicRelevance = true; ToolRelevance |= GSelectionColorMaterial->GetRelevance_Concurrent(FeatureLevel); } } // Mask if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::Mask) && GMaskRegionMaterial != nullptr && ((EditToolRenderData && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_REGION)) || (!(GLandscapeEditRenderMode & ELandscapeEditRenderMode::InvertedMask)))) { Result.bDynamicRelevance = true; ToolRelevance |= GMaskRegionMaterial->GetRelevance_Concurrent(FeatureLevel); } ToolRelevance.SetPrimitiveViewRelevance(Result); } // Various visualizations need to render using dynamic relevance if ((View->Family->EngineShowFlags.Bounds && IsSelected()) || GLandscapeDebugOptions.bShowPatches) { Result.bDynamicRelevance = true; } #endif // Use the dynamic path for rendering landscape components pass only for Rich Views or if the static path is disabled for debug. if (IsRichView(*View->Family) || GLandscapeDebugOptions.bDisableStatic || View->Family->EngineShowFlags.Wireframe || #if WITH_EDITOR (IsSelected() && !GLandscapeEditModeActive) || GLandscapeViewMode != ELandscapeViewMode::Normal #else IsSelected() #endif ) { Result.bDynamicRelevance = true; } else { Result.bStaticRelevance = true; } Result.bShadowRelevance = (GAllowLandscapeShadows > 0) && IsShadowCast(View); return Result; } /** * Determines the relevance of this primitive's elements to the given light. * @param LightSceneProxy The light to determine relevance for * @param bDynamic (output) The light is dynamic for this primitive * @param bRelevant (output) The light is relevant for this primitive * @param bLightMapped (output) The light is light mapped for this primitive */ void FLandscapeComponentSceneProxy::GetLightRelevance(const FLightSceneProxy* LightSceneProxy, bool& bDynamic, bool& bRelevant, bool& bLightMapped, bool& bShadowMapped) const { // Attach the light to the primitive's static meshes. bDynamic = true; bRelevant = false; bLightMapped = true; bShadowMapped = true; if (ComponentLightInfo) { ELightInteractionType InteractionType = ComponentLightInfo->GetInteraction(LightSceneProxy).GetType(); if (InteractionType != LIT_CachedIrrelevant) { bRelevant = true; } if (InteractionType != LIT_CachedLightMap && InteractionType != LIT_CachedIrrelevant) { bLightMapped = false; } if (InteractionType != LIT_Dynamic) { bDynamic = false; } if (InteractionType != LIT_CachedSignedDistanceFieldShadowMap2D) { bShadowMapped = false; } } else { bRelevant = true; bLightMapped = false; } } FLightInteraction FLandscapeComponentSceneProxy::FLandscapeLCI::GetInteraction(const class FLightSceneProxy* LightSceneProxy) const { // Check if the light has static lighting or shadowing. if (LightSceneProxy->HasStaticShadowing()) { const FGuid LightGuid = LightSceneProxy->GetLightGuid(); if (LightMap && LightMap->ContainsLight(LightGuid)) { return FLightInteraction::LightMap(); } if (ShadowMap && ShadowMap->ContainsLight(LightGuid)) { return FLightInteraction::ShadowMap2D(); } if (IrrelevantLights.Contains(LightGuid)) { return FLightInteraction::Irrelevant(); } } // Use dynamic lighting if the light doesn't have static lighting. return FLightInteraction::Dynamic(); } #if WITH_EDITOR namespace DebugColorMask { const FLinearColor Masks[5] = { FLinearColor(1.f, 0.f, 0.f, 0.f), FLinearColor(0.f, 1.f, 0.f, 0.f), FLinearColor(0.f, 0.f, 1.f, 0.f), FLinearColor(0.f, 0.f, 0.f, 1.f), FLinearColor(0.f, 0.f, 0.f, 0.f) }; }; #endif void FLandscapeComponentSceneProxy::OnTransformChanged() { // Set Lightmap ScaleBias int32 PatchExpandCountX = 1; int32 PatchExpandCountY = 1; int32 DesiredSize = 1; const float LightMapRatio = ::GetTerrainExpandPatchCount(StaticLightingResolution, PatchExpandCountX, PatchExpandCountY, ComponentSizeQuads, (NumSubsections * (SubsectionSizeQuads + 1)), DesiredSize, StaticLightingLOD); const float LightmapLODScaleX = LightMapRatio / ((ComponentSizeVerts >> StaticLightingLOD) + 2 * PatchExpandCountX); const float LightmapLODScaleY = LightMapRatio / ((ComponentSizeVerts >> StaticLightingLOD) + 2 * PatchExpandCountY); const float LightmapBiasX = PatchExpandCountX * LightmapLODScaleX; const float LightmapBiasY = PatchExpandCountY * LightmapLODScaleY; const float LightmapScaleX = LightmapLODScaleX * (float)((ComponentSizeVerts >> StaticLightingLOD) - 1) / ComponentSizeQuads; const float LightmapScaleY = LightmapLODScaleY * (float)((ComponentSizeVerts >> StaticLightingLOD) - 1) / ComponentSizeQuads; const float LightmapExtendFactorX = (float)SubsectionSizeQuads * LightmapScaleX; const float LightmapExtendFactorY = (float)SubsectionSizeQuads * LightmapScaleY; // cache component's WorldToLocal FMatrix LtoW = GetLocalToWorld(); WorldToLocal = LtoW.InverseFast(); // cache component's LocalToWorldNoScaling LocalToWorldNoScaling = LtoW; LocalToWorldNoScaling.RemoveScaling(); // Set FLandscapeUniformVSParameters for this subsection FLandscapeUniformShaderParameters LandscapeParams; LandscapeParams.HeightmapUVScaleBias = HeightmapScaleBias; LandscapeParams.WeightmapUVScaleBias = WeightmapScaleBias; LandscapeParams.LocalToWorldNoScaling = LocalToWorldNoScaling; LandscapeParams.LandscapeLightmapScaleBias = FVector4( LightmapScaleX, LightmapScaleY, LightmapBiasY, LightmapBiasX); LandscapeParams.SubsectionSizeVertsLayerUVPan = FVector4( SubsectionSizeVerts, 1.f / (float)SubsectionSizeQuads, SectionBase.X, SectionBase.Y ); LandscapeParams.SubsectionOffsetParams = FVector4( HeightmapSubsectionOffsetU, HeightmapSubsectionOffsetV, WeightmapSubsectionOffset, SubsectionSizeQuads ); LandscapeParams.LightmapSubsectionOffsetParams = FVector4( LightmapExtendFactorX, LightmapExtendFactorY, 0, 0 ); LandscapeUniformShaderParameters.SetContents(LandscapeParams); } namespace { inline bool RequiresAdjacencyInformation(FMaterialRenderProxy* MaterialRenderProxy, ERHIFeatureLevel::Type InFeatureLevel) // Assumes VertexFactory supports tessellation, and rendering thread with this function { if (RHISupportsTessellation(GShaderPlatformForFeatureLevel[InFeatureLevel]) && MaterialRenderProxy) { check(IsInRenderingThread()); const FMaterial* MaterialResource = MaterialRenderProxy->GetMaterial(InFeatureLevel); check(MaterialResource); EMaterialTessellationMode TessellationMode = MaterialResource->GetTessellationMode(); bool bEnableCrackFreeDisplacement = MaterialResource->IsCrackFreeDisplacementEnabled(); return TessellationMode == MTM_PNTriangles || (TessellationMode == MTM_FlatTessellation && bEnableCrackFreeDisplacement); } else { return false; } } }; /** * Draw the scene proxy as a dynamic element * * @param PDI - draw interface to render to * @param View - current view */ void FLandscapeComponentSceneProxy::DrawStaticElements(FStaticPrimitiveDrawInterface* PDI) { int32 NumBatches = (1 + LastLOD - FirstLOD) * (FMath::Square(NumSubsections) + 1); if (SharedBuffers->GrassIndexBuffer) { NumBatches++; } StaticBatchParamArray.Empty(NumBatches); FMeshBatch MeshBatch; MeshBatch.Elements.Empty(NumBatches); FMaterialRenderProxy* RenderProxy = MaterialInterface->GetRenderProxy(false); // Could be different from bRequiresAdjacencyInformation during shader compilation bool bCurrentRequiresAdjacencyInformation = RequiresAdjacencyInformation(RenderProxy, GetScene().GetFeatureLevel()); if (bCurrentRequiresAdjacencyInformation) { check(SharedBuffers->AdjacencyIndexBuffers); } MeshBatch.VertexFactory = VertexFactory; MeshBatch.MaterialRenderProxy = RenderProxy; MeshBatch.LCI = ComponentLightInfo.Get(); MeshBatch.ReverseCulling = IsLocalToWorldDeterminantNegative(); MeshBatch.CastShadow = true; MeshBatch.Type = bCurrentRequiresAdjacencyInformation ? PT_12_ControlPointPatchList : PT_TriangleList; MeshBatch.DepthPriorityGroup = SDPG_World; if (SharedBuffers->GrassIndexBuffer) { // Combined grass rendering batch element FMeshBatchElement* BatchElement = new(MeshBatch.Elements) FMeshBatchElement; FLandscapeBatchElementParams* BatchElementParams = new(StaticBatchParamArray)FLandscapeBatchElementParams; BatchElementParams->LocalToWorldNoScalingPtr = &LocalToWorldNoScaling; BatchElement->UserData = BatchElementParams; BatchElement->PrimitiveUniformBufferResource = &GetUniformBuffer(); BatchElementParams->LandscapeUniformShaderParametersResource = &LandscapeUniformShaderParameters; BatchElementParams->SceneProxy = this; BatchElementParams->SubX = -1; BatchElementParams->SubY = -1; BatchElementParams->CurrentLOD = 0; BatchElement->IndexBuffer = SharedBuffers->GrassIndexBuffer; BatchElement->NumPrimitives = (FMath::Square(NumSubsections) * FMath::Square(SubsectionSizeQuads) + 2 * NumSubsections * SubsectionSizeQuads + 1) * 2; BatchElement->FirstIndex = 0; BatchElement->MinVertexIndex = 0; BatchElement->MaxVertexIndex = SharedBuffers->NumVertices-1; } for (int32 LOD = FirstLOD; LOD <= LastLOD; LOD++) { int32 LodSubsectionSizeVerts = SubsectionSizeVerts >> LOD; if (ForcedLOD < 0 && NumSubsections > 1) { // Per-subsection batch elements for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { FMeshBatchElement* BatchElement = new(MeshBatch.Elements) FMeshBatchElement; FLandscapeBatchElementParams* BatchElementParams = new(StaticBatchParamArray)FLandscapeBatchElementParams; BatchElement->UserData = BatchElementParams; BatchElement->PrimitiveUniformBufferResource = &GetUniformBuffer(); BatchElementParams->LandscapeUniformShaderParametersResource = &LandscapeUniformShaderParameters; BatchElementParams->LocalToWorldNoScalingPtr = &LocalToWorldNoScaling; BatchElementParams->SceneProxy = this; BatchElementParams->SubX = SubX; BatchElementParams->SubY = SubY; BatchElementParams->CurrentLOD = LOD; uint32 NumPrimitives = FMath::Square((LodSubsectionSizeVerts - 1)) * 2; if (bCurrentRequiresAdjacencyInformation) { BatchElement->IndexBuffer = SharedBuffers->AdjacencyIndexBuffers->IndexBuffers[LOD]; BatchElement->FirstIndex = (SubX + SubY * NumSubsections) * NumPrimitives * 12; } else { BatchElement->IndexBuffer = SharedBuffers->IndexBuffers[LOD]; BatchElement->FirstIndex = (SubX + SubY * NumSubsections) * NumPrimitives * 3; } BatchElement->NumPrimitives = NumPrimitives; BatchElement->MinVertexIndex = SharedBuffers->IndexRanges[LOD].MinIndex[SubX][SubY]; BatchElement->MaxVertexIndex = SharedBuffers->IndexRanges[LOD].MaxIndex[SubX][SubY]; } } } // Combined batch element FMeshBatchElement* BatchElement = new(MeshBatch.Elements) FMeshBatchElement; FLandscapeBatchElementParams* BatchElementParams = new(StaticBatchParamArray)FLandscapeBatchElementParams; BatchElementParams->LocalToWorldNoScalingPtr = &LocalToWorldNoScaling; BatchElement->UserData = BatchElementParams; BatchElement->PrimitiveUniformBufferResource = &GetUniformBuffer(); BatchElementParams->LandscapeUniformShaderParametersResource = &LandscapeUniformShaderParameters; BatchElementParams->SceneProxy = this; BatchElementParams->SubX = -1; BatchElementParams->SubY = -1; BatchElementParams->CurrentLOD = LOD; BatchElement->IndexBuffer = bCurrentRequiresAdjacencyInformation ? SharedBuffers->AdjacencyIndexBuffers->IndexBuffers[LOD] : SharedBuffers->IndexBuffers[LOD]; BatchElement->NumPrimitives = FMath::Square((LodSubsectionSizeVerts - 1)) * FMath::Square(NumSubsections) * 2; BatchElement->FirstIndex = 0; BatchElement->MinVertexIndex = SharedBuffers->IndexRanges[LOD].MinIndexFull; BatchElement->MaxVertexIndex = SharedBuffers->IndexRanges[LOD].MaxIndexFull; } PDI->DrawMesh(MeshBatch, FLT_MAX); } uint64 FLandscapeVertexFactory::GetStaticBatchElementVisibility(const class FSceneView& View, const struct FMeshBatch* Batch) const { const FLandscapeComponentSceneProxy* SceneProxy = ((FLandscapeBatchElementParams*)Batch->Elements[0].UserData)->SceneProxy; return SceneProxy->GetStaticBatchElementVisibility(View, Batch); } uint64 FLandscapeComponentSceneProxy::GetStaticBatchElementVisibility(const class FSceneView& View, const struct FMeshBatch* Batch) const { uint64 BatchesToRenderMask = 0; int32 GrassBatchOffset = SharedBuffers->GrassIndexBuffer ? 1 : 0; SCOPE_CYCLE_COUNTER(STAT_LandscapeStaticDrawLODTime); if (ForcedLOD >= 0) { // When forcing LOD we only create one Batch Element (excluding the grass rendering batch) ensure(Batch->Elements.Num() - GrassBatchOffset == 1); int32 BatchElementIndex = GrassBatchOffset; BatchesToRenderMask |= (((uint64)1) << BatchElementIndex); INC_DWORD_STAT(STAT_LandscapeDrawCalls); INC_DWORD_STAT_BY(STAT_LandscapeTriangles, Batch->Elements[BatchElementIndex].NumPrimitives); } else { // camera position in local heightmap space FVector CameraLocalPos3D = WorldToLocal.TransformPosition(View.ViewMatrices.ViewOrigin); FVector2D CameraLocalPos(CameraLocalPos3D.X, CameraLocalPos3D.Y); int32 BatchesPerLOD = NumSubsections > 1 ? FMath::Square(NumSubsections) + 1 : 1; int32 CalculatedLods[LANDSCAPE_MAX_SUBSECTION_NUM][LANDSCAPE_MAX_SUBSECTION_NUM]; int32 CombinedLOD = -1; int32 bAllSameLOD = true; // Components with positive LODBias don't generate batch elements for unused LODs. int32 LODBiasOffset = FMath::Max(LODBias, 0); for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32 ThisSubsectionLOD = CalcLODForSubsection(View, SubX, SubY, CameraLocalPos); // check if all LODs are the same. if (ThisSubsectionLOD != CombinedLOD && CombinedLOD != -1) { bAllSameLOD = false; } CombinedLOD = ThisSubsectionLOD; CalculatedLods[SubX][SubY] = ThisSubsectionLOD; } } if (bAllSameLOD && NumSubsections > 1 && !GLandscapeDebugOptions.bDisableCombine) { // choose the combined batch element int32 BatchElementIndex = GrassBatchOffset + (CombinedLOD - LODBiasOffset + 1)*BatchesPerLOD - 1; if (ensure(Batch->Elements.IsValidIndex(BatchElementIndex))) { BatchesToRenderMask |= (((uint64)1) << BatchElementIndex); INC_DWORD_STAT(STAT_LandscapeDrawCalls); INC_DWORD_STAT_BY(STAT_LandscapeTriangles, Batch->Elements[BatchElementIndex].NumPrimitives); } } else { for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32 BatchElementIndex = GrassBatchOffset + (CalculatedLods[SubX][SubY] - LODBiasOffset) * BatchesPerLOD + SubY*NumSubsections + SubX; if (ensure(Batch->Elements.IsValidIndex(BatchElementIndex))) { BatchesToRenderMask |= (((uint64)1) << BatchElementIndex); INC_DWORD_STAT(STAT_LandscapeDrawCalls); INC_DWORD_STAT_BY(STAT_LandscapeTriangles, Batch->Elements[BatchElementIndex].NumPrimitives); } } } } } INC_DWORD_STAT(STAT_LandscapeComponents); return BatchesToRenderMask; } float FLandscapeComponentSceneProxy::CalcDesiredLOD(const class FSceneView& View, const FVector2D& CameraLocalPos, int32 SubX, int32 SubY) const { #if WITH_EDITOR if (View.Family->LandscapeLODOverride >= 0) { return FMath::Clamp(View.Family->LandscapeLODOverride, FirstLOD, LastLOD); } #endif // FLandscapeComponentSceneProxy::NumSubsections, SubsectionSizeQuads, MaxLOD, LODFalloff and LODDistance are the same for all components and so are safe to use in the neighbour LOD calculations // HeightmapTexture, LODBias, ForcedLOD are component-specific with neighbor lookup const bool bIsInThisComponent = (SubX >= 0 && SubX < NumSubsections && SubY >= 0 && SubY < NumSubsections); auto* SubsectionHeightmapTexture = HeightmapTexture; int8 SubsectionForcedLOD = ForcedLOD; int8 SubsectionLODBias = LODBias; if (SubX < 0) { SubsectionHeightmapTexture = Neighbors[1] ? Neighbors[1]->HeightmapTexture : nullptr; SubsectionForcedLOD = Neighbors[1] ? Neighbors[1]->ForcedLOD : -1; SubsectionLODBias = Neighbors[1] ? Neighbors[1]->LODBias : 0; } else if (SubX >= NumSubsections) { SubsectionHeightmapTexture = Neighbors[2] ? Neighbors[2]->HeightmapTexture : nullptr; SubsectionForcedLOD = Neighbors[2] ? Neighbors[2]->ForcedLOD : -1; SubsectionLODBias = Neighbors[2] ? Neighbors[2]->LODBias : 0; } else if (SubY < 0) { SubsectionHeightmapTexture = Neighbors[0] ? Neighbors[0]->HeightmapTexture : nullptr; SubsectionForcedLOD = Neighbors[0] ? Neighbors[0]->ForcedLOD : -1; SubsectionLODBias = Neighbors[0] ? Neighbors[0]->LODBias : 0; } else if (SubY >= NumSubsections) { SubsectionHeightmapTexture = Neighbors[3] ? Neighbors[3]->HeightmapTexture : nullptr; SubsectionForcedLOD = Neighbors[3] ? Neighbors[3]->ForcedLOD : -1; SubsectionLODBias = Neighbors[3] ? Neighbors[3]->LODBias : 0; } const int32 MinStreamedLOD = SubsectionHeightmapTexture ? FMath::Min(((FTexture2DResource*)SubsectionHeightmapTexture->Resource)->GetCurrentFirstMip(), FMath::CeilLogTwo(SubsectionSizeVerts) - 1) : 0; float fLOD = FLT_MAX; if (SubsectionForcedLOD >= 0) { fLOD = SubsectionForcedLOD; } else { if (View.IsPerspectiveProjection()) { FVector2D ComponentPosition(0.5f * (float)SubsectionSizeQuads, 0.5f * (float)SubsectionSizeQuads); FVector2D CurrentCameraLocalPos = CameraLocalPos - FVector2D(SubX * SubsectionSizeQuads, SubY * SubsectionSizeQuads); float ComponentDistance = FVector2D(CurrentCameraLocalPos - ComponentPosition).Size() + DistDiff; switch (LODFalloff) { case ELandscapeLODFalloff::SquareRoot: fLOD = FMath::Sqrt(FMath::Max(0.f, ComponentDistance / LODDistance)); break; default: case ELandscapeLODFalloff::Linear: fLOD = ComponentDistance / LODDistance; break; } } else { float Scale = 1.0f / (View.ViewRect.Width() * View.ViewMatrices.ProjMatrix.M[0][0]); // The "/ 5.0f" is totally arbitrary switch (LODFalloff) { case ELandscapeLODFalloff::SquareRoot: fLOD = FMath::Sqrt(Scale / 5.0f); break; default: case ELandscapeLODFalloff::Linear: fLOD = Scale / 5.0f; break; } } fLOD = FMath::Clamp(fLOD, SubsectionLODBias, FMath::Min(MaxLOD, MaxLOD + SubsectionLODBias)); } // ultimately due to texture streaming we sometimes need to go past MaxLOD fLOD = FMath::Max(fLOD, MinStreamedLOD); return fLOD; } int32 FLandscapeComponentSceneProxy::CalcLODForSubsection(const class FSceneView& View, int32 SubX, int32 SubY, const FVector2D& CameraLocalPos) const { return FMath::FloorToInt(CalcDesiredLOD(View, CameraLocalPos, SubX, SubY)); } void FLandscapeComponentSceneProxy::CalcLODParamsForSubsection(const class FSceneView& View, const FVector2D& CameraLocalPos, int32 SubX, int32 SubY, int32 BatchLOD, float& OutfLOD, FVector4& OutNeighborLODs) const { OutfLOD = FMath::Max(BatchLOD, CalcDesiredLOD(View, CameraLocalPos, SubX, SubY)); OutNeighborLODs[0] = FMath::Max(OutfLOD, CalcDesiredLOD(View, CameraLocalPos, SubX, SubY - 1)); OutNeighborLODs[1] = FMath::Max(OutfLOD, CalcDesiredLOD(View, CameraLocalPos, SubX - 1, SubY )); OutNeighborLODs[2] = FMath::Max(OutfLOD, CalcDesiredLOD(View, CameraLocalPos, SubX + 1, SubY )); OutNeighborLODs[3] = FMath::Max(OutfLOD, CalcDesiredLOD(View, CameraLocalPos, SubX, SubY + 1)); } void FLandscapeComponentSceneProxy::GetDynamicMeshElements(const TArray& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const { QUICK_SCOPE_CYCLE_COUNTER(STAT_FLandscapeComponentSceneProxy_GetMeshElements); int32 NumPasses = 0; int32 NumTriangles = 0; int32 NumDrawCalls = 0; const bool bIsWireframe = ViewFamily.EngineShowFlags.Wireframe; for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) { if (VisibilityMap & (1 << ViewIndex)) { const FSceneView* View = Views[ViewIndex]; FVector CameraLocalPos3D = WorldToLocal.TransformPosition(View->ViewMatrices.ViewOrigin); FVector2D CameraLocalPos(CameraLocalPos3D.X, CameraLocalPos3D.Y); FLandscapeElementParamArray& ParameterArray = Collector.AllocateOneFrameResource(); ParameterArray.ElementParams.Empty(NumSubsections * NumSubsections); ParameterArray.ElementParams.AddUninitialized(NumSubsections * NumSubsections); FMeshBatch& Mesh = Collector.AllocateMesh(); // Could be different from bRequiresAdjacencyInformation during shader compilation FMaterialRenderProxy* RenderProxy = MaterialInterface->GetRenderProxy(false); bool bCurrentRequiresAdjacencyInformation = RequiresAdjacencyInformation(RenderProxy, View->GetFeatureLevel()); Mesh.Type = bCurrentRequiresAdjacencyInformation ? PT_12_ControlPointPatchList : PT_TriangleList; Mesh.LCI = ComponentLightInfo.Get(); Mesh.CastShadow = true; Mesh.VertexFactory = VertexFactory; Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); #if WITH_EDITOR FMeshBatch& MeshTools = Collector.AllocateMesh(); MeshTools.LCI = ComponentLightInfo.Get(); MeshTools.Type = PT_TriangleList; MeshTools.CastShadow = false; MeshTools.VertexFactory = VertexFactory; MeshTools.ReverseCulling = IsLocalToWorldDeterminantNegative(); #endif // Setup the LOD parameters for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32 SubSectionIdx = SubX + SubY*NumSubsections; int32 CurrentLOD = CalcLODForSubsection(*View, SubX, SubY, CameraLocalPos); FMeshBatchElement& BatchElement = (SubX == 0 && SubY == 0) ? *Mesh.Elements.GetData() : *(new(Mesh.Elements) FMeshBatchElement); BatchElement.PrimitiveUniformBufferResource = &GetUniformBuffer(); FLandscapeBatchElementParams& BatchElementParams = ParameterArray.ElementParams[SubSectionIdx]; BatchElementParams.LocalToWorldNoScalingPtr = &LocalToWorldNoScaling; BatchElement.UserData = &BatchElementParams; BatchElementParams.LandscapeUniformShaderParametersResource = &LandscapeUniformShaderParameters; BatchElementParams.SceneProxy = this; BatchElementParams.SubX = SubX; BatchElementParams.SubY = SubY; BatchElementParams.CurrentLOD = CurrentLOD; int32 LodSubsectionSizeVerts = (SubsectionSizeVerts >> CurrentLOD); uint32 NumPrimitives = FMath::Square((LodSubsectionSizeVerts - 1)) * 2; if (bCurrentRequiresAdjacencyInformation) { check(SharedBuffers->AdjacencyIndexBuffers); BatchElement.IndexBuffer = SharedBuffers->AdjacencyIndexBuffers->IndexBuffers[CurrentLOD]; BatchElement.FirstIndex = (SubX + SubY * NumSubsections) * NumPrimitives * 12; } else { BatchElement.IndexBuffer = SharedBuffers->IndexBuffers[CurrentLOD]; BatchElement.FirstIndex = (SubX + SubY * NumSubsections) * NumPrimitives * 3; } BatchElement.NumPrimitives = NumPrimitives; BatchElement.MinVertexIndex = SharedBuffers->IndexRanges[CurrentLOD].MinIndex[SubX][SubY]; BatchElement.MaxVertexIndex = SharedBuffers->IndexRanges[CurrentLOD].MaxIndex[SubX][SubY]; #if WITH_EDITOR FMeshBatchElement& BatchElementTools = (SubX == 0 && SubY == 0) ? *MeshTools.Elements.GetData() : *(new(MeshTools.Elements) FMeshBatchElement); BatchElementTools.PrimitiveUniformBufferResource = &GetUniformBuffer(); BatchElementTools.UserData = &BatchElementParams; // Tools never use tessellation BatchElementTools.IndexBuffer = SharedBuffers->IndexBuffers[CurrentLOD]; BatchElementTools.NumPrimitives = NumPrimitives; BatchElementTools.FirstIndex = (SubX + SubY * NumSubsections) * NumPrimitives * 3; BatchElementTools.MinVertexIndex = SharedBuffers->IndexRanges[CurrentLOD].MinIndex[SubX][SubY]; BatchElementTools.MaxVertexIndex = SharedBuffers->IndexRanges[CurrentLOD].MaxIndex[SubX][SubY]; #endif } } // Render the landscape component #if WITH_EDITOR switch (GLandscapeViewMode) { case ELandscapeViewMode::DebugLayer: if (GLayerDebugColorMaterial && EditToolRenderData) { auto DebugColorMaterialInstance = new FLandscapeDebugMaterialRenderProxy(GLayerDebugColorMaterial->GetRenderProxy(false), (EditToolRenderData->DebugChannelR >= 0 ? WeightmapTextures[EditToolRenderData->DebugChannelR / 4] : nullptr), (EditToolRenderData->DebugChannelG >= 0 ? WeightmapTextures[EditToolRenderData->DebugChannelG / 4] : nullptr), (EditToolRenderData->DebugChannelB >= 0 ? WeightmapTextures[EditToolRenderData->DebugChannelB / 4] : nullptr), (EditToolRenderData->DebugChannelR >= 0 ? DebugColorMask::Masks[EditToolRenderData->DebugChannelR % 4] : DebugColorMask::Masks[4]), (EditToolRenderData->DebugChannelG >= 0 ? DebugColorMask::Masks[EditToolRenderData->DebugChannelG % 4] : DebugColorMask::Masks[4]), (EditToolRenderData->DebugChannelB >= 0 ? DebugColorMask::Masks[EditToolRenderData->DebugChannelB % 4] : DebugColorMask::Masks[4]) ); MeshTools.MaterialRenderProxy = DebugColorMaterialInstance; Collector.RegisterOneFrameMaterialProxy(DebugColorMaterialInstance); MeshTools.bCanApplyViewModeOverrides = true; MeshTools.bUseWireframeSelectionColoring = IsSelected(); Collector.AddMesh(ViewIndex, MeshTools); NumPasses++; NumTriangles += MeshTools.GetNumPrimitives(); NumDrawCalls += MeshTools.Elements.Num(); } break; case ELandscapeViewMode::LayerDensity: if (EditToolRenderData) { int32 ColorIndex = FMath::Min(NumWeightmapLayerAllocations, GEngine->ShaderComplexityColors.Num()); auto LayerDensityMaterialInstance = new FColoredMaterialRenderProxy(GEngine->LevelColorationUnlitMaterial->GetRenderProxy(false), ColorIndex ? GEngine->ShaderComplexityColors[ColorIndex - 1] : FLinearColor::Black); MeshTools.MaterialRenderProxy = LayerDensityMaterialInstance; Collector.RegisterOneFrameMaterialProxy(LayerDensityMaterialInstance); MeshTools.bCanApplyViewModeOverrides = true; MeshTools.bUseWireframeSelectionColoring = IsSelected(); Collector.AddMesh(ViewIndex, MeshTools); NumPasses++; NumTriangles += MeshTools.GetNumPrimitives(); NumDrawCalls += MeshTools.Elements.Num(); } break; case ELandscapeViewMode::LOD: { FLinearColor WireColors[LANDSCAPE_LOD_LEVELS]; WireColors[0] = FLinearColor(1, 1, 1); WireColors[1] = FLinearColor(1, 0, 0); WireColors[2] = FLinearColor(0, 1, 0); WireColors[3] = FLinearColor(0, 0, 1); WireColors[4] = FLinearColor(1, 1, 0); WireColors[5] = FLinearColor(1, 0, 1); WireColors[6] = FLinearColor(0, 1, 1); WireColors[7] = FLinearColor(0.5f, 0, 0.5f); for (int32 i = 0; i < MeshTools.Elements.Num(); i++) { FMeshBatch& LODMesh = Collector.AllocateMesh(); LODMesh = MeshTools; LODMesh.Elements.Empty(1); LODMesh.Elements.Add(MeshTools.Elements[i]); int32 ColorIndex = ((FLandscapeBatchElementParams*)MeshTools.Elements[i].UserData)->CurrentLOD; FLinearColor Color = ForcedLOD >= 0 ? WireColors[ColorIndex] : WireColors[ColorIndex] * 0.2f; auto LODMaterialInstance = new FColoredMaterialRenderProxy(GEngine->LevelColorationUnlitMaterial->GetRenderProxy(false), Color); LODMesh.MaterialRenderProxy = LODMaterialInstance; Collector.RegisterOneFrameMaterialProxy(LODMaterialInstance); if (ViewFamily.EngineShowFlags.Wireframe) { LODMesh.bCanApplyViewModeOverrides = false; LODMesh.bWireframe = true; } else { LODMesh.bCanApplyViewModeOverrides = true; LODMesh.bUseWireframeSelectionColoring = IsSelected(); } Collector.AddMesh(ViewIndex, LODMesh); NumPasses++; NumTriangles += MeshTools.Elements[i].NumPrimitives; NumDrawCalls++; } } break; case ELandscapeViewMode::WireframeOnTop: { // base mesh Mesh.MaterialRenderProxy = MaterialInterface->GetRenderProxy(false); Mesh.bCanApplyViewModeOverrides = false; Collector.AddMesh(ViewIndex, Mesh); NumPasses++; NumTriangles += Mesh.GetNumPrimitives(); NumDrawCalls += Mesh.Elements.Num(); // wireframe on top FMeshBatch& WireMesh = Collector.AllocateMesh(); WireMesh = MeshTools; auto WireMaterialInstance = new FColoredMaterialRenderProxy(GEngine->LevelColorationUnlitMaterial->GetRenderProxy(false), FLinearColor(0, 0, 1)); WireMesh.MaterialRenderProxy = WireMaterialInstance; Collector.RegisterOneFrameMaterialProxy(WireMaterialInstance); WireMesh.bCanApplyViewModeOverrides = false; WireMesh.bWireframe = true; Collector.AddMesh(ViewIndex, WireMesh); NumPasses++; NumTriangles += WireMesh.GetNumPrimitives(); NumDrawCalls++; } break; default: #else { #endif // WITH_EDITOR // Regular Landscape rendering. Only use the dynamic path if we're rendering a rich view or we've disabled the static path for debugging. if( IsRichView(ViewFamily) || GLandscapeDebugOptions.bDisableStatic || bIsWireframe || #if WITH_EDITOR (IsSelected() && !GLandscapeEditModeActive) #else IsSelected() #endif ) { Mesh.MaterialRenderProxy = MaterialInterface->GetRenderProxy(false); Mesh.bCanApplyViewModeOverrides = true; Mesh.bUseWireframeSelectionColoring = IsSelected(); Collector.AddMesh(ViewIndex, Mesh); NumPasses++; NumTriangles += Mesh.GetNumPrimitives(); NumDrawCalls += Mesh.Elements.Num(); } } #if WITH_EDITOR // Extra render passes for landscape tools if (GLandscapeEditModeActive) { // Region selection if (EditToolRenderData && EditToolRenderData->SelectedType) { if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::SelectRegion) && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_REGION) && !(GLandscapeEditRenderMode & ELandscapeEditRenderMode::Mask)) { FMeshBatch& SelectMesh = Collector.AllocateMesh(); SelectMesh = MeshTools; auto SelectMaterialInstance = new FLandscapeSelectMaterialRenderProxy(GSelectionRegionMaterial->GetRenderProxy(false), EditToolRenderData->DataTexture ? EditToolRenderData->DataTexture : GLandscapeBlackTexture); SelectMesh.MaterialRenderProxy = SelectMaterialInstance; Collector.RegisterOneFrameMaterialProxy(SelectMaterialInstance); Collector.AddMesh(ViewIndex, SelectMesh); NumPasses++; NumTriangles += SelectMesh.GetNumPrimitives(); NumDrawCalls += SelectMesh.Elements.Num(); } if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::SelectComponent) && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_COMPONENT)) { FMeshBatch& SelectMesh = Collector.AllocateMesh(); SelectMesh = MeshTools; SelectMesh.MaterialRenderProxy = GSelectionColorMaterial->GetRenderProxy(0); Collector.AddMesh(ViewIndex, SelectMesh); NumPasses++; NumTriangles += SelectMesh.GetNumPrimitives(); NumDrawCalls += SelectMesh.Elements.Num(); } } // Mask if ((GLandscapeEditRenderMode & ELandscapeEditRenderMode::SelectRegion) && (GLandscapeEditRenderMode & ELandscapeEditRenderMode::Mask)) { if (EditToolRenderData && (EditToolRenderData->SelectedType & FLandscapeEditToolRenderData::ST_REGION)) { FMeshBatch& MaskMesh = Collector.AllocateMesh(); MaskMesh = MeshTools; auto MaskMaterialInstance = new FLandscapeMaskMaterialRenderProxy(GMaskRegionMaterial->GetRenderProxy(false), EditToolRenderData->DataTexture ? EditToolRenderData->DataTexture : GLandscapeBlackTexture, !!(GLandscapeEditRenderMode & ELandscapeEditRenderMode::InvertedMask)); MaskMesh.MaterialRenderProxy = MaskMaterialInstance; Collector.RegisterOneFrameMaterialProxy(MaskMaterialInstance); Collector.AddMesh(ViewIndex, MaskMesh); NumPasses++; NumTriangles += MaskMesh.GetNumPrimitives(); NumDrawCalls += MaskMesh.Elements.Num(); } else if (!(GLandscapeEditRenderMode & ELandscapeEditRenderMode::InvertedMask)) { FMeshBatch& MaskMesh = Collector.AllocateMesh(); MaskMesh = MeshTools; auto MaskMaterialInstance = new FLandscapeMaskMaterialRenderProxy(GMaskRegionMaterial->GetRenderProxy(false), GLandscapeBlackTexture, false); MaskMesh.MaterialRenderProxy = MaskMaterialInstance; Collector.RegisterOneFrameMaterialProxy(MaskMaterialInstance); Collector.AddMesh(ViewIndex, MaskMesh); NumPasses++; NumTriangles += MaskMesh.GetNumPrimitives(); NumDrawCalls += MaskMesh.Elements.Num(); } } // Edit mode tools if (EditToolRenderData) { if (EditToolRenderData->ToolMaterial) { FMeshBatch& EditMesh = Collector.AllocateMesh(); EditMesh = MeshTools; EditMesh.MaterialRenderProxy = EditToolRenderData->ToolMaterial->GetRenderProxy(0); Collector.AddMesh(ViewIndex, EditMesh); NumPasses++; NumTriangles += EditMesh.GetNumPrimitives(); NumDrawCalls += EditMesh.Elements.Num(); } if (EditToolRenderData->GizmoMaterial && GLandscapeEditRenderMode & ELandscapeEditRenderMode::Gizmo) { FMeshBatch& EditMesh = Collector.AllocateMesh(); EditMesh = MeshTools; EditMesh.MaterialRenderProxy = EditToolRenderData->GizmoMaterial->GetRenderProxy(0); Collector.AddMesh(ViewIndex, EditMesh); NumPasses++; NumTriangles += EditMesh.GetNumPrimitives(); NumDrawCalls += EditMesh.Elements.Num(); } } } #endif // WITH_EDITOR if (GLandscapeDebugOptions.bShowPatches) { DrawWireBox(Collector.GetPDI(ViewIndex), GetBounds().GetBox(), FColor(255, 255, 0), SDPG_World); } RenderBounds(Collector.GetPDI(ViewIndex), ViewFamily.EngineShowFlags, GetBounds(), IsSelected()); } } INC_DWORD_STAT_BY(STAT_LandscapeComponents, NumPasses); INC_DWORD_STAT_BY(STAT_LandscapeDrawCalls, NumDrawCalls); INC_DWORD_STAT_BY(STAT_LandscapeTriangles, NumTriangles * NumPasses); } // // FLandscapeVertexBuffer // /** * Initialize the RHI for this rendering resource */ void FLandscapeVertexBuffer::InitRHI() { // create a static vertex buffer FRHIResourceCreateInfo CreateInfo; VertexBufferRHI = RHICreateVertexBuffer(NumVertices * sizeof(FLandscapeVertex), BUF_Static, CreateInfo); FLandscapeVertex* Vertex = (FLandscapeVertex*)RHILockVertexBuffer(VertexBufferRHI, 0, NumVertices * sizeof(FLandscapeVertex), RLM_WriteOnly); int32 VertexIndex = 0; for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32 Cols = SubX == NumSubsections - 1 && FeatureLevel > ERHIFeatureLevel::ES3_1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; int32 Rows = SubY == NumSubsections - 1 && FeatureLevel > ERHIFeatureLevel::ES3_1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; for (int32 y = 0; y < Rows; y++) { for (int32 x = 0; x < Cols; x++) { Vertex->VertexX = x; Vertex->VertexY = y; Vertex->SubX = SubX; Vertex->SubY = SubY; Vertex++; VertexIndex++; } } } } check(NumVertices == VertexIndex); RHIUnlockVertexBuffer(VertexBufferRHI); } // // FLandscapeSharedBuffers // template void FLandscapeSharedBuffers::CreateIndexBuffers(ERHIFeatureLevel::Type InFeatureLevel, bool bRequiresAdjacencyInformation) { if (InFeatureLevel <= ERHIFeatureLevel::ES3_1) { if (!bVertexScoresComputed) { bVertexScoresComputed = ComputeVertexScores(); } } TMap VertexMap; INDEX_TYPE VertexCount = 0; int32 SubsectionSizeQuads = SubsectionSizeVerts - 1; // Layout index buffer to determine best vertex order int32 MaxLOD = NumIndexBuffers - 1; for (int32 Mip = MaxLOD; Mip >= 0; Mip--) { int32 LodSubsectionSizeQuads = (SubsectionSizeVerts >> Mip) - 1; TArray NewIndices; int32 ExpectedNumIndices = FMath::Square(NumSubsections) * FMath::Square(LodSubsectionSizeQuads) * 6; NewIndices.Empty(ExpectedNumIndices); int32& MaxIndexFull = IndexRanges[Mip].MaxIndexFull; int32& MinIndexFull = IndexRanges[Mip].MinIndexFull; MaxIndexFull = 0; MinIndexFull = MAX_int32; if (InFeatureLevel <= ERHIFeatureLevel::ES3_1) { // ES2 version float MipRatio = (float)SubsectionSizeQuads / (float)LodSubsectionSizeQuads; // Morph current MIP to base MIP for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { TArray SubIndices; SubIndices.Empty(FMath::Square(LodSubsectionSizeQuads) * 6); int32& MaxIndex = IndexRanges[Mip].MaxIndex[SubX][SubY]; int32& MinIndex = IndexRanges[Mip].MinIndex[SubX][SubY]; MaxIndex = 0; MinIndex = MAX_int32; for (int32 y = 0; y < LodSubsectionSizeQuads; y++) { for (int32 x = 0; x < LodSubsectionSizeQuads; x++) { int32 x0 = FMath::RoundToInt((float)x * MipRatio); int32 y0 = FMath::RoundToInt((float)y * MipRatio); int32 x1 = FMath::RoundToInt((float)(x + 1) * MipRatio); int32 y1 = FMath::RoundToInt((float)(y + 1) * MipRatio); FLandscapeVertexRef V00(x0, y0, SubX, SubY); FLandscapeVertexRef V10(x1, y0, SubX, SubY); FLandscapeVertexRef V11(x1, y1, SubX, SubY); FLandscapeVertexRef V01(x0, y1, SubX, SubY); uint64 Key00 = V00.MakeKey(); uint64 Key10 = V10.MakeKey(); uint64 Key11 = V11.MakeKey(); uint64 Key01 = V01.MakeKey(); INDEX_TYPE i00; INDEX_TYPE i10; INDEX_TYPE i11; INDEX_TYPE i01; INDEX_TYPE* KeyPtr = VertexMap.Find(Key00); if (KeyPtr == nullptr) { i00 = VertexCount++; VertexMap.Add(Key00, i00); } else { i00 = *KeyPtr; } KeyPtr = VertexMap.Find(Key10); if (KeyPtr == nullptr) { i10 = VertexCount++; VertexMap.Add(Key10, i10); } else { i10 = *KeyPtr; } KeyPtr = VertexMap.Find(Key11); if (KeyPtr == nullptr) { i11 = VertexCount++; VertexMap.Add(Key11, i11); } else { i11 = *KeyPtr; } KeyPtr = VertexMap.Find(Key01); if (KeyPtr == nullptr) { i01 = VertexCount++; VertexMap.Add(Key01, i01); } else { i01 = *KeyPtr; } // Update the min/max index ranges MaxIndex = FMath::Max(MaxIndex, i00); MinIndex = FMath::Min(MinIndex, i00); MaxIndex = FMath::Max(MaxIndex, i10); MinIndex = FMath::Min(MinIndex, i10); MaxIndex = FMath::Max(MaxIndex, i11); MinIndex = FMath::Min(MinIndex, i11); MaxIndex = FMath::Max(MaxIndex, i01); MinIndex = FMath::Min(MinIndex, i01); SubIndices.Add(i00); SubIndices.Add(i11); SubIndices.Add(i10); SubIndices.Add(i00); SubIndices.Add(i01); SubIndices.Add(i11); } } // update min/max for full subsection MaxIndexFull = FMath::Max(MaxIndexFull, MaxIndex); MinIndexFull = FMath::Min(MinIndexFull, MinIndex); TArray NewSubIndices; ::OptimizeFaces(SubIndices, NewSubIndices, 32); NewIndices.Append(NewSubIndices); } } } else { // non-ES2 version int SubOffset = 0; for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32& MaxIndex = IndexRanges[Mip].MaxIndex[SubX][SubY]; int32& MinIndex = IndexRanges[Mip].MinIndex[SubX][SubY]; MaxIndex = 0; MinIndex = MAX_int32; int32 StrideX = SubX == NumSubsections - 1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; int32 StrideY = SubY == NumSubsections - 1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; for (int32 y = 0; y < LodSubsectionSizeQuads; y++) { for (int32 x = 0; x < LodSubsectionSizeQuads; x++) { INDEX_TYPE i00 = (x + 0) + (y + 0) * StrideX + SubOffset; INDEX_TYPE i10 = (x + 1) + (y + 0) * StrideX + SubOffset; INDEX_TYPE i11 = (x + 1) + (y + 1) * StrideX + SubOffset; INDEX_TYPE i01 = (x + 0) + (y + 1) * StrideX + SubOffset; NewIndices.Add(i00); NewIndices.Add(i11); NewIndices.Add(i10); NewIndices.Add(i00); NewIndices.Add(i01); NewIndices.Add(i11); // Update the min/max index ranges MaxIndex = FMath::Max(MaxIndex, i00); MinIndex = FMath::Min(MinIndex, i00); MaxIndex = FMath::Max(MaxIndex, i10); MinIndex = FMath::Min(MinIndex, i10); MaxIndex = FMath::Max(MaxIndex, i11); MinIndex = FMath::Min(MinIndex, i11); MaxIndex = FMath::Max(MaxIndex, i01); MinIndex = FMath::Min(MinIndex, i01); } } // update min/max for full subsection MaxIndexFull = FMath::Max(MaxIndexFull, MaxIndex); MinIndexFull = FMath::Min(MinIndexFull, MinIndex); SubOffset += StrideX * StrideY; } } check(MinIndexFull <= (uint32)((INDEX_TYPE)(~(INDEX_TYPE)0))); check(NewIndices.Num() == ExpectedNumIndices); } // Create and init new index buffer with index data FRawStaticIndexBuffer16or32* IndexBuffer = (FRawStaticIndexBuffer16or32*)IndexBuffers[Mip]; if (!IndexBuffer) { IndexBuffer = new FRawStaticIndexBuffer16or32(false); } IndexBuffer->AssignNewBuffer(NewIndices); // Delay init resource to keep CPU data until create AdjacencyIndexbuffers if (!bRequiresAdjacencyInformation) { IndexBuffer->InitResource(); } IndexBuffers[Mip] = IndexBuffer; } } template void FLandscapeSharedBuffers::CreateGrassIndexBuffer() { int32 SubsectionSizeQuads = SubsectionSizeVerts - 1; TArray NewIndices; int32 ExpectedNumIndices = (FMath::Square(NumSubsections) * FMath::Square(SubsectionSizeQuads) + 2 * NumSubsections * SubsectionSizeQuads + 1) * 6; NewIndices.Empty(ExpectedNumIndices); int32 SubOffset = 0; for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { int32 Cols = SubX == NumSubsections - 1 ? SubsectionSizeQuads + 1 : SubsectionSizeQuads; int32 Rows = SubY == NumSubsections - 1 ? SubsectionSizeQuads + 1 : SubsectionSizeQuads; int32 StrideX = SubX == NumSubsections - 1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; int32 StrideY = SubY == NumSubsections - 1 ? SubsectionSizeVerts + 1 : SubsectionSizeVerts; for (int32 y = 0; y < Rows; y++) { for (int32 x = 0; x < Cols; x++) { INDEX_TYPE i00 = (x + 0) + (y + 0) * StrideX + SubOffset; INDEX_TYPE i10 = (x + 1) + (y + 0) * StrideX + SubOffset; INDEX_TYPE i11 = (x + 1) + (y + 1) * StrideX + SubOffset; INDEX_TYPE i01 = (x + 0) + (y + 1) * StrideX + SubOffset; NewIndices.Add(i00); NewIndices.Add(i11); NewIndices.Add(i10); NewIndices.Add(i00); NewIndices.Add(i01); NewIndices.Add(i11); } } SubOffset += StrideX * StrideY; } } check(NewIndices.Num() == ExpectedNumIndices); // Create and init new index buffer with index data FRawStaticIndexBuffer16or32* IndexBuffer = new FRawStaticIndexBuffer16or32(false); IndexBuffer->AssignNewBuffer(NewIndices); IndexBuffer->InitResource(); GrassIndexBuffer = IndexBuffer; } FLandscapeSharedBuffers::FLandscapeSharedBuffers(int32 InSharedBuffersKey, int32 InSubsectionSizeQuads, int32 InNumSubsections, ERHIFeatureLevel::Type InFeatureLevel, bool bRequiresAdjacencyInformation) : SharedBuffersKey(InSharedBuffersKey) , NumIndexBuffers(FMath::CeilLogTwo(InSubsectionSizeQuads + 1)) , SubsectionSizeVerts(InSubsectionSizeQuads + 1) , NumSubsections(InNumSubsections) , VertexFactory(nullptr) , VertexBuffer(nullptr) , AdjacencyIndexBuffers(nullptr) , bUse32BitIndices(false) , GrassIndexBuffer(nullptr) { NumVertices = FMath::Square(SubsectionSizeVerts) * FMath::Square(NumSubsections); if (InFeatureLevel > ERHIFeatureLevel::ES3_1) { NumVertices += 2 * NumSubsections * SubsectionSizeVerts + 1; // Vertex Buffer cannot be shared VertexBuffer = new FLandscapeVertexBuffer(InFeatureLevel, NumVertices, SubsectionSizeVerts, NumSubsections); } IndexBuffers = new FIndexBuffer*[NumIndexBuffers]; FMemory::Memzero(IndexBuffers, sizeof(FIndexBuffer*)* NumIndexBuffers); IndexRanges = new FLandscapeIndexRanges[NumIndexBuffers](); // See if we need to use 16 or 32-bit index buffers if (NumVertices > 65535) { bUse32BitIndices = true; CreateIndexBuffers(InFeatureLevel, bRequiresAdjacencyInformation); if (InFeatureLevel > ERHIFeatureLevel::ES3_1) { CreateGrassIndexBuffer(); } } else { CreateIndexBuffers(InFeatureLevel, bRequiresAdjacencyInformation); if (InFeatureLevel > ERHIFeatureLevel::ES3_1) { CreateGrassIndexBuffer(); } } } FLandscapeSharedBuffers::~FLandscapeSharedBuffers() { delete VertexBuffer; for (int32 i = 0; i < NumIndexBuffers; i++) { IndexBuffers[i]->ReleaseResource(); delete IndexBuffers[i]; } delete[] IndexBuffers; delete[] IndexRanges; if (GrassIndexBuffer) { GrassIndexBuffer->ReleaseResource(); delete GrassIndexBuffer; } if (AdjacencyIndexBuffers) { if (AdjacencyIndexBuffers->Release() == 0) { FLandscapeComponentSceneProxy::SharedAdjacencyIndexBufferMap.Remove(SharedBuffersKey); } AdjacencyIndexBuffers = nullptr; } delete VertexFactory; } template static void BuildLandscapeAdjacencyIndexBuffer(int32 LODSubsectionSizeQuads, int32 NumSubsections, const FRawStaticIndexBuffer16or32* Indices, TArray& OutPnAenIndices) { if (Indices && Indices->Num()) { // Landscape use regular grid, so only expand Index buffer works // PN AEN Dominant Corner uint32 TriCount = LODSubsectionSizeQuads*LODSubsectionSizeQuads * 2; uint32 ExpandedCount = 12 * TriCount * NumSubsections * NumSubsections; OutPnAenIndices.Empty(ExpandedCount); OutPnAenIndices.AddUninitialized(ExpandedCount); for (int32 SubY = 0; SubY < NumSubsections; SubY++) { for (int32 SubX = 0; SubX < NumSubsections; SubX++) { uint32 SubsectionTriIndex = (SubX + SubY * NumSubsections) * TriCount; for (uint32 TriIdx = SubsectionTriIndex; TriIdx < SubsectionTriIndex + TriCount; ++TriIdx) { uint32 OutStartIdx = TriIdx * 12; uint32 InStartIdx = TriIdx * 3; OutPnAenIndices[OutStartIdx + 0] = Indices->Get(InStartIdx + 0); OutPnAenIndices[OutStartIdx + 1] = Indices->Get(InStartIdx + 1); OutPnAenIndices[OutStartIdx + 2] = Indices->Get(InStartIdx + 2); OutPnAenIndices[OutStartIdx + 3] = Indices->Get(InStartIdx + 0); OutPnAenIndices[OutStartIdx + 4] = Indices->Get(InStartIdx + 1); OutPnAenIndices[OutStartIdx + 5] = Indices->Get(InStartIdx + 1); OutPnAenIndices[OutStartIdx + 6] = Indices->Get(InStartIdx + 2); OutPnAenIndices[OutStartIdx + 7] = Indices->Get(InStartIdx + 2); OutPnAenIndices[OutStartIdx + 8] = Indices->Get(InStartIdx + 0); OutPnAenIndices[OutStartIdx + 9] = Indices->Get(InStartIdx + 0); OutPnAenIndices[OutStartIdx + 10] = Indices->Get(InStartIdx + 1); OutPnAenIndices[OutStartIdx + 11] = Indices->Get(InStartIdx + 2); } } } } else { OutPnAenIndices.Empty(); } } FLandscapeSharedAdjacencyIndexBuffer::FLandscapeSharedAdjacencyIndexBuffer(FLandscapeSharedBuffers* Buffers) { ensure(Buffers && Buffers->IndexBuffers); // Currently only support PN-AEN-Dominant Corner, which is the only mode for UE4 for now IndexBuffers.Empty(Buffers->NumIndexBuffers); bool b32BitIndex = Buffers->NumVertices > 65535; for (int32 i = 0; i < Buffers->NumIndexBuffers; ++i) { if (b32BitIndex) { TArray OutPnAenIndices; BuildLandscapeAdjacencyIndexBuffer((Buffers->SubsectionSizeVerts >> i) - 1, Buffers->NumSubsections, (FRawStaticIndexBuffer16or32*)Buffers->IndexBuffers[i], OutPnAenIndices); FRawStaticIndexBuffer16or32* IndexBuffer = new FRawStaticIndexBuffer16or32(); IndexBuffer->AssignNewBuffer(OutPnAenIndices); IndexBuffers.Add(IndexBuffer); } else { TArray OutPnAenIndices; BuildLandscapeAdjacencyIndexBuffer((Buffers->SubsectionSizeVerts >> i) - 1, Buffers->NumSubsections, (FRawStaticIndexBuffer16or32*)Buffers->IndexBuffers[i], OutPnAenIndices); FRawStaticIndexBuffer16or32* IndexBuffer = new FRawStaticIndexBuffer16or32(); IndexBuffer->AssignNewBuffer(OutPnAenIndices); IndexBuffers.Add(IndexBuffer); } IndexBuffers[i]->InitResource(); } } FLandscapeSharedAdjacencyIndexBuffer::~FLandscapeSharedAdjacencyIndexBuffer() { for (int i = 0; i < IndexBuffers.Num(); ++i) { IndexBuffers[i]->ReleaseResource(); delete IndexBuffers[i]; } } // // FLandscapeVertexFactoryVertexShaderParameters // /** Shader parameters for use with FLandscapeVertexFactory */ class FLandscapeVertexFactoryVertexShaderParameters : public FVertexFactoryShaderParameters { public: /** * Bind shader constants by name * @param ParameterMap - mapping of named shader constants to indices */ virtual void Bind(const FShaderParameterMap& ParameterMap) override { HeightmapTextureParameter.Bind(ParameterMap, TEXT("HeightmapTexture")); HeightmapTextureParameterSampler.Bind(ParameterMap, TEXT("HeightmapTextureSampler")); LodValuesParameter.Bind(ParameterMap, TEXT("LodValues")); NeighborSectionLodParameter.Bind(ParameterMap, TEXT("NeighborSectionLod")); LodBiasParameter.Bind(ParameterMap, TEXT("LodBias")); SectionLodsParameter.Bind(ParameterMap, TEXT("SectionLods")); XYOffsetTextureParameter.Bind(ParameterMap, TEXT("XYOffsetmapTexture")); XYOffsetTextureParameterSampler.Bind(ParameterMap, TEXT("XYOffsetmapTextureSampler")); } /** * Serialize shader params to an archive * @param Ar - archive to serialize to */ virtual void Serialize(FArchive& Ar) override { Ar << HeightmapTextureParameter; Ar << HeightmapTextureParameterSampler; Ar << LodValuesParameter; Ar << NeighborSectionLodParameter; Ar << LodBiasParameter; Ar << SectionLodsParameter; Ar << XYOffsetTextureParameter; Ar << XYOffsetTextureParameterSampler; } /** * Set any shader data specific to this vertex factory */ virtual void SetMesh(FRHICommandList& RHICmdList, FShader* VertexShader, const class FVertexFactory* VertexFactory, const class FSceneView& View, const struct FMeshBatchElement& BatchElement, uint32 DataFlags) const override { SCOPE_CYCLE_COUNTER(STAT_LandscapeVFDrawTime); const FLandscapeBatchElementParams* BatchElementParams = (const FLandscapeBatchElementParams*)BatchElement.UserData; check(BatchElementParams); const FLandscapeComponentSceneProxy* SceneProxy = BatchElementParams->SceneProxy; SetUniformBufferParameter(RHICmdList, VertexShader->GetVertexShader(), VertexShader->GetUniformBufferParameter(), *BatchElementParams->LandscapeUniformShaderParametersResource); if (HeightmapTextureParameter.IsBound()) { SetTextureParameter( RHICmdList, VertexShader->GetVertexShader(), HeightmapTextureParameter, HeightmapTextureParameterSampler, TStaticSamplerState::GetRHI(), SceneProxy->HeightmapTexture->Resource->TextureRHI ); } if (LodBiasParameter.IsBound()) { FVector4 LodBias( 0.0f, // unused 0.0f, // unused ((FTexture2DResource*)SceneProxy->HeightmapTexture->Resource)->GetCurrentFirstMip(), SceneProxy->XYOffsetmapTexture ? ((FTexture2DResource*)SceneProxy->XYOffsetmapTexture->Resource)->GetCurrentFirstMip() : 0.0f ); SetShaderValue(RHICmdList, VertexShader->GetVertexShader(), LodBiasParameter, LodBias); } // Calculate LOD params FVector CameraLocalPos3D = SceneProxy->WorldToLocal.TransformPosition(View.ViewMatrices.ViewOrigin); FVector2D CameraLocalPos = FVector2D(CameraLocalPos3D.X, CameraLocalPos3D.Y); FVector4 fCurrentLODs; FVector4 CurrentNeighborLODs[4]; if (BatchElementParams->SubX == -1) { for (int32 SubY = 0; SubY < SceneProxy->NumSubsections; SubY++) { for (int32 SubX = 0; SubX < SceneProxy->NumSubsections; SubX++) { int32 SubIndex = SubX + 2 * SubY; SceneProxy->CalcLODParamsForSubsection(View, CameraLocalPos, SubX, SubY, BatchElementParams->CurrentLOD, fCurrentLODs[SubIndex], CurrentNeighborLODs[SubIndex]); } } } else { int32 SubIndex = BatchElementParams->SubX + 2 * BatchElementParams->SubY; SceneProxy->CalcLODParamsForSubsection(View, CameraLocalPos, BatchElementParams->SubX, BatchElementParams->SubY, BatchElementParams->CurrentLOD, fCurrentLODs[SubIndex], CurrentNeighborLODs[SubIndex]); } if (SectionLodsParameter.IsBound()) { SetShaderValue(RHICmdList, VertexShader->GetVertexShader(), SectionLodsParameter, fCurrentLODs); } if (NeighborSectionLodParameter.IsBound()) { SetShaderValue(RHICmdList, VertexShader->GetVertexShader(), NeighborSectionLodParameter, CurrentNeighborLODs); } if (LodValuesParameter.IsBound()) { FVector4 LodValues( BatchElementParams->CurrentLOD, 0.0f, // unused (float)((SceneProxy->SubsectionSizeVerts >> BatchElementParams->CurrentLOD) - 1), 1.f / (float)((SceneProxy->SubsectionSizeVerts >> BatchElementParams->CurrentLOD) - 1)); SetShaderValue(RHICmdList, VertexShader->GetVertexShader(), LodValuesParameter, LodValues); } if (XYOffsetTextureParameter.IsBound() && SceneProxy->XYOffsetmapTexture) { SetTextureParameter( RHICmdList, VertexShader->GetVertexShader(), XYOffsetTextureParameter, XYOffsetTextureParameterSampler, TStaticSamplerState::GetRHI(), SceneProxy->XYOffsetmapTexture->Resource->TextureRHI ); } } virtual uint32 GetSize() const override { return sizeof(*this); } protected: FShaderParameter LodValuesParameter; FShaderParameter NeighborSectionLodParameter; FShaderParameter LodBiasParameter; FShaderParameter SectionLodsParameter; FShaderResourceParameter HeightmapTextureParameter; FShaderResourceParameter HeightmapTextureParameterSampler; FShaderResourceParameter XYOffsetTextureParameter; FShaderResourceParameter XYOffsetTextureParameterSampler; TShaderUniformBufferParameter LandscapeShaderParameters; }; // // FLandscapeVertexFactoryPixelShaderParameters // /** * Bind shader constants by name * @param ParameterMap - mapping of named shader constants to indices */ void FLandscapeVertexFactoryPixelShaderParameters::Bind(const FShaderParameterMap& ParameterMap) { NormalmapTextureParameter.Bind(ParameterMap, TEXT("NormalmapTexture")); NormalmapTextureParameterSampler.Bind(ParameterMap, TEXT("NormalmapTextureSampler")); LocalToWorldNoScalingParameter.Bind(ParameterMap, TEXT("LocalToWorldNoScaling")); } /** * Serialize shader params to an archive * @param Ar - archive to serialize to */ void FLandscapeVertexFactoryPixelShaderParameters::Serialize(FArchive& Ar) { Ar << NormalmapTextureParameter << NormalmapTextureParameterSampler << LocalToWorldNoScalingParameter; } /** * Set any shader data specific to this vertex factory */ void FLandscapeVertexFactoryPixelShaderParameters::SetMesh(FRHICommandList& RHICmdList, FShader* PixelShader, const class FVertexFactory* VertexFactory, const class FSceneView& View, const struct FMeshBatchElement& BatchElement, uint32 DataFlags) const { SCOPE_CYCLE_COUNTER(STAT_LandscapeVFDrawTime); const FLandscapeBatchElementParams* BatchElementParams = (const FLandscapeBatchElementParams*)BatchElement.UserData; if (LocalToWorldNoScalingParameter.IsBound()) { SetShaderValue(RHICmdList, PixelShader->GetPixelShader(), LocalToWorldNoScalingParameter, *BatchElementParams->LocalToWorldNoScalingPtr); } if (NormalmapTextureParameter.IsBound()) { SetTextureParameter( RHICmdList, PixelShader->GetPixelShader(), NormalmapTextureParameter, NormalmapTextureParameterSampler, BatchElementParams->SceneProxy->NormalmapTexture->Resource); } } // // FLandscapeVertexFactory // void FLandscapeVertexFactory::InitRHI() { // list of declaration items FVertexDeclarationElementList Elements; // position decls Elements.Add(AccessStreamComponent(Data.PositionComponent, 0)); // create the actual device decls InitDeclaration(Elements, FVertexFactory::DataType()); } FVertexFactoryShaderParameters* FLandscapeVertexFactory::ConstructShaderParameters(EShaderFrequency ShaderFrequency) { switch (ShaderFrequency) { case SF_Vertex: return new FLandscapeVertexFactoryVertexShaderParameters(); break; case SF_Pixel: return new FLandscapeVertexFactoryPixelShaderParameters(); break; default: return nullptr; } } void FLandscapeVertexFactory::ModifyCompilationEnvironment(EShaderPlatform Platform, const FMaterial* Material, FShaderCompilerEnvironment& OutEnvironment) { FVertexFactory::ModifyCompilationEnvironment(Platform, Material, OutEnvironment); } IMPLEMENT_VERTEX_FACTORY_TYPE(FLandscapeVertexFactory, "LandscapeVertexFactory", true, true, true, false, false); /** * Copy the data from another vertex factory * @param Other - factory to copy from */ void FLandscapeVertexFactory::Copy(const FLandscapeVertexFactory& Other) { //SetSceneProxy(Other.Proxy()); ENQUEUE_UNIQUE_RENDER_COMMAND_TWOPARAMETER( FLandscapeVertexFactoryCopyData, FLandscapeVertexFactory*, VertexFactory, this, const DataType*, DataCopy, &Other.Data, { VertexFactory->Data = *DataCopy; }); BeginUpdateResourceRHI(this); } // // FLandscapeXYOffsetVertexFactory // void FLandscapeXYOffsetVertexFactory::ModifyCompilationEnvironment(EShaderPlatform Platform, const FMaterial* Material, FShaderCompilerEnvironment& OutEnvironment) { FLandscapeVertexFactory::ModifyCompilationEnvironment(Platform, Material, OutEnvironment); OutEnvironment.SetDefine(TEXT("LANDSCAPE_XYOFFSET"), TEXT("1")); } IMPLEMENT_VERTEX_FACTORY_TYPE(FLandscapeXYOffsetVertexFactory, "LandscapeVertexFactory", true, true, true, false, false); /** ULandscapeMaterialInstanceConstant */ ULandscapeMaterialInstanceConstant::ULandscapeMaterialInstanceConstant(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { bIsLayerThumbnail = false; DataWeightmapIndex = -1; DataWeightmapSize = 0; } void ULandscapeComponent::GetStreamingTextureInfo(TArray& OutStreamingTextures) const { ALandscapeProxy* Proxy = Cast(GetOuter()); FSphere BoundingSphere = Bounds.GetSphere(); float LocalStreamingDistanceMultiplier = 1.f; if (Proxy) { LocalStreamingDistanceMultiplier = FMath::Max(0.0f, Proxy->StreamingDistanceMultiplier); } const float TexelFactor = 0.75f * LocalStreamingDistanceMultiplier * ComponentSizeQuads * FMath::Abs(Proxy->GetRootComponent()->RelativeScale3D.X); // Normal usage... // Enumerate the textures used by the material. if (MaterialInstance) { TArray Textures; MaterialInstance->GetUsedTextures(Textures, EMaterialQualityLevel::Num, false, GetWorld()->FeatureLevel, false); // Add each texture to the output with the appropriate parameters. // TODO: Take into account which UVIndex is being used. for (int32 TextureIndex = 0; TextureIndex < Textures.Num(); TextureIndex++) { FStreamingTexturePrimitiveInfo& StreamingTexture = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingTexture.Bounds = BoundingSphere; StreamingTexture.TexelFactor = TexelFactor; StreamingTexture.Texture = Textures[TextureIndex]; } UMaterial* Material = MaterialInstance->GetMaterial(); if (Material) { int32 NumExpressions = Material->Expressions.Num(); for (int32 ExpressionIndex = 0; ExpressionIndex < NumExpressions; ExpressionIndex++) { UMaterialExpression* Expression = Material->Expressions[ExpressionIndex]; UMaterialExpressionTextureSample* TextureSample = Cast(Expression); // TODO: This is only works for direct Coordinate Texture Sample cases if (TextureSample && TextureSample->Coordinates.Expression) { UMaterialExpressionTextureCoordinate* TextureCoordinate = Cast(TextureSample->Coordinates.Expression); UMaterialExpressionLandscapeLayerCoords* TerrainTextureCoordinate = Cast(TextureSample->Coordinates.Expression); if (TextureCoordinate || TerrainTextureCoordinate) { for (int32 i = 0; i < OutStreamingTextures.Num(); ++i) { FStreamingTexturePrimitiveInfo& StreamingTexture = OutStreamingTextures[i]; if (StreamingTexture.Texture == TextureSample->Texture) { if (TextureCoordinate) { StreamingTexture.TexelFactor = TexelFactor * FPlatformMath::Max(TextureCoordinate->UTiling, TextureCoordinate->VTiling); } else //if ( TerrainTextureCoordinate ) { StreamingTexture.TexelFactor = TexelFactor * TerrainTextureCoordinate->MappingScale; } break; } } } } } } // Lightmap const auto FeatureLevel = GetWorld() ? GetWorld()->FeatureLevel : GMaxRHIFeatureLevel; FLightMap2D* Lightmap = LightMap ? LightMap->GetLightMap2D() : nullptr; uint32 LightmapIndex = AllowHighQualityLightmaps(FeatureLevel) ? 0 : 1; if (Lightmap && Lightmap->IsValid(LightmapIndex)) { const FVector2D& Scale = Lightmap->GetCoordinateScale(); if (Scale.X > SMALL_NUMBER && Scale.Y > SMALL_NUMBER) { float LightmapFactorX = TexelFactor / Scale.X; float LightmapFactorY = TexelFactor / Scale.Y; FStreamingTexturePrimitiveInfo& StreamingTexture = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingTexture.Bounds = BoundingSphere; StreamingTexture.TexelFactor = FMath::Max(LightmapFactorX, LightmapFactorY); StreamingTexture.Texture = Lightmap->GetTexture(LightmapIndex); } } // Shadowmap FShadowMap2D* Shadowmap = ShadowMap ? ShadowMap->GetShadowMap2D() : nullptr; if (Shadowmap && Shadowmap->IsValid()) { const FVector2D& Scale = Shadowmap->GetCoordinateScale(); if (Scale.X > SMALL_NUMBER && Scale.Y > SMALL_NUMBER) { float ShadowmapFactorX = TexelFactor / Scale.X; float ShadowmapFactorY = TexelFactor / Scale.Y; FStreamingTexturePrimitiveInfo& StreamingTexture = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingTexture.Bounds = BoundingSphere; StreamingTexture.TexelFactor = FMath::Max(ShadowmapFactorX, ShadowmapFactorY); StreamingTexture.Texture = Shadowmap->GetTexture(); } } } // Weightmap for (int32 TextureIndex = 0; TextureIndex < WeightmapTextures.Num(); TextureIndex++) { FStreamingTexturePrimitiveInfo& StreamingWeightmap = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingWeightmap.Bounds = BoundingSphere; StreamingWeightmap.TexelFactor = TexelFactor; StreamingWeightmap.Texture = WeightmapTextures[TextureIndex]; } // Heightmap if (HeightmapTexture) { FStreamingTexturePrimitiveInfo& StreamingHeightmap = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingHeightmap.Bounds = BoundingSphere; float HeightmapTexelFactor = TexelFactor * (static_cast(HeightmapTexture->GetSizeY()) / (ComponentSizeQuads + 1)); StreamingHeightmap.TexelFactor = ForcedLOD >= 0 ? -13 + ForcedLOD : HeightmapTexelFactor; // Minus Value indicate ForcedLOD, 13 for 8k texture StreamingHeightmap.Texture = HeightmapTexture; } // XYOffset if (XYOffsetmapTexture) { FStreamingTexturePrimitiveInfo& StreamingXYOffset = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingXYOffset.Bounds = BoundingSphere; StreamingXYOffset.TexelFactor = TexelFactor; StreamingXYOffset.Texture = XYOffsetmapTexture; } #if WITH_EDITOR if (GIsEditor && EditToolRenderData && EditToolRenderData->DataTexture) { FStreamingTexturePrimitiveInfo& StreamingDatamap = *new(OutStreamingTextures)FStreamingTexturePrimitiveInfo; StreamingDatamap.Bounds = BoundingSphere; StreamingDatamap.TexelFactor = TexelFactor; StreamingDatamap.Texture = EditToolRenderData->DataTexture; } #endif } void ALandscapeProxy::ChangeLODDistanceFactor(float InLODDistanceFactor) { LODDistanceFactor = FMath::Clamp(InLODDistanceFactor, 0.1f, MAX_LANDSCAPE_LOD_DISTANCE_FACTOR); float LODFactor; switch (LODFalloff) { case ELandscapeLODFalloff::SquareRoot: LODFactor = FMath::Square(FMath::Min(LANDSCAPE_LOD_SQUARE_ROOT_FACTOR * LODDistanceFactor, MAX_LANDSCAPE_LOD_DISTANCE_FACTOR)); break; default: case ELandscapeLODFalloff::Linear: LODFactor = LODDistanceFactor; break; } if (LandscapeComponents.Num()) { int32 CompNum = LandscapeComponents.Num(); FLandscapeComponentSceneProxy** Proxies = new FLandscapeComponentSceneProxy*[CompNum]; for (int32 Idx = 0; Idx < CompNum; ++Idx) { Proxies[Idx] = (FLandscapeComponentSceneProxy*)(LandscapeComponents[Idx]->SceneProxy); } ENQUEUE_UNIQUE_RENDER_COMMAND_THREEPARAMETER( LandscapeChangeLODDistanceFactorCommand, FLandscapeComponentSceneProxy**, Proxies, Proxies, int32, CompNum, CompNum, float, InLODDistanceFactor, FMath::Sqrt(2.f * FMath::Square((float)SubsectionSizeQuads)) * LANDSCAPE_LOD_DISTANCE_FACTOR / LODFactor, { for (int32 Idx = 0; Idx < CompNum; ++Idx) { Proxies[Idx]->ChangeLODDistanceFactor_RenderThread(InLODDistanceFactor); } delete[] Proxies; } ); } }; void FLandscapeComponentSceneProxy::ChangeLODDistanceFactor_RenderThread(float InLODDistanceFactor) { LODDistance = InLODDistanceFactor; } void FLandscapeComponentSceneProxy::GetHeightfieldRepresentation(UTexture2D*& OutHeightmapTexture, UTexture2D*& OutDiffuseColorTexture, FHeightfieldComponentDescription& OutDescription) { OutHeightmapTexture = HeightmapTexture; OutDiffuseColorTexture = BaseColorForGITexture; OutDescription.HeightfieldScaleBias = HeightmapScaleBias; OutDescription.MinMaxUV = FVector4( HeightmapScaleBias.Z, HeightmapScaleBias.W, HeightmapScaleBias.Z + SubsectionSizeVerts * NumSubsections * HeightmapScaleBias.X, HeightmapScaleBias.W + SubsectionSizeVerts * NumSubsections * HeightmapScaleBias.Y); if (NumSubsections > 1) { OutDescription.MinMaxUV.Z -= HeightmapScaleBias.X; OutDescription.MinMaxUV.W -= HeightmapScaleBias.Y; } OutDescription.HeightfieldRect = FIntRect(SectionBase.X, SectionBase.Y, SectionBase.X + NumSubsections * SubsectionSizeQuads, SectionBase.Y + NumSubsections * SubsectionSizeQuads); OutDescription.NumSubsections = NumSubsections; OutDescription.SubsectionScaleAndBias = FVector4(SubsectionSizeQuads, SubsectionSizeQuads, HeightmapSubsectionOffsetU, HeightmapSubsectionOffsetV); } // // FLandscapeNeighborInfo // void FLandscapeNeighborInfo::RegisterNeighbors() { if (!bRegistered) { // Register ourselves in the map. TMap& SceneProxyMap = SharedSceneProxyMap.FindOrAdd(LandscapeKey); const FLandscapeNeighborInfo* Existing = SceneProxyMap.FindRef(ComponentBase); if (Existing == nullptr)//(ensure(Existing == nullptr)) { SceneProxyMap.Add(ComponentBase, this); bRegistered = true; // Find Neighbors Neighbors[0] = SceneProxyMap.FindRef(ComponentBase + FIntPoint(0, -1)); Neighbors[1] = SceneProxyMap.FindRef(ComponentBase + FIntPoint(-1, 0)); Neighbors[2] = SceneProxyMap.FindRef(ComponentBase + FIntPoint(1, 0)); Neighbors[3] = SceneProxyMap.FindRef(ComponentBase + FIntPoint(0, 1)); // Add ourselves to our neighbors if (Neighbors[0]) { Neighbors[0]->Neighbors[3] = this; } if (Neighbors[1]) { Neighbors[1]->Neighbors[2] = this; } if (Neighbors[2]) { Neighbors[2]->Neighbors[1] = this; } if (Neighbors[3]) { Neighbors[3]->Neighbors[0] = this; } } else { UE_LOG(LogLandscape, Warning, TEXT("Duplicate ComponentBase %d, %d"), ComponentBase.X, ComponentBase.Y); } } } void FLandscapeNeighborInfo::UnregisterNeighbors() { if (bRegistered) { // Remove ourselves from the map TMap* SceneProxyMap = SharedSceneProxyMap.Find(LandscapeKey); check(SceneProxyMap); const FLandscapeNeighborInfo* MapEntry = SceneProxyMap->FindRef(ComponentBase); if (MapEntry == this) //(/*ensure*/(MapEntry == this)) { SceneProxyMap->Remove(ComponentBase); if (SceneProxyMap->Num() == 0) { // remove the entire LandscapeKey entry as this is the last scene proxy SharedSceneProxyMap.Remove(LandscapeKey); } else { // remove reference to us from our neighbors if (Neighbors[0]) { Neighbors[0]->Neighbors[3] = nullptr; } if (Neighbors[1]) { Neighbors[1]->Neighbors[2] = nullptr; } if (Neighbors[2]) { Neighbors[2]->Neighbors[1] = nullptr; } if (Neighbors[3]) { Neighbors[3]->Neighbors[0] = nullptr; } } } } } // // FLandscapeMeshProxySceneProxy // FLandscapeMeshProxySceneProxy::FLandscapeMeshProxySceneProxy(UStaticMeshComponent* InComponent, const FGuid& InGuid, const TArray& InProxyComponentBases, int8 InProxyLOD) : FStaticMeshSceneProxy(InComponent) { if (!IsComponentLevelVisible()) { bNeedsLevelAddedToWorldNotification = true; } ProxyNeighborInfos.Empty(InProxyComponentBases.Num()); for (FIntPoint ComponentBase : InProxyComponentBases) { new(ProxyNeighborInfos) FLandscapeNeighborInfo(InComponent->GetWorld(), InGuid, ComponentBase, nullptr, InProxyLOD, 0); } } void FLandscapeMeshProxySceneProxy::CreateRenderThreadResources() { FStaticMeshSceneProxy::CreateRenderThreadResources(); if (IsComponentLevelVisible()) { for (FLandscapeNeighborInfo& Info : ProxyNeighborInfos) { Info.RegisterNeighbors(); } } } void FLandscapeMeshProxySceneProxy::OnLevelAddedToWorld() { for (FLandscapeNeighborInfo& Info : ProxyNeighborInfos) { Info.RegisterNeighbors(); } } FLandscapeMeshProxySceneProxy::~FLandscapeMeshProxySceneProxy() { for (FLandscapeNeighborInfo& Info : ProxyNeighborInfos) { Info.UnregisterNeighbors(); } } FPrimitiveSceneProxy* ULandscapeMeshProxyComponent::CreateSceneProxy() { if (StaticMesh == NULL || StaticMesh->RenderData == NULL || StaticMesh->RenderData->LODResources.Num() == 0 || StaticMesh->RenderData->LODResources[0].VertexBuffer.GetNumVertices() == 0) { return NULL; } return new FLandscapeMeshProxySceneProxy(this, LandscapeGuid, ProxyComponentBases, ProxyLOD); }