You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
1903 lines
63 KiB
C++
1903 lines
63 KiB
C++
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "Landscape.h"
|
|
#include "Engine/Engine.h"
|
|
#include "EngineGlobals.h"
|
|
#include "UnrealEngine.h"
|
|
#include "EngineUtils.h"
|
|
#include "Shader.h"
|
|
#include "ShaderParameterUtils.h"
|
|
#include "MeshMaterialShader.h"
|
|
#include "EngineModule.h"
|
|
#include "RendererInterface.h"
|
|
#include "DrawingPolicy.h"
|
|
#include "GenericOctreePublic.h"
|
|
#include "PrimitiveSceneInfo.h"
|
|
#include "MaterialCompiler.h"
|
|
#include "LandscapeGrassType.h"
|
|
#include "Materials/MaterialExpressionLandscapeGrassOutput.h"
|
|
#include "EdGraph/EdGraphNode.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "ContentStreaming.h"
|
|
#include "LandscapeDataAccess.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "Landscape"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogGrass, Log, All);
|
|
|
|
static TAutoConsoleVariable<float> CVarGuardBandMultiplier(
|
|
TEXT("grass.GuardBandMultiplier"),
|
|
1.3f,
|
|
TEXT("Used to control discarding in the grass system. Approximate range, 1-4. Multiplied by the cull distance to control when we add grass components."));
|
|
|
|
static TAutoConsoleVariable<float> CVarGuardBandDiscardMultiplier(
|
|
TEXT("grass.GuardBandDiscardMultiplier"),
|
|
1.4f,
|
|
TEXT("Used to control discarding in the grass system. Approximate range, 1-4. Multiplied by the cull distance to control when we discard grass components."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarMinFramesToKeepGrass(
|
|
TEXT("grass.MinFramesToKeepGrass"),
|
|
30,
|
|
TEXT("Minimum number of frames before cached grass can be discarded; used to prevent thrashing."));
|
|
|
|
static TAutoConsoleVariable<float> CVarMinTimeToKeepGrass(
|
|
TEXT("grass.MinTimeToKeepGrass"),
|
|
5.0f,
|
|
TEXT("Minimum number of seconds before cached grass can be discarded; used to prevent thrashing."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaxInstancesPerComponent(
|
|
TEXT("grass.MaxInstancesPerComponent"),
|
|
65536,
|
|
TEXT("Used to control the number of hierarchical components created. More can be more efficient, but can be hitchy as new components come into range"));
|
|
|
|
static TAutoConsoleVariable<int32> CVarMaxAsyncTasks(
|
|
TEXT("grass.MaxAsyncTasks"),
|
|
4,
|
|
TEXT("Used to control the number of hierarchical components created at a time."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarUseHaltonDistribution(
|
|
TEXT("grass.UseHaltonDistribution"),
|
|
0,
|
|
TEXT("Used to control the distribution of grass instances. If non-zero, use a halton sequence."));
|
|
|
|
static TAutoConsoleVariable<float> CVarGrassDensityScale(
|
|
TEXT("grass.densityScale"),
|
|
1,
|
|
TEXT("Multiplier on all grass densities. Do a grass.flushcache or grass.flushcachepie afterwards as appropriate."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarGrassEnable(
|
|
TEXT("grass.Enable"),
|
|
1,
|
|
TEXT("1: Enable Grass; 0: Disable Grass"));
|
|
|
|
static TAutoConsoleVariable<int32> CVarUseStreamingManagerForCameras(
|
|
TEXT("grass.UseStreamingManagerForCameras"),
|
|
1,
|
|
TEXT("1: Use Streaming Manager; 0: Use ViewLocationsRenderedLastFrame"));
|
|
|
|
static TAutoConsoleVariable<int32> CVarCullSubsections(
|
|
TEXT("grass.CullSubsections"),
|
|
1,
|
|
TEXT("1: Cull each foliage component; 0: Cull only based on the landscape component."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarDisableGPUCull(
|
|
TEXT("grass.DisableGPUCull"),
|
|
0,
|
|
TEXT("For debugging. Set this to zero to see where the grass is generated. Useful for tweaking the guard bands."));
|
|
|
|
static TAutoConsoleVariable<int32> CVarPrerenderGrassmaps(
|
|
TEXT("grass.PrerenderGrassmaps"),
|
|
1,
|
|
TEXT("1: Pre-render grass maps for all components in the editor; 0: Generate grass maps on demand while moving through the editor"));
|
|
|
|
DECLARE_CYCLE_STAT(TEXT("Grass Async Build Time"), STAT_FoliageGrassAsyncBuildTime, STATGROUP_Foliage);
|
|
DECLARE_CYCLE_STAT(TEXT("Grass Start Comp"), STAT_FoliageGrassStartComp, STATGROUP_Foliage);
|
|
DECLARE_CYCLE_STAT(TEXT("Grass End Comp"), STAT_FoliageGrassEndComp, STATGROUP_Foliage);
|
|
DECLARE_CYCLE_STAT(TEXT("Grass Destroy Comps"), STAT_FoliageGrassDestoryComp, STATGROUP_Foliage);
|
|
DECLARE_CYCLE_STAT(TEXT("Grass Update"), STAT_GrassUpdate, STATGROUP_Foliage);
|
|
|
|
//
|
|
// Grass weightmap rendering
|
|
//
|
|
class FLandscapeGrassWeightVS : public FMeshMaterialShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FLandscapeGrassWeightVS, MeshMaterial);
|
|
|
|
FShaderParameter RenderOffsetParameter;
|
|
|
|
protected:
|
|
|
|
FLandscapeGrassWeightVS()
|
|
{}
|
|
|
|
FLandscapeGrassWeightVS(const FMeshMaterialShaderType::CompiledShaderInitializerType& Initializer)
|
|
: FMeshMaterialShader(Initializer)
|
|
{
|
|
RenderOffsetParameter.Bind(Initializer.ParameterMap, TEXT("RenderOffset"));
|
|
}
|
|
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform, const FMaterial* Material, const FVertexFactoryType* VertexFactoryType)
|
|
{
|
|
// Only compile for LandscapeVertexFactory
|
|
return (Material->IsUsedWithLandscape() || Material->IsSpecialEngineMaterial()) && IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4) && (VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLandscapeVertexFactory"), FNAME_Find))) && !IsConsolePlatform(Platform);
|
|
}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, const FMaterialRenderProxy* MaterialRenderProxy, const FMaterial& MaterialResource, const FSceneView& View, const FVector2D& RenderOffset)
|
|
{
|
|
FMeshMaterialShader::SetParameters(RHICmdList, GetVertexShader(), MaterialRenderProxy, MaterialResource, View, ESceneRenderTargetsMode::DontSet);
|
|
SetShaderValue(RHICmdList, GetVertexShader(), RenderOffsetParameter, RenderOffset);
|
|
}
|
|
|
|
void SetMesh(FRHICommandList& RHICmdList, const FVertexFactory* VertexFactory, const FSceneView& View, const FPrimitiveSceneProxy* Proxy, const FMeshBatchElement& BatchElement, float DitheredLODTransitionValue)
|
|
{
|
|
FMeshMaterialShader::SetMesh(RHICmdList, GetVertexShader(), VertexFactory, View, Proxy, BatchElement,DitheredLODTransitionValue);
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FMeshMaterialShader::Serialize(Ar);
|
|
Ar << RenderOffsetParameter;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLandscapeGrassWeightVS, TEXT("LandscapeGrassWeight"), TEXT("VSMain"), SF_Vertex);
|
|
|
|
class FLandscapeGrassWeightPS : public FMeshMaterialShader
|
|
{
|
|
DECLARE_SHADER_TYPE(FLandscapeGrassWeightPS, MeshMaterial);
|
|
FShaderParameter OutputPassParameter;
|
|
public:
|
|
|
|
static bool ShouldCache(EShaderPlatform Platform, const FMaterial* Material, const FVertexFactoryType* VertexFactoryType)
|
|
{
|
|
// Only compile for LandscapeVertexFactory
|
|
return (Material->IsUsedWithLandscape() || Material->IsSpecialEngineMaterial()) && IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4) && (VertexFactoryType == FindVertexFactoryType(FName(TEXT("FLandscapeVertexFactory"), FNAME_Find))) && !IsConsolePlatform(Platform);
|
|
}
|
|
|
|
FLandscapeGrassWeightPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer)
|
|
: FMeshMaterialShader(Initializer)
|
|
{
|
|
OutputPassParameter.Bind(Initializer.ParameterMap, TEXT("OutputPass"));
|
|
}
|
|
|
|
FLandscapeGrassWeightPS()
|
|
{}
|
|
|
|
void SetParameters(FRHICommandList& RHICmdList, const FMaterialRenderProxy* MaterialRenderProxy, const FMaterial& MaterialResource, const FSceneView* View, int32 OutputPass)
|
|
{
|
|
FMeshMaterialShader::SetParameters(RHICmdList, GetPixelShader(), MaterialRenderProxy, MaterialResource, *View, ESceneRenderTargetsMode::DontSet);
|
|
if (OutputPassParameter.IsBound())
|
|
{
|
|
SetShaderValue(RHICmdList, GetPixelShader(), OutputPassParameter, OutputPass);
|
|
}
|
|
}
|
|
|
|
void SetMesh(FRHICommandList& RHICmdList, const FVertexFactory* VertexFactory, const FSceneView& View, const FPrimitiveSceneProxy* Proxy, const FMeshBatchElement& BatchElement, float DitheredLODTransitionValue)
|
|
{
|
|
FMeshMaterialShader::SetMesh(RHICmdList, GetPixelShader(), VertexFactory, View, Proxy, BatchElement,DitheredLODTransitionValue);
|
|
}
|
|
|
|
virtual bool Serialize(FArchive& Ar) override
|
|
{
|
|
bool bShaderHasOutdatedParameters = FMeshMaterialShader::Serialize(Ar);
|
|
Ar << OutputPassParameter;
|
|
return bShaderHasOutdatedParameters;
|
|
}
|
|
};
|
|
|
|
IMPLEMENT_MATERIAL_SHADER_TYPE(, FLandscapeGrassWeightPS, TEXT("LandscapeGrassWeight"), TEXT("PSMain"), SF_Pixel);
|
|
|
|
|
|
/**
|
|
* Drawing policy used to write out landscape grass weightmap.
|
|
*/
|
|
class FLandscapeGrassWeightDrawingPolicy : public FMeshDrawingPolicy
|
|
{
|
|
public:
|
|
FLandscapeGrassWeightDrawingPolicy(
|
|
const FVertexFactory* InVertexFactory,
|
|
const FMaterialRenderProxy* InMaterialRenderProxy,
|
|
const FMaterial& InMaterialResource
|
|
)
|
|
:
|
|
FMeshDrawingPolicy(InVertexFactory, InMaterialRenderProxy, InMaterialResource, false, false)
|
|
{
|
|
PixelShader = InMaterialResource.GetShader<FLandscapeGrassWeightPS>(InVertexFactory->GetType());
|
|
VertexShader = InMaterialResource.GetShader<FLandscapeGrassWeightVS>(VertexFactory->GetType());
|
|
}
|
|
|
|
// FMeshDrawingPolicy interface.
|
|
bool Matches(const FLandscapeGrassWeightDrawingPolicy& Other) const
|
|
{
|
|
return FMeshDrawingPolicy::Matches(Other)
|
|
&& VertexShader == Other.VertexShader
|
|
&& PixelShader == Other.PixelShader;
|
|
}
|
|
|
|
void SetSharedState(FRHICommandList& RHICmdList, const FSceneView* View, const ContextDataType PolicyContext, int32 OutputPass, const FVector2D& RenderOffset) const
|
|
{
|
|
// Set the shader parameters for the material.
|
|
VertexShader->SetParameters(RHICmdList, MaterialRenderProxy, *MaterialResource, *View, RenderOffset);
|
|
PixelShader->SetParameters(RHICmdList, MaterialRenderProxy, *MaterialResource, View, OutputPass);
|
|
|
|
// Set the shared mesh resources.
|
|
FMeshDrawingPolicy::SetSharedState(RHICmdList, View, PolicyContext);
|
|
}
|
|
|
|
FBoundShaderStateInput GetBoundShaderStateInput(ERHIFeatureLevel::Type InFeatureLevel)
|
|
{
|
|
return FBoundShaderStateInput(
|
|
FMeshDrawingPolicy::GetVertexDeclaration(),
|
|
VertexShader->GetVertexShader(),
|
|
FHullShaderRHIRef(),
|
|
FDomainShaderRHIRef(),
|
|
PixelShader->GetPixelShader(),
|
|
NULL);
|
|
}
|
|
|
|
void SetMeshRenderState(
|
|
FRHICommandList& RHICmdList,
|
|
const FSceneView& View,
|
|
const FPrimitiveSceneProxy* PrimitiveSceneProxy,
|
|
const FMeshBatch& Mesh,
|
|
int32 BatchElementIndex,
|
|
bool bBackFace,
|
|
float DitheredLODTransitionValue,
|
|
const ElementDataType& ElementData,
|
|
const ContextDataType PolicyContext
|
|
) const
|
|
{
|
|
const FMeshBatchElement& BatchElement = Mesh.Elements[BatchElementIndex];
|
|
VertexShader->SetMesh(RHICmdList, VertexFactory, View, PrimitiveSceneProxy, BatchElement,DitheredLODTransitionValue);
|
|
PixelShader->SetMesh(RHICmdList, VertexFactory, View, PrimitiveSceneProxy, BatchElement,DitheredLODTransitionValue);
|
|
FMeshDrawingPolicy::SetMeshRenderState(RHICmdList, View, PrimitiveSceneProxy, Mesh, BatchElementIndex, bBackFace, DitheredLODTransitionValue, ElementData, PolicyContext);
|
|
}
|
|
|
|
friend int32 CompareDrawingPolicy(const FLandscapeGrassWeightDrawingPolicy& A, const FLandscapeGrassWeightDrawingPolicy& B)
|
|
{
|
|
COMPAREDRAWINGPOLICYMEMBERS(VertexShader);
|
|
COMPAREDRAWINGPOLICYMEMBERS(PixelShader);
|
|
COMPAREDRAWINGPOLICYMEMBERS(VertexFactory);
|
|
COMPAREDRAWINGPOLICYMEMBERS(MaterialRenderProxy);
|
|
COMPAREDRAWINGPOLICYMEMBERS(bIsTwoSidedMaterial);
|
|
return 0;
|
|
}
|
|
private:
|
|
FLandscapeGrassWeightVS* VertexShader;
|
|
FLandscapeGrassWeightPS* PixelShader;
|
|
};
|
|
|
|
// data also accessible by render thread
|
|
class FLandscapeGrassWeightExporter_RenderThread
|
|
{
|
|
FLandscapeGrassWeightExporter_RenderThread(int32 InNumPasses)
|
|
: RenderTargetResource(nullptr)
|
|
, NumPasses(InNumPasses)
|
|
{}
|
|
|
|
friend class FLandscapeGrassWeightExporter;
|
|
|
|
public:
|
|
virtual ~FLandscapeGrassWeightExporter_RenderThread()
|
|
{}
|
|
|
|
struct FComponentInfo
|
|
{
|
|
ULandscapeComponent* Component;
|
|
FVector2D ViewOffset;
|
|
int32 PixelOffsetX;
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo;
|
|
|
|
FComponentInfo(ULandscapeComponent* InComponent, FVector2D& InViewOffset, int32 InPixelOffsetX)
|
|
: Component(InComponent)
|
|
, ViewOffset(InViewOffset)
|
|
, PixelOffsetX(InPixelOffsetX)
|
|
, PrimitiveSceneInfo(InComponent->SceneProxy->GetPrimitiveSceneInfo())
|
|
{}
|
|
};
|
|
|
|
FTextureRenderTarget2DResource* RenderTargetResource;
|
|
TArray<FComponentInfo> ComponentInfos;
|
|
FIntPoint TargetSize;
|
|
int32 NumPasses;
|
|
float PassOffsetX;
|
|
FVector ViewOrigin;
|
|
FMatrix ViewRotationMatrix;
|
|
FMatrix ProjectionMatrix;
|
|
|
|
void RenderLandscapeComponentToTexture_RenderThread(FRHICommandListImmediate& RHICmdList)
|
|
{
|
|
FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(RenderTargetResource, NULL, FEngineShowFlags(ESFIM_Game)).SetWorldTimes(FApp::GetCurrentTime() - GStartTime, FApp::GetDeltaTime(), FApp::GetCurrentTime() - GStartTime));
|
|
|
|
#if WITH_EDITOR
|
|
ViewFamily.LandscapeLODOverride = 0; // Force LOD render
|
|
#endif
|
|
|
|
FSceneViewInitOptions ViewInitOptions;
|
|
ViewInitOptions.SetViewRectangle(FIntRect(0, 0, TargetSize.X, TargetSize.Y));
|
|
ViewInitOptions.ViewOrigin = ViewOrigin;
|
|
ViewInitOptions.ViewRotationMatrix = ViewRotationMatrix;
|
|
ViewInitOptions.ProjectionMatrix = ProjectionMatrix;
|
|
ViewInitOptions.ViewFamily = &ViewFamily;
|
|
|
|
GetRendererModule().CreateAndInitSingleView(RHICmdList, &ViewFamily, &ViewInitOptions);
|
|
|
|
const FSceneView* View = ViewFamily.Views[0];
|
|
RHICmdList.SetViewport(View->ViewRect.Min.X, View->ViewRect.Min.Y, 0.0f, View->ViewRect.Max.X, View->ViewRect.Max.Y, 1.0f);
|
|
RHICmdList.SetBlendState(TStaticBlendState<>::GetRHI());
|
|
RHICmdList.SetRasterizerState(TStaticRasterizerState<FM_Solid, CM_None>::GetRHI());
|
|
RHICmdList.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
|
|
RHICmdList.SetScissorRect(false, 0, 0, 0, 0);
|
|
|
|
for (auto& ComponentInfo : ComponentInfos)
|
|
{
|
|
FPrimitiveSceneInfo* PrimitiveSceneInfo = ComponentInfo.PrimitiveSceneInfo;
|
|
for (int32 PassIdx = 0; PassIdx < NumPasses; PassIdx++)
|
|
{
|
|
for (int32 StaticMeshIdx = 0; StaticMeshIdx < PrimitiveSceneInfo->StaticMeshes.Num(); StaticMeshIdx++)
|
|
{
|
|
FMeshBatch& Mesh = *(FMeshBatch*)(&PrimitiveSceneInfo->StaticMeshes[StaticMeshIdx]);
|
|
|
|
FLandscapeGrassWeightDrawingPolicy DrawingPolicy(Mesh.VertexFactory, Mesh.MaterialRenderProxy, *Mesh.MaterialRenderProxy->GetMaterial(GMaxRHIFeatureLevel));
|
|
RHICmdList.BuildAndSetLocalBoundShaderState(DrawingPolicy.GetBoundShaderStateInput(GMaxRHIFeatureLevel));
|
|
DrawingPolicy.SetSharedState(RHICmdList, View, FLandscapeGrassWeightDrawingPolicy::ContextDataType(), PassIdx, ComponentInfo.ViewOffset + FVector2D(PassOffsetX * PassIdx, 0));
|
|
|
|
// The first batch element contains the grass batch for the entire component
|
|
DrawingPolicy.SetMeshRenderState(RHICmdList, *View, PrimitiveSceneInfo->Proxy, Mesh, 0, false, 0.0f, FMeshDrawingPolicy::ElementDataType(), FLandscapeGrassWeightDrawingPolicy::ContextDataType());
|
|
DrawingPolicy.DrawMesh(RHICmdList, Mesh, 0);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
class FLandscapeGrassWeightExporter : public FLandscapeGrassWeightExporter_RenderThread
|
|
{
|
|
ALandscapeProxy* LandscapeProxy;
|
|
int32 ComponentSizeQuads;
|
|
int32 ComponentSizeVerts;
|
|
TArray<ULandscapeGrassType*> GrassTypes;
|
|
UTextureRenderTarget2D* RenderTargetTexture;
|
|
|
|
public:
|
|
|
|
FLandscapeGrassWeightExporter(ALandscapeProxy* InLandscapeProxy, const TArray<ULandscapeComponent*>& InLandscapeComponents, const TArray<ULandscapeGrassType*> InGrassTypes)
|
|
: FLandscapeGrassWeightExporter_RenderThread((InGrassTypes.Num() + 5) / 4)
|
|
, LandscapeProxy(InLandscapeProxy)
|
|
, ComponentSizeQuads(InLandscapeProxy->ComponentSizeQuads)
|
|
, ComponentSizeVerts(ComponentSizeQuads + 1)
|
|
, GrassTypes(InGrassTypes)
|
|
, RenderTargetTexture(nullptr)
|
|
{
|
|
check(InLandscapeComponents.Num());
|
|
|
|
TargetSize = FIntPoint(ComponentSizeVerts * NumPasses * InLandscapeComponents.Num(), ComponentSizeVerts);
|
|
FIntPoint TargetSizeMinusOne(TargetSize - FIntPoint(1,1));
|
|
PassOffsetX = 2.0f * (float)ComponentSizeVerts / (float)TargetSize.X;
|
|
|
|
for (int32 Idx = 0; Idx<InLandscapeComponents.Num();Idx++)
|
|
{
|
|
ULandscapeComponent* Component = InLandscapeComponents[Idx];
|
|
|
|
FIntPoint ComponentOffset = (Component->GetSectionBase() - LandscapeProxy->LandscapeSectionOffset);
|
|
int32 PixelOffsetX = Idx * NumPasses * ComponentSizeVerts;
|
|
|
|
FVector2D ViewOffset(-ComponentOffset.X,ComponentOffset.Y);
|
|
ViewOffset.X += PixelOffsetX;
|
|
ViewOffset /= (FVector2D(TargetSize) * 0.5f);
|
|
|
|
new(ComponentInfos)FComponentInfo(Component, ViewOffset, PixelOffsetX);
|
|
}
|
|
|
|
// center of target area in world
|
|
FVector TargetCenter = LandscapeProxy->GetTransform().TransformPosition(FVector(TargetSizeMinusOne, 0.f)*0.5f);
|
|
|
|
// extent of target in world space
|
|
FVector TargetExtent = FVector(TargetSize, 0.f)*LandscapeProxy->GetActorScale()*0.5f;
|
|
|
|
ViewOrigin = TargetCenter;
|
|
ViewRotationMatrix = FInverseRotationMatrix(LandscapeProxy->GetActorRotation());
|
|
ViewRotationMatrix *= FMatrix(FPlane(1, 0, 0, 0),
|
|
FPlane(0, -1, 0, 0),
|
|
FPlane(0, 0, -1, 0),
|
|
FPlane(0, 0, 0, 1));
|
|
|
|
const float ZOffset = WORLD_MAX;
|
|
ProjectionMatrix = FReversedZOrthoMatrix(
|
|
TargetExtent.X,
|
|
TargetExtent.Y,
|
|
0.5f / ZOffset,
|
|
ZOffset);
|
|
|
|
RenderTargetTexture = NewObject<UTextureRenderTarget2D>();
|
|
check(RenderTargetTexture);
|
|
RenderTargetTexture->ClearColor = FLinearColor::Transparent;
|
|
RenderTargetTexture->TargetGamma = 1.f;
|
|
RenderTargetTexture->InitCustomFormat(TargetSize.X, TargetSize.Y, PF_B8G8R8A8, false);
|
|
RenderTargetResource = RenderTargetTexture->GameThread_GetRenderTargetResource()->GetTextureRenderTarget2DResource();
|
|
|
|
// render
|
|
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
|
|
FDrawSceneCommand,
|
|
FLandscapeGrassWeightExporter_RenderThread*, Exporter, this,
|
|
{
|
|
Exporter->RenderLandscapeComponentToTexture_RenderThread(RHICmdList);
|
|
FlushPendingDeleteRHIResources_RenderThread();
|
|
});
|
|
}
|
|
|
|
void ApplyResults()
|
|
{
|
|
TArray<FColor> Samples;
|
|
Samples.SetNumUninitialized(TargetSize.X*TargetSize.Y);
|
|
|
|
// Copy the contents of the remote texture to system memory
|
|
FReadSurfaceDataFlags ReadSurfaceDataFlags;
|
|
ReadSurfaceDataFlags.SetLinearToGamma(false);
|
|
RenderTargetResource->ReadPixels(Samples, ReadSurfaceDataFlags, FIntRect(0, 0, TargetSize.X, TargetSize.Y));
|
|
|
|
for (auto& ComponentInfo : ComponentInfos)
|
|
{
|
|
FLandscapeComponentGrassData* NewGrassData = new FLandscapeComponentGrassData(ComponentInfo.Component->MaterialInstance->GetMaterial()->StateId);
|
|
|
|
NewGrassData->HeightData.Empty(FMath::Square(ComponentSizeVerts));
|
|
|
|
TArray<TArray<uint8>*> GrassWeightArrays;
|
|
GrassWeightArrays.Empty(GrassTypes.Num());
|
|
for (auto GrassType : GrassTypes)
|
|
{
|
|
int32 Index = GrassWeightArrays.Add(&NewGrassData->WeightData.Add(GrassType));
|
|
GrassWeightArrays[Index]->Empty(FMath::Square(ComponentSizeVerts));
|
|
}
|
|
|
|
for (int32 PassIdx = 0; PassIdx < NumPasses; PassIdx++)
|
|
{
|
|
FColor* SampleData = &Samples[ComponentInfo.PixelOffsetX + PassIdx*ComponentSizeVerts];
|
|
if (PassIdx == 0)
|
|
{
|
|
NewGrassData->HeightData.Empty(FMath::Square(ComponentSizeVerts));
|
|
|
|
for (int32 y = 0; y < ComponentSizeVerts; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeVerts; x++)
|
|
{
|
|
FColor& Sample = SampleData[x + y * TargetSize.X];
|
|
uint16 Height = (((uint16)Sample.R) << 8) + (uint16)(Sample.G);
|
|
NewGrassData->HeightData.Add(Height);
|
|
GrassWeightArrays[0]->Add(Sample.B);
|
|
if (GrassTypes.Num() > 1)
|
|
{
|
|
GrassWeightArrays[1]->Add(Sample.A);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int32 y = 0; y < ComponentSizeVerts; y++)
|
|
{
|
|
for (int32 x = 0; x < ComponentSizeVerts; x++)
|
|
{
|
|
FColor& Sample = SampleData[x + y * TargetSize.X];
|
|
|
|
int32 TypeIdx = PassIdx * 4 - 2;
|
|
GrassWeightArrays[TypeIdx++]->Add(Sample.R);
|
|
if (TypeIdx < GrassTypes.Num())
|
|
{
|
|
GrassWeightArrays[TypeIdx++]->Add(Sample.G);
|
|
if (TypeIdx < GrassTypes.Num())
|
|
{
|
|
GrassWeightArrays[TypeIdx++]->Add(Sample.B);
|
|
if (TypeIdx < GrassTypes.Num())
|
|
{
|
|
GrassWeightArrays[TypeIdx++]->Add(Sample.A);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Assign the new data (thread-safe)
|
|
ComponentInfo.Component->GrassData = MakeShareable(NewGrassData);
|
|
}
|
|
}
|
|
|
|
void AddReferencedObjects(UObject* InThis, FReferenceCollector& Collector)
|
|
{
|
|
if (RenderTargetTexture)
|
|
{
|
|
Collector.AddReferencedObject(RenderTargetTexture);
|
|
}
|
|
|
|
if (LandscapeProxy)
|
|
{
|
|
Collector.AddReferencedObject(LandscapeProxy);
|
|
}
|
|
|
|
for (auto& Info : ComponentInfos)
|
|
{
|
|
if (Info.Component)
|
|
{
|
|
Collector.AddReferencedObject(Info.Component);
|
|
}
|
|
}
|
|
|
|
for (auto GrassType : GrassTypes)
|
|
{
|
|
if (GrassType)
|
|
{
|
|
Collector.AddReferencedObject(GrassType);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
#if WITH_EDITOR
|
|
|
|
bool ULandscapeComponent::IsGrassMapOutdated() const
|
|
{
|
|
if (GrassData->HasData())
|
|
{
|
|
if (GrassData->MaterialStateId != MaterialInstance->GetMaterial()->StateId)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ULandscapeComponent::CanRenderGrassMap() const
|
|
{
|
|
// Check we can render
|
|
UWorld* World = GetWorld();
|
|
if (!GIsEditor || GUsingNullRHI || !World || World->IsGameWorld() || World->FeatureLevel < ERHIFeatureLevel::SM4 || !SceneProxy)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check we can render the material
|
|
if (!MaterialInstance->GetMaterialResource(GetWorld()->FeatureLevel)->HasValidGameThreadShaderMap())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool IsTextureStreamedForGrassMapRender(UTexture2D* InTexture)
|
|
{
|
|
if (!InTexture || InTexture->ResidentMips != InTexture->GetNumMips()
|
|
|| !InTexture->Resource || ((FTexture2DResource*)InTexture->Resource)->GetCurrentFirstMip() > 0)
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ULandscapeComponent::AreTexturesStreamedForGrassMapRender() const
|
|
{
|
|
// Check for valid heightmap that is fully streamed in
|
|
if (!IsTextureStreamedForGrassMapRender(HeightmapTexture))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check for valid weightmaps that is fully streamed in
|
|
for (auto WeightmapTexture : WeightmapTextures)
|
|
{
|
|
if (!IsTextureStreamedForGrassMapRender(WeightmapTexture))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void ULandscapeComponent::RenderGrassMap()
|
|
{
|
|
UMaterialInterface* Material = GetLandscapeMaterial();
|
|
if (CanRenderGrassMap())
|
|
{
|
|
TArray<const UMaterialExpressionLandscapeGrassOutput*> GrassExpressions;
|
|
Material->GetMaterial()->GetAllExpressionsOfType<UMaterialExpressionLandscapeGrassOutput>(GrassExpressions);
|
|
if (GrassExpressions.Num() > 0)
|
|
{
|
|
TArray<ULandscapeGrassType*> GrassTypes;
|
|
GrassTypes.Empty(GrassExpressions[0]->GrassTypes.Num());
|
|
for (auto& GrassTypeInput : GrassExpressions[0]->GrassTypes)
|
|
{
|
|
if (GrassTypeInput.GrassType)
|
|
{
|
|
GrassTypes.Add(GrassTypeInput.GrassType);
|
|
}
|
|
}
|
|
|
|
TArray<ULandscapeComponent*> LandscapeComponents;
|
|
LandscapeComponents.Add(this);
|
|
|
|
FLandscapeGrassWeightExporter Exporter(GetLandscapeProxy(), LandscapeComponents, GrassTypes);
|
|
Exporter.ApplyResults();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
void ULandscapeComponent::RemoveGrassMap()
|
|
{
|
|
GrassData = MakeShareable(new FLandscapeComponentGrassData());
|
|
}
|
|
|
|
void ALandscapeProxy::RenderGrassMaps(const TArray<ULandscapeComponent*>& InLandscapeComponents, const TArray<ULandscapeGrassType*>& GrassTypes)
|
|
{
|
|
FLandscapeGrassWeightExporter Exporter(this, InLandscapeComponents, GrassTypes);
|
|
Exporter.ApplyResults();
|
|
}
|
|
|
|
#endif
|
|
|
|
//
|
|
// UMaterialExpressionLandscapeGrassOutput
|
|
//
|
|
UMaterialExpressionLandscapeGrassOutput::UMaterialExpressionLandscapeGrassOutput(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
// Structure to hold one-time initialization
|
|
struct FConstructorStatics
|
|
{
|
|
FString STRING_Landscape;
|
|
FName NAME_Grass;
|
|
FConstructorStatics()
|
|
: STRING_Landscape(LOCTEXT("Landscape", "Landscape").ToString())
|
|
, NAME_Grass("Grass")
|
|
{
|
|
}
|
|
};
|
|
static FConstructorStatics ConstructorStatics;
|
|
|
|
MenuCategories.Add(ConstructorStatics.STRING_Landscape);
|
|
|
|
// No outputs
|
|
Outputs.Reset();
|
|
|
|
// Default input
|
|
new(GrassTypes)FGrassInput(ConstructorStatics.NAME_Grass);
|
|
}
|
|
|
|
int32 UMaterialExpressionLandscapeGrassOutput::Compile(class FMaterialCompiler* Compiler, int32 OutputIndex, int32 MultiplexIndex)
|
|
{
|
|
if (GrassTypes.IsValidIndex(OutputIndex))
|
|
{
|
|
if (GrassTypes[OutputIndex].Input.Expression)
|
|
{
|
|
return Compiler->CustomOutput(this, OutputIndex, GrassTypes[OutputIndex].Input.Compile(Compiler, MultiplexIndex));
|
|
}
|
|
else
|
|
{
|
|
return CompilerError(Compiler, TEXT("Input missing"));
|
|
}
|
|
}
|
|
|
|
return INDEX_NONE;
|
|
}
|
|
|
|
void UMaterialExpressionLandscapeGrassOutput::GetCaption(TArray<FString>& OutCaptions) const
|
|
{
|
|
OutCaptions.Add(TEXT("Grass"));
|
|
}
|
|
|
|
const TArray<FExpressionInput*> UMaterialExpressionLandscapeGrassOutput::GetInputs()
|
|
{
|
|
TArray<FExpressionInput*> OutInputs;
|
|
for (auto& GrassType : GrassTypes)
|
|
{
|
|
OutInputs.Add(&GrassType.Input);
|
|
}
|
|
return OutInputs;
|
|
}
|
|
|
|
FExpressionInput* UMaterialExpressionLandscapeGrassOutput::GetInput(int32 InputIndex)
|
|
{
|
|
return &GrassTypes[InputIndex].Input;
|
|
}
|
|
|
|
FString UMaterialExpressionLandscapeGrassOutput::GetInputName(int32 InputIndex) const
|
|
{
|
|
return GrassTypes[InputIndex].Name.ToString();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
void UMaterialExpressionLandscapeGrassOutput::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
if (PropertyChangedEvent.MemberProperty)
|
|
{
|
|
const FName PropertyName = PropertyChangedEvent.MemberProperty->GetFName();
|
|
if (PropertyName == GET_MEMBER_NAME_CHECKED(UMaterialExpressionLandscapeGrassOutput, GrassTypes))
|
|
{
|
|
if (GraphNode)
|
|
{
|
|
GraphNode->ReconstructNode();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// ULandscapeGrassType
|
|
//
|
|
|
|
ULandscapeGrassType::ULandscapeGrassType(const FObjectInitializer& ObjectInitializer)
|
|
: Super(ObjectInitializer)
|
|
{
|
|
GrassDensity_DEPRECATED = 400;
|
|
StartCullDistance_DEPRECATED = 10000.0f;
|
|
EndCullDistance_DEPRECATED = 10000.0f;
|
|
PlacementJitter_DEPRECATED = 1.0f;
|
|
RandomRotation_DEPRECATED = true;
|
|
AlignToSurface_DEPRECATED = true;
|
|
}
|
|
|
|
void ULandscapeGrassType::PostLoad()
|
|
{
|
|
Super::PostLoad();
|
|
if (GrassMesh_DEPRECATED && !GrassVarieties.Num())
|
|
{
|
|
FGrassVariety Grass;
|
|
Grass.GrassMesh = GrassMesh_DEPRECATED;
|
|
Grass.GrassDensity = GrassDensity_DEPRECATED;
|
|
Grass.StartCullDistance = StartCullDistance_DEPRECATED;
|
|
Grass.EndCullDistance = EndCullDistance_DEPRECATED;
|
|
Grass.PlacementJitter = PlacementJitter_DEPRECATED;
|
|
Grass.RandomRotation = RandomRotation_DEPRECATED;
|
|
Grass.AlignToSurface = AlignToSurface_DEPRECATED;
|
|
|
|
GrassVarieties.Add(Grass);
|
|
GrassMesh_DEPRECATED = nullptr;
|
|
}
|
|
}
|
|
|
|
|
|
#if WITH_EDITOR
|
|
void ULandscapeGrassType::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
Super::PostEditChangeProperty(PropertyChangedEvent);
|
|
|
|
if (GIsEditor)
|
|
{
|
|
// Only care current world object
|
|
for (TActorIterator<ALandscapeProxy> It(GWorld); It; ++It)
|
|
{
|
|
ALandscapeProxy* Proxy = *It;
|
|
const UMaterialInterface* MaterialInterface = Proxy->LandscapeMaterial;
|
|
if (MaterialInterface)
|
|
{
|
|
TArray<const UMaterialExpressionLandscapeGrassOutput*> GrassExpressions;
|
|
MaterialInterface->GetMaterial()->GetAllExpressionsOfType<UMaterialExpressionLandscapeGrassOutput>(GrassExpressions);
|
|
|
|
// Should only be one grass type node
|
|
if (GrassExpressions.Num() > 0)
|
|
{
|
|
for (auto& Output : GrassExpressions[0]->GrassTypes)
|
|
{
|
|
if (Output.GrassType == this)
|
|
{
|
|
Proxy->FlushGrassComponents();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
//
|
|
// FLandscapeComponentGrassData
|
|
//
|
|
FArchive& operator<<(FArchive& Ar, FLandscapeComponentGrassData& Data)
|
|
{
|
|
if (Ar.UE4Ver() >= VER_UE4_SERIALIZE_LANDSCAPE_GRASS_DATA_MATERIAL_GUID)
|
|
{
|
|
Ar << Data.MaterialStateId;
|
|
}
|
|
|
|
Data.HeightData.BulkSerialize(Ar);
|
|
// Each weight data array, being 1 byte will be serialized in bulk.
|
|
return Ar << Data.WeightData;
|
|
}
|
|
|
|
//
|
|
// ALandscapeProxy grass-related functions
|
|
//
|
|
|
|
void ALandscapeProxy::TickGrass()
|
|
{
|
|
// Update foliage
|
|
static TArray<FVector> OldCameras;
|
|
if (CVarUseStreamingManagerForCameras.GetValueOnGameThread() == 0)
|
|
{
|
|
UWorld* World = GetWorld();
|
|
if (!World)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!OldCameras.Num() && !World->ViewLocationsRenderedLastFrame.Num())
|
|
{
|
|
// no cameras, no grass update
|
|
return;
|
|
}
|
|
|
|
// there is a bug here, which often leaves us with no cameras in the editor
|
|
const TArray<FVector>& Cameras = World->ViewLocationsRenderedLastFrame.Num() ? World->ViewLocationsRenderedLastFrame : OldCameras;
|
|
|
|
if (&Cameras != &OldCameras)
|
|
{
|
|
check(IsInGameThread());
|
|
OldCameras = Cameras;
|
|
}
|
|
UpdateGrass(Cameras);
|
|
}
|
|
else
|
|
{
|
|
int32 Num = IStreamingManager::Get().GetNumViews();
|
|
if (!Num)
|
|
{
|
|
// no cameras, no grass update
|
|
return;
|
|
}
|
|
OldCameras.Reset(Num);
|
|
for (int32 Index = 0; Index < Num; Index++)
|
|
{
|
|
auto& ViewInfo = IStreamingManager::Get().GetViewInformation(Index);
|
|
OldCameras.Add(ViewInfo.ViewOrigin);
|
|
}
|
|
UpdateGrass(OldCameras);
|
|
}
|
|
}
|
|
|
|
struct FGrassBuilderBase
|
|
{
|
|
bool bHaveValidData;
|
|
float GrassDensity;
|
|
FVector DrawScale;
|
|
FVector DrawLoc;
|
|
FMatrix LandscapeToWorld;
|
|
|
|
FIntPoint SectionBase;
|
|
FIntPoint LandscapeSectionOffset;
|
|
int32 ComponentSizeQuads;
|
|
FVector Origin;
|
|
FVector Extent;
|
|
int32 SqrtMaxInstances;
|
|
|
|
FGrassBuilderBase(ALandscapeProxy* Landscape, ULandscapeComponent* Component, const FGrassVariety& GrassVariety, int32 SqrtSubsections = 1, int32 SubX = 0, int32 SubY = 0)
|
|
{
|
|
bHaveValidData = true;
|
|
|
|
const float DensityScale = CVarGrassDensityScale.GetValueOnAnyThread();
|
|
GrassDensity = GrassVariety.GrassDensity * DensityScale;
|
|
|
|
DrawScale = Landscape->GetRootComponent()->RelativeScale3D;
|
|
DrawLoc = Landscape->GetActorLocation();
|
|
LandscapeSectionOffset = Landscape->LandscapeSectionOffset;
|
|
|
|
SectionBase = Component->GetSectionBase();
|
|
ComponentSizeQuads = Component->ComponentSizeQuads;
|
|
|
|
Origin = FVector(DrawScale.X * float(SectionBase.X), DrawScale.Y * float(SectionBase.Y), 0.0f);
|
|
Extent = FVector(DrawScale.X * float(SectionBase.X + ComponentSizeQuads), DrawScale.Y * float(SectionBase.Y + ComponentSizeQuads), 0.0f) - Origin;
|
|
|
|
SqrtMaxInstances = FMath::CeilToInt(FMath::Sqrt(FMath::Abs(Extent.X * Extent.Y * GrassDensity / 1000.0f / 1000.0f)));
|
|
|
|
if (!SqrtMaxInstances)
|
|
{
|
|
bHaveValidData = false;
|
|
}
|
|
const FRotator DrawRot = Landscape->GetActorRotation();
|
|
LandscapeToWorld = Landscape->GetRootComponent()->ComponentToWorld.ToMatrixNoScale();
|
|
|
|
if (bHaveValidData && SqrtSubsections != 1)
|
|
{
|
|
check(SqrtMaxInstances > 2 * SqrtSubsections);
|
|
SqrtMaxInstances /= SqrtSubsections;
|
|
check(SqrtMaxInstances > 0);
|
|
|
|
Extent /= float(SqrtSubsections);
|
|
Origin += Extent * FVector(float(SubX), float(SubY), 0.0f);
|
|
}
|
|
}
|
|
};
|
|
|
|
// FLandscapeComponentGrassAccess - accessor wrapper for data for one GrassType from one Component
|
|
struct FLandscapeComponentGrassAccess
|
|
{
|
|
FLandscapeComponentGrassAccess(const ULandscapeComponent* InComponent, const ULandscapeGrassType* GrassType)
|
|
: GrassData(InComponent->GrassData)
|
|
, HeightData(InComponent->GrassData->HeightData)
|
|
, WeightData(InComponent->GrassData->WeightData.Find(GrassType))
|
|
, Stride(InComponent->ComponentSizeQuads + 1)
|
|
{}
|
|
|
|
bool IsValid()
|
|
{
|
|
return WeightData && WeightData->Num() == FMath::Square(Stride) && HeightData.Num() == FMath::Square(Stride);
|
|
}
|
|
|
|
FORCEINLINE float GetHeight(int32 IdxX, int32 IdxY)
|
|
{
|
|
return LandscapeDataAccess::GetLocalHeight(HeightData[IdxX + Stride*IdxY]);
|
|
}
|
|
FORCEINLINE float GetWeight(int32 IdxX, int32 IdxY)
|
|
{
|
|
return ((float)(*WeightData)[IdxX + Stride*IdxY]) / 255.f;
|
|
}
|
|
|
|
FORCEINLINE int32 GetStride()
|
|
{
|
|
return Stride;
|
|
}
|
|
|
|
private:
|
|
TSharedRef<FLandscapeComponentGrassData, ESPMode::ThreadSafe> GrassData;
|
|
TArray<uint16>& HeightData;
|
|
TArray<uint8>* WeightData;
|
|
int32 Stride;
|
|
};
|
|
|
|
template<uint32 Base>
|
|
static FORCEINLINE float Halton(uint32 Index)
|
|
{
|
|
float Result = 0.0f;
|
|
float InvBase = 1.0f / Base;
|
|
float Fraction = InvBase;
|
|
while( Index > 0 )
|
|
{
|
|
Result += ( Index % Base ) * Fraction;
|
|
Index /= Base;
|
|
Fraction *= InvBase;
|
|
}
|
|
return Result;
|
|
}
|
|
|
|
struct FAsyncGrassBuilder : public FGrassBuilderBase
|
|
{
|
|
FLandscapeComponentGrassAccess GrassData;
|
|
bool RandomRotation;
|
|
bool AlignToSurface;
|
|
float PlacementJitter;
|
|
FRandomStream RandomStream;
|
|
FMatrix XForm;
|
|
FBox MeshBox;
|
|
int32 DesiredInstancesPerLeaf;
|
|
|
|
double RasterTime;
|
|
double BuildTime;
|
|
double InstanceTime;
|
|
int32 TotalInstances;
|
|
uint32 HaltonBaseIndex;
|
|
|
|
// output
|
|
FStaticMeshInstanceData InstanceBuffer;
|
|
TArray<FClusterNode> ClusterTree;
|
|
int32 OutOcclusionLayerNum;
|
|
|
|
FAsyncGrassBuilder(ALandscapeProxy* Landscape, ULandscapeComponent* Component, const ULandscapeGrassType* GrassType, const FGrassVariety& GrassVariety, UHierarchicalInstancedStaticMeshComponent* HierarchicalInstancedStaticMeshComponent, int32 SqrtSubsections, int32 SubX, int32 SubY, uint32 InHaltonBaseIndex)
|
|
: FGrassBuilderBase(Landscape, Component, GrassVariety, SqrtSubsections, SubX, SubY)
|
|
, GrassData(Component, GrassType)
|
|
{
|
|
bHaveValidData = bHaveValidData && GrassData.IsValid();
|
|
XForm = LandscapeToWorld * HierarchicalInstancedStaticMeshComponent->ComponentToWorld.ToMatrixWithScale().Inverse();
|
|
PlacementJitter = GrassVariety.PlacementJitter;
|
|
RandomRotation = GrassVariety.RandomRotation;
|
|
AlignToSurface = GrassVariety.AlignToSurface;
|
|
|
|
RandomStream.Initialize(HierarchicalInstancedStaticMeshComponent->InstancingRandomSeed);
|
|
|
|
MeshBox = GrassVariety.GrassMesh->GetBounds().GetBox();
|
|
DesiredInstancesPerLeaf = HierarchicalInstancedStaticMeshComponent->DesiredInstancesPerLeaf();
|
|
check(DesiredInstancesPerLeaf > 0);
|
|
|
|
TotalInstances = 0;
|
|
HaltonBaseIndex = InHaltonBaseIndex;
|
|
OutOcclusionLayerNum = 0;
|
|
}
|
|
|
|
void Build()
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassAsyncBuildTime);
|
|
check(bHaveValidData);
|
|
RasterTime -= FPlatformTime::Seconds();
|
|
|
|
float Div = 1.0f / float(SqrtMaxInstances);
|
|
TArray<FMatrix> InstanceTransforms;
|
|
if (HaltonBaseIndex)
|
|
{
|
|
if (Extent.X < 0)
|
|
{
|
|
Origin.X += Extent.X;
|
|
Extent.X *= -1.0f;
|
|
}
|
|
if (Extent.Y < 0)
|
|
{
|
|
Origin.Y += Extent.Y;
|
|
Extent.Y *= -1.0f;
|
|
}
|
|
int32 MaxNum = SqrtMaxInstances * SqrtMaxInstances;
|
|
InstanceTransforms.Reserve(MaxNum);
|
|
FVector DivExtent(Extent * Div);
|
|
for (int32 InstanceIndex = 0; InstanceIndex < MaxNum; InstanceIndex++)
|
|
{
|
|
float HaltonX = Halton<2>(InstanceIndex + HaltonBaseIndex);
|
|
float HaltonY = Halton<3>(InstanceIndex + HaltonBaseIndex);
|
|
FVector Location(Origin.X + HaltonX * Extent.X, Origin.Y + HaltonY * Extent.Y, 0.0f);
|
|
FVector LocationWithHeight;
|
|
float Weight = GetLayerWeightAtLocationLocal(Location, &LocationWithHeight);
|
|
bool bKeep = Weight > 0.0f && Weight >= RandomStream.GetFraction();
|
|
if (bKeep)
|
|
{
|
|
float Rot = RandomRotation ? RandomStream.GetFraction() * 360.0f : 0.0f;
|
|
FMatrix OutXForm;
|
|
if (AlignToSurface)
|
|
{
|
|
FVector LocationWithHeightDX;
|
|
FVector LocationDX(Location);
|
|
LocationDX.X = FMath::Clamp<float>(LocationDX.X + (HaltonX < 0.5f ? DivExtent.X : -DivExtent.X), Origin.X, Origin.X + Extent.X);
|
|
GetLayerWeightAtLocationLocal(LocationDX, &LocationWithHeightDX, false);
|
|
|
|
FVector LocationWithHeightDY;
|
|
FVector LocationDY(Location);
|
|
LocationDY.Y = FMath::Clamp<float>(LocationDX.Y + (HaltonY < 0.5f ? DivExtent.Y : -DivExtent.Y), Origin.Y, Origin.Y + Extent.Y);
|
|
GetLayerWeightAtLocationLocal(LocationDY, &LocationWithHeightDY, false);
|
|
|
|
if (LocationWithHeight != LocationWithHeightDX && LocationWithHeight != LocationWithHeightDY)
|
|
{
|
|
FVector NewZ = ((LocationWithHeight - LocationWithHeightDX) ^ (LocationWithHeight - LocationWithHeightDY)).GetSafeNormal();
|
|
NewZ *= FMath::Sign(NewZ.Z);
|
|
|
|
const FVector NewX = (FVector(0, -1, 0) ^ NewZ).GetSafeNormal();
|
|
const FVector NewY = NewZ ^ NewX;
|
|
|
|
FMatrix Align = FMatrix(NewX, NewY, NewZ, FVector::ZeroVector);
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * Align * FTranslationMatrix(LocationWithHeight) * XForm;
|
|
}
|
|
else
|
|
{
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * FTranslationMatrix(LocationWithHeight) * XForm;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * FTranslationMatrix(LocationWithHeight) * XForm;
|
|
}
|
|
InstanceTransforms.Add(OutXForm);
|
|
}
|
|
}
|
|
if (InstanceTransforms.Num())
|
|
{
|
|
TotalInstances += InstanceTransforms.Num();
|
|
InstanceBuffer.AllocateInstances(InstanceTransforms.Num());
|
|
for (int32 InstanceIndex = 0; InstanceIndex < InstanceTransforms.Num(); InstanceIndex++)
|
|
{
|
|
const FMatrix& OutXForm = InstanceTransforms[InstanceIndex];
|
|
FInstanceStream* RenderData = InstanceBuffer.GetInstanceWriteAddress(InstanceIndex);
|
|
RenderData->SetInstance(OutXForm, RandomStream.GetFraction());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int32 NumKept = 0;
|
|
float MaxJitter1D = FMath::Clamp<float>(PlacementJitter, 0.0f, .99f) * Div * .5f;
|
|
FVector MaxJitter(MaxJitter1D, MaxJitter1D, 0.0f);
|
|
MaxJitter *= Extent;
|
|
Origin += Extent * (Div * 0.5f);
|
|
struct FInstanceLocal
|
|
{
|
|
FVector Pos;
|
|
bool bKeep;
|
|
};
|
|
TArray<FInstanceLocal> Instances;
|
|
Instances.AddUninitialized(SqrtMaxInstances * SqrtMaxInstances);
|
|
{
|
|
int32 InstanceIndex = 0;
|
|
for (int32 xStart = 0; xStart < SqrtMaxInstances; xStart++)
|
|
{
|
|
for (int32 yStart = 0; yStart < SqrtMaxInstances; yStart++)
|
|
{
|
|
FVector Location(Origin.X + float(xStart) * Div * Extent.X, Origin.Y + float(yStart) * Div * Extent.Y, 0.0f);
|
|
Location += FVector(RandomStream.GetFraction() * 2.0f - 1.0f, RandomStream.GetFraction() * 2.0f - 1.0f, 0.0f) * MaxJitter;
|
|
|
|
FInstanceLocal& Instance = Instances[InstanceIndex];
|
|
float Weight = GetLayerWeightAtLocationLocal(Location, &Instance.Pos);
|
|
Instance.bKeep = Weight > 0.0f && Weight >= RandomStream.GetFraction();
|
|
if (Instance.bKeep)
|
|
{
|
|
NumKept++;
|
|
}
|
|
InstanceIndex++;
|
|
}
|
|
}
|
|
}
|
|
if (NumKept)
|
|
{
|
|
InstanceTransforms.AddUninitialized(NumKept);
|
|
TotalInstances += NumKept;
|
|
{
|
|
InstanceBuffer.AllocateInstances(NumKept);
|
|
int32 InstanceIndex = 0;
|
|
int32 OutInstanceIndex = 0;
|
|
for (int32 xStart = 0; xStart < SqrtMaxInstances; xStart++)
|
|
{
|
|
for (int32 yStart = 0; yStart < SqrtMaxInstances; yStart++)
|
|
{
|
|
const FInstanceLocal& Instance = Instances[InstanceIndex];
|
|
if (Instance.bKeep)
|
|
{
|
|
float Rot = RandomRotation ? RandomStream.GetFraction() * 360.0f : 0.0f;
|
|
FMatrix OutXForm;
|
|
if (AlignToSurface)
|
|
{
|
|
FVector PosX1 = xStart ? Instances[InstanceIndex - SqrtMaxInstances].Pos : Instance.Pos;
|
|
FVector PosX2 = (xStart + 1 < SqrtMaxInstances) ? Instances[InstanceIndex + SqrtMaxInstances].Pos : Instance.Pos;
|
|
FVector PosY1 = yStart ? Instances[InstanceIndex - 1].Pos : Instance.Pos;
|
|
FVector PosY2 = (yStart + 1 < SqrtMaxInstances) ? Instances[InstanceIndex + 1].Pos : Instance.Pos;
|
|
|
|
if (PosX1 != PosX2 && PosY1 != PosY2)
|
|
{
|
|
FVector NewZ = ((PosX1 - PosX2) ^ (PosY1 - PosY2)).GetSafeNormal();
|
|
NewZ *= FMath::Sign(NewZ.Z);
|
|
|
|
const FVector NewX = (FVector(0, -1, 0) ^ NewZ).GetSafeNormal();
|
|
const FVector NewY = NewZ ^ NewX;
|
|
|
|
FMatrix Align = FMatrix(NewX, NewY, NewZ, FVector::ZeroVector);
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * Align * FTranslationMatrix(Instance.Pos) * XForm;
|
|
}
|
|
else
|
|
{
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * FTranslationMatrix(Instance.Pos) * XForm;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutXForm = FRotationMatrix(FRotator(0.0f, Rot, 0.0f)) * FTranslationMatrix(Instance.Pos) * XForm;
|
|
}
|
|
InstanceTransforms[OutInstanceIndex] = OutXForm;
|
|
FInstanceStream* RenderData = InstanceBuffer.GetInstanceWriteAddress(OutInstanceIndex++);
|
|
RenderData->SetInstance(OutXForm, RandomStream.GetFraction());
|
|
}
|
|
InstanceIndex++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int32 NumInstances = InstanceTransforms.Num();
|
|
if (NumInstances)
|
|
{
|
|
TArray<int32> SortedInstances;
|
|
TArray<int32> InstanceReorderTable;
|
|
UHierarchicalInstancedStaticMeshComponent::BuildTreeAnyThread(InstanceTransforms, MeshBox, ClusterTree, SortedInstances, InstanceReorderTable, OutOcclusionLayerNum, DesiredInstancesPerLeaf);
|
|
|
|
// in-place sort the instances
|
|
FInstanceStream SwapBuffer;
|
|
for (int32 FirstUnfixedIndex = 0; FirstUnfixedIndex < NumInstances; FirstUnfixedIndex++)
|
|
{
|
|
int32 LoadFrom = SortedInstances[FirstUnfixedIndex];
|
|
if (LoadFrom != FirstUnfixedIndex)
|
|
{
|
|
check(LoadFrom > FirstUnfixedIndex);
|
|
FMemory::Memcpy(&SwapBuffer, InstanceBuffer.GetInstanceWriteAddress(FirstUnfixedIndex), sizeof(FInstanceStream));
|
|
FMemory::Memcpy(InstanceBuffer.GetInstanceWriteAddress(FirstUnfixedIndex), InstanceBuffer.GetInstanceWriteAddress(LoadFrom), sizeof(FInstanceStream));
|
|
FMemory::Memcpy(InstanceBuffer.GetInstanceWriteAddress(LoadFrom), &SwapBuffer, sizeof(FInstanceStream));
|
|
|
|
int32 SwapGoesTo = InstanceReorderTable[FirstUnfixedIndex];
|
|
check(SwapGoesTo > FirstUnfixedIndex);
|
|
check(SortedInstances[SwapGoesTo] == FirstUnfixedIndex);
|
|
SortedInstances[SwapGoesTo] = LoadFrom;
|
|
InstanceReorderTable[LoadFrom] = SwapGoesTo;
|
|
|
|
InstanceReorderTable[FirstUnfixedIndex] = FirstUnfixedIndex;
|
|
SortedInstances[FirstUnfixedIndex] = FirstUnfixedIndex;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
FORCEINLINE_DEBUGGABLE float GetLayerWeightAtLocationLocal(const FVector& InLocation, FVector* OutLocation, bool bWeight = true)
|
|
{
|
|
// Find location
|
|
float TestX = InLocation.X / DrawScale.X - (float)SectionBase.X;
|
|
float TestY = InLocation.Y / DrawScale.Y - (float)SectionBase.Y;
|
|
|
|
// Find data
|
|
int32 X1 = FMath::FloorToInt(TestX);
|
|
int32 Y1 = FMath::FloorToInt(TestY);
|
|
int32 X2 = FMath::CeilToInt(TestX);
|
|
int32 Y2 = FMath::CeilToInt(TestY);
|
|
|
|
// Min is to prevent the sampling of the final column from overflowing
|
|
int32 IdxX1 = FMath::Min<int32>(X1, GrassData.GetStride() - 1);
|
|
int32 IdxY1 = FMath::Min<int32>(Y1, GrassData.GetStride() - 1);
|
|
int32 IdxX2 = FMath::Min<int32>(X2, GrassData.GetStride() - 1);
|
|
int32 IdxY2 = FMath::Min<int32>(Y2, GrassData.GetStride() - 1);
|
|
|
|
float LerpX = FMath::Fractional(TestX);
|
|
float LerpY = FMath::Fractional(TestY);
|
|
|
|
float Result = 0.0f;
|
|
if (bWeight)
|
|
{
|
|
// sample
|
|
float Sample11 = GrassData.GetWeight(IdxX1, IdxY1);
|
|
float Sample21 = GrassData.GetWeight(IdxX2, IdxY1);
|
|
float Sample12 = GrassData.GetWeight(IdxX1, IdxY2);
|
|
float Sample22 = GrassData.GetWeight(IdxX2, IdxY2);
|
|
|
|
// Bilinear interpolate
|
|
Result = FMath::Lerp(
|
|
FMath::Lerp(Sample11, Sample21, LerpX),
|
|
FMath::Lerp(Sample12, Sample22, LerpX),
|
|
LerpY);
|
|
}
|
|
|
|
{
|
|
// sample
|
|
float Sample11 = GrassData.GetHeight(IdxX1, IdxY1);
|
|
float Sample21 = GrassData.GetHeight(IdxX2, IdxY1);
|
|
float Sample12 = GrassData.GetHeight(IdxX1, IdxY2);
|
|
float Sample22 = GrassData.GetHeight(IdxX2, IdxY2);
|
|
|
|
OutLocation->X = InLocation.X - DrawScale.X * float(LandscapeSectionOffset.X);
|
|
OutLocation->Y = InLocation.Y - DrawScale.Y * float(LandscapeSectionOffset.Y);
|
|
// Bilinear interpolate
|
|
OutLocation->Z = DrawScale.Z * FMath::Lerp(
|
|
FMath::Lerp(Sample11, Sample21, LerpX),
|
|
FMath::Lerp(Sample12, Sample22, LerpX),
|
|
LerpY);
|
|
}
|
|
return Result;
|
|
}
|
|
};
|
|
|
|
void ALandscapeProxy::FlushGrassComponents(const TSet<ULandscapeComponent*>* OnlyForComponents, bool bFlushGrassMaps)
|
|
{
|
|
if (OnlyForComponents)
|
|
{
|
|
for (FCachedLandscapeFoliage::TGrassSet::TIterator Iter(FoliageCache.CachedGrassComps); Iter; ++Iter)
|
|
{
|
|
ULandscapeComponent* Component = (*Iter).Key.BasedOn.Get();
|
|
// if the weak pointer in the cache is invalid, we should kill them anyway
|
|
if (Component == nullptr || OnlyForComponents->Contains(Component))
|
|
{
|
|
UHierarchicalInstancedStaticMeshComponent *Used = (*Iter).Foliage.Get();
|
|
if (Used)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassDestoryComp);
|
|
Used->ClearInstances();
|
|
Used->DetachFromParent(false, false);
|
|
Used->DestroyComponent();
|
|
}
|
|
Iter.RemoveCurrent();
|
|
}
|
|
}
|
|
#if WITH_EDITOR
|
|
if (GIsEditor && bFlushGrassMaps)
|
|
{
|
|
for (ULandscapeComponent* Component : *OnlyForComponents)
|
|
{
|
|
Component->RemoveGrassMap();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
// Clear old foliage component containers
|
|
FoliageComponents.Empty();
|
|
|
|
// Might as well clear the cache...
|
|
FoliageCache.ClearCache();
|
|
// Destroy any owned foliage components
|
|
TInlineComponentArray<UHierarchicalInstancedStaticMeshComponent*> FoliageComps;
|
|
GetComponents(FoliageComps);
|
|
for (UHierarchicalInstancedStaticMeshComponent* Component : FoliageComps)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassDestoryComp);
|
|
Component->ClearInstances();
|
|
Component->DetachFromParent(false, false);
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
TArray<USceneComponent*> AttachedFoliageComponents = RootComponent->AttachChildren.FilterByPredicate(
|
|
[](USceneComponent* Component)
|
|
{
|
|
return Cast<UHierarchicalInstancedStaticMeshComponent>(Component);
|
|
});
|
|
|
|
// Destroy any attached but un-owned foliage components
|
|
for (USceneComponent* Component : AttachedFoliageComponents)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassDestoryComp);
|
|
CastChecked<UHierarchicalInstancedStaticMeshComponent>(Component)->ClearInstances();
|
|
Component->DetachFromParent(false, false);
|
|
Component->DestroyComponent();
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
if (GIsEditor && bFlushGrassMaps)
|
|
{
|
|
// Clear GrassMaps
|
|
TInlineComponentArray<ULandscapeComponent*> LandComps;
|
|
GetComponents(LandComps);
|
|
for (ULandscapeComponent* Component : LandComps)
|
|
{
|
|
Component->RemoveGrassMap();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
TArray<ULandscapeGrassType*> ALandscapeProxy::GetGrassTypes() const
|
|
{
|
|
TArray<ULandscapeGrassType*> GrassTypes;
|
|
if (LandscapeMaterial)
|
|
{
|
|
TArray<const UMaterialExpressionLandscapeGrassOutput*> GrassExpressions;
|
|
LandscapeMaterial->GetMaterial()->GetAllExpressionsOfType<UMaterialExpressionLandscapeGrassOutput>(GrassExpressions);
|
|
if (GrassExpressions.Num() > 0)
|
|
{
|
|
for (auto& Type : GrassExpressions[0]->GrassTypes)
|
|
{
|
|
GrassTypes.Add(Type.GrassType);
|
|
}
|
|
}
|
|
}
|
|
return GrassTypes;
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
int32 ALandscapeProxy::TotalComponentsNeedingGrassMapRender = 0;
|
|
int32 ALandscapeProxy::TotalTexturesToStreamForVisibleGrassMapRender = 0;
|
|
int32 ALandscapeProxy::TotalComponentsNeedingTextureBaking = 0;
|
|
#endif
|
|
|
|
void ALandscapeProxy::UpdateGrass(const TArray<FVector>& Cameras, bool bForceSync)
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_GrassUpdate);
|
|
|
|
if (CVarGrassEnable.GetValueOnAnyThread() > 0)
|
|
{
|
|
TArray<ULandscapeGrassType*> GrassTypes = GetGrassTypes();
|
|
|
|
float GuardBand = CVarGuardBandMultiplier.GetValueOnAnyThread();
|
|
float DiscardGuardBand = CVarGuardBandDiscardMultiplier.GetValueOnAnyThread();
|
|
bool bCullSubsections = CVarCullSubsections.GetValueOnAnyThread() > 0;
|
|
bool bDisableGPUCull = CVarDisableGPUCull.GetValueOnAnyThread() > 0;
|
|
int32 MaxInstancesPerComponent = FMath::Max<int32>(1024, CVarMaxInstancesPerComponent.GetValueOnAnyThread());
|
|
int32 MaxTasks = CVarMaxAsyncTasks.GetValueOnAnyThread();
|
|
|
|
UWorld* World = GetWorld();
|
|
if (World)
|
|
{
|
|
#if WITH_EDITOR
|
|
int32 RequiredTexturesNotStreamedIn = 0;
|
|
TSet<ULandscapeComponent*> ComponentsNeedingGrassMapRender;
|
|
TSet<UTexture2D*> CurrentForcedStreamedTextures;
|
|
TSet<UTexture2D*> DesiredForceStreamedTextures;
|
|
|
|
if (!World->IsGameWorld())
|
|
{
|
|
// see if we need to flush grass for any components
|
|
TSet<ULandscapeComponent*> FlushComponents;
|
|
for (auto Component : LandscapeComponents)
|
|
{
|
|
// check textures currently needing force streaming
|
|
if (Component->HeightmapTexture->bForceMiplevelsToBeResident)
|
|
{
|
|
CurrentForcedStreamedTextures.Add(Component->HeightmapTexture);
|
|
}
|
|
for (auto WeightmapTexture : Component->WeightmapTextures)
|
|
{
|
|
if (WeightmapTexture->bForceMiplevelsToBeResident)
|
|
{
|
|
CurrentForcedStreamedTextures.Add(WeightmapTexture);
|
|
}
|
|
}
|
|
|
|
if (Component->IsGrassMapOutdated())
|
|
{
|
|
FlushComponents.Add(Component);
|
|
if (GrassTypes.Num() > 0)
|
|
{
|
|
ComponentsNeedingGrassMapRender.Add(Component);
|
|
}
|
|
}
|
|
else
|
|
if (!Component->GrassData->HasData())
|
|
{
|
|
if (GrassTypes.Num() > 0)
|
|
{
|
|
ComponentsNeedingGrassMapRender.Add(Component);
|
|
}
|
|
}
|
|
}
|
|
if (FlushComponents.Num())
|
|
{
|
|
FlushGrassComponents(&FlushComponents);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
int32 NumCompsCreated = 0;
|
|
for (int32 ComponentIndex = 0; ComponentIndex < LandscapeComponents.Num(); ComponentIndex++)
|
|
{
|
|
ULandscapeComponent* Component = LandscapeComponents[ComponentIndex];
|
|
|
|
// skip if we have no data and no way to generate it
|
|
if (World->IsGameWorld() && !Component->GrassData->HasData())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FBoxSphereBounds WorldBounds = Component->CalcBounds(Component->ComponentToWorld);
|
|
float MinDistanceToComp = Cameras.Num() ? MAX_flt : 0.0f;
|
|
|
|
for (auto& Pos : Cameras)
|
|
{
|
|
MinDistanceToComp = FMath::Min<float>(MinDistanceToComp, WorldBounds.ComputeSquaredDistanceFromBoxToPoint(Pos));
|
|
}
|
|
|
|
MinDistanceToComp = FMath::Sqrt(MinDistanceToComp);
|
|
|
|
for (auto GrassType : GrassTypes)
|
|
{
|
|
if (GrassType)
|
|
{
|
|
int32 GrassVarietyIndex = -1;
|
|
uint32 HaltonBaseIndex = 1;
|
|
for (auto& GrassVariety : GrassType->GrassVarieties)
|
|
{
|
|
GrassVarietyIndex++;
|
|
if (GrassVariety.GrassMesh && GrassVariety.GrassDensity > 0.0f && GrassVariety.EndCullDistance > 0)
|
|
{
|
|
bool bRandomRotation = GrassVariety.RandomRotation;
|
|
bool bAlign = GrassVariety.AlignToSurface;
|
|
float MustHaveDistance = GuardBand * (float)GrassVariety.EndCullDistance;
|
|
float DiscardDistance = DiscardGuardBand * (float)GrassVariety.EndCullDistance;
|
|
|
|
bool bUseHalton = !GrassVariety.bUseGrid;
|
|
|
|
if (!bUseHalton && MinDistanceToComp > DiscardDistance)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FGrassBuilderBase ForSubsectionMath(this, Component, GrassVariety);
|
|
|
|
int32 SqrtSubsections = 1;
|
|
|
|
if (ForSubsectionMath.bHaveValidData && ForSubsectionMath.SqrtMaxInstances > 0)
|
|
{
|
|
SqrtSubsections = FMath::Clamp<int32>(FMath::CeilToInt(float(ForSubsectionMath.SqrtMaxInstances) / FMath::Sqrt((float)MaxInstancesPerComponent)), 1, 16);
|
|
}
|
|
int32 MaxInstancesSub = FMath::Square(ForSubsectionMath.SqrtMaxInstances / SqrtSubsections);
|
|
|
|
if (bUseHalton && MinDistanceToComp > DiscardDistance)
|
|
{
|
|
HaltonBaseIndex += MaxInstancesSub * SqrtSubsections * SqrtSubsections;
|
|
continue;
|
|
}
|
|
|
|
FBox LocalBox = Component->CachedLocalBox;
|
|
FVector LocalExtentDiv = (LocalBox.Max - LocalBox.Min) * FVector(1.0f / float(SqrtSubsections), 1.0f / float(SqrtSubsections), 1.0f);
|
|
for (int32 SubX = 0; SubX < SqrtSubsections; SubX++)
|
|
{
|
|
for (int32 SubY = 0; SubY < SqrtSubsections; SubY++)
|
|
{
|
|
float MinDistanceToSubComp = MinDistanceToComp;
|
|
|
|
if (bCullSubsections && SqrtSubsections > 1)
|
|
{
|
|
FVector BoxMin;
|
|
BoxMin.X = LocalBox.Min.X + LocalExtentDiv.X * float(SubX);
|
|
BoxMin.Y = LocalBox.Min.Y + LocalExtentDiv.Y * float(SubY);
|
|
BoxMin.Z = LocalBox.Min.Z;
|
|
|
|
FVector BoxMax;
|
|
BoxMax.X = LocalBox.Min.X + LocalExtentDiv.X * float(SubX + 1);
|
|
BoxMax.Y = LocalBox.Min.Y + LocalExtentDiv.Y * float(SubY + 1);
|
|
BoxMax.Z = LocalBox.Max.Z;
|
|
|
|
FBox LocalSubBox(BoxMin, BoxMax);
|
|
FBox WorldSubBox = LocalSubBox.TransformBy(Component->ComponentToWorld);
|
|
|
|
MinDistanceToSubComp = Cameras.Num() ? MAX_flt : 0.0f;
|
|
for (auto& Pos : Cameras)
|
|
{
|
|
MinDistanceToSubComp = FMath::Min<float>(MinDistanceToSubComp, ComputeSquaredDistanceFromBoxToPoint(WorldSubBox.Min, WorldSubBox.Max, Pos));
|
|
}
|
|
MinDistanceToSubComp = FMath::Sqrt(MinDistanceToSubComp);
|
|
}
|
|
|
|
if (bUseHalton)
|
|
{
|
|
HaltonBaseIndex += MaxInstancesSub; // we are going to pre-increment this for all of the continues...however we need to subtract later if we actually do this sub
|
|
}
|
|
|
|
if (MinDistanceToSubComp > DiscardDistance)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FCachedLandscapeFoliage::FGrassComp NewComp;
|
|
NewComp.Key.BasedOn = Component;
|
|
NewComp.Key.GrassType = GrassType;
|
|
NewComp.Key.SqrtSubsections = SqrtSubsections;
|
|
NewComp.Key.CachedMaxInstancesPerComponent = MaxInstancesPerComponent;
|
|
NewComp.Key.SubsectionX = SubX;
|
|
NewComp.Key.SubsectionY = SubY;
|
|
NewComp.Key.NumVarieties = GrassType->GrassVarieties.Num();
|
|
NewComp.Key.VarietyIndex = GrassVarietyIndex;
|
|
|
|
{
|
|
FCachedLandscapeFoliage::FGrassComp* Existing = FoliageCache.CachedGrassComps.Find(NewComp.Key);
|
|
if (Existing || MinDistanceToSubComp > MustHaveDistance)
|
|
{
|
|
if (Existing)
|
|
{
|
|
Existing->Touch();
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (NumCompsCreated || (!bForceSync && AsyncFoliageTasks.Num() >= MaxTasks))
|
|
{
|
|
continue; // one per frame, but we still want to touch the existing ones
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
// render grass data if we don't have any
|
|
if (!Component->GrassData->HasData())
|
|
{
|
|
if (!Component->CanRenderGrassMap())
|
|
{
|
|
// we can't currently render grassmaps (eg shaders not compiled)
|
|
continue;
|
|
}
|
|
else if (!Component->AreTexturesStreamedForGrassMapRender())
|
|
{
|
|
// we're ready to generate but our textures need streaming in
|
|
DesiredForceStreamedTextures.Add(Component->HeightmapTexture);
|
|
for (auto WeightmapTexture : Component->WeightmapTextures)
|
|
{
|
|
DesiredForceStreamedTextures.Add(WeightmapTexture);
|
|
}
|
|
RequiredTexturesNotStreamedIn++;
|
|
continue;
|
|
}
|
|
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GrassRenderToTexture);
|
|
Component->RenderGrassMap();
|
|
ComponentsNeedingGrassMapRender.Remove(Component);
|
|
Component->MarkPackageDirty();
|
|
}
|
|
#endif
|
|
|
|
NumCompsCreated++;
|
|
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassStartComp);
|
|
int32 FolSeed = FCrc::StrCrc32((GrassType->GetName() + Component->GetName() + FString::Printf(TEXT("%d %d %d"), SubX, SubY, GrassVarietyIndex)).GetCharArray().GetData());
|
|
if (FolSeed == 0)
|
|
{
|
|
FolSeed++;
|
|
}
|
|
|
|
UHierarchicalInstancedStaticMeshComponent* HierarchicalInstancedStaticMeshComponent;
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GrassCreateComp);
|
|
HierarchicalInstancedStaticMeshComponent = NewObject<UHierarchicalInstancedStaticMeshComponent>(this);
|
|
}
|
|
NewComp.Foliage = HierarchicalInstancedStaticMeshComponent;
|
|
FoliageCache.CachedGrassComps.Add(NewComp);
|
|
|
|
HierarchicalInstancedStaticMeshComponent->Mobility = EComponentMobility::Static;
|
|
|
|
HierarchicalInstancedStaticMeshComponent->StaticMesh = GrassVariety.GrassMesh;
|
|
HierarchicalInstancedStaticMeshComponent->MinLOD = GrassVariety.MinLOD;
|
|
HierarchicalInstancedStaticMeshComponent->bSelectable = false;
|
|
HierarchicalInstancedStaticMeshComponent->bHasPerInstanceHitProxies = true;
|
|
static FName NoCollision(TEXT("NoCollision"));
|
|
HierarchicalInstancedStaticMeshComponent->SetCollisionProfileName(NoCollision);
|
|
HierarchicalInstancedStaticMeshComponent->bDisableCollision = true;
|
|
HierarchicalInstancedStaticMeshComponent->SetCanEverAffectNavigation(false);
|
|
HierarchicalInstancedStaticMeshComponent->InstancingRandomSeed = FolSeed;
|
|
|
|
if (!Cameras.Num() || bDisableGPUCull)
|
|
{
|
|
// if we don't have any cameras, then we are rendering landscape LOD materials or somesuch and we want to disable culling
|
|
HierarchicalInstancedStaticMeshComponent->InstanceStartCullDistance = 0;
|
|
HierarchicalInstancedStaticMeshComponent->InstanceEndCullDistance = 0;
|
|
}
|
|
else
|
|
{
|
|
HierarchicalInstancedStaticMeshComponent->InstanceStartCullDistance = GrassVariety.StartCullDistance;
|
|
HierarchicalInstancedStaticMeshComponent->InstanceEndCullDistance = GrassVariety.EndCullDistance;
|
|
}
|
|
|
|
//@todo - take the settings from a UFoliageType object. For now, disable distance field lighting on grass so we don't hitch.
|
|
HierarchicalInstancedStaticMeshComponent->bAffectDistanceFieldLighting = false;
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GrassAttachComp);
|
|
|
|
HierarchicalInstancedStaticMeshComponent->AttachTo(GetRootComponent());
|
|
FTransform DesiredTransform = GetRootComponent()->ComponentToWorld;
|
|
DesiredTransform.RemoveScaling();
|
|
HierarchicalInstancedStaticMeshComponent->SetWorldTransform(DesiredTransform);
|
|
|
|
FoliageComponents.Add(HierarchicalInstancedStaticMeshComponent);
|
|
}
|
|
|
|
FAsyncGrassBuilder* Builder;
|
|
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GrassCreateBuilder);
|
|
|
|
uint32 HaltonIndexForSub = 0;
|
|
if (bUseHalton)
|
|
{
|
|
check(HaltonBaseIndex > (uint32)MaxInstancesSub);
|
|
HaltonIndexForSub = HaltonBaseIndex - (uint32)MaxInstancesSub;
|
|
}
|
|
Builder = new FAsyncGrassBuilder(this, Component, GrassType, GrassVariety, HierarchicalInstancedStaticMeshComponent, SqrtSubsections, SubX, SubY, HaltonIndexForSub);
|
|
}
|
|
|
|
if (Builder->bHaveValidData)
|
|
{
|
|
FAsyncTask<FAsyncGrassTask>* Task = new FAsyncTask<FAsyncGrassTask>(Builder, NewComp.Key, HierarchicalInstancedStaticMeshComponent);
|
|
|
|
Task->StartBackgroundTask();
|
|
|
|
AsyncFoliageTasks.Add(Task);
|
|
}
|
|
else
|
|
{
|
|
delete Builder;
|
|
}
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_GrassRegisterComp);
|
|
HierarchicalInstancedStaticMeshComponent->RegisterComponent();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if WITH_EDITOR
|
|
|
|
TotalTexturesToStreamForVisibleGrassMapRender -= NumTexturesToStreamForVisibleGrassMapRender;
|
|
NumTexturesToStreamForVisibleGrassMapRender = RequiredTexturesNotStreamedIn;
|
|
TotalTexturesToStreamForVisibleGrassMapRender += NumTexturesToStreamForVisibleGrassMapRender;
|
|
|
|
{
|
|
int32 NumComponentsRendered = 0;
|
|
int32 NumComponentsUnableToRender = 0;
|
|
if (GrassTypes.Num() > 0 && CVarPrerenderGrassmaps.GetValueOnAnyThread() > 0)
|
|
{
|
|
// try to render some grassmaps
|
|
TArray<ULandscapeComponent*> ComponentsToRender;
|
|
for (auto Component : ComponentsNeedingGrassMapRender)
|
|
{
|
|
if (Component->CanRenderGrassMap())
|
|
{
|
|
if (Component->AreTexturesStreamedForGrassMapRender())
|
|
{
|
|
// We really want to throttle the number based on component size.
|
|
if (NumComponentsRendered <= 4)
|
|
{
|
|
ComponentsToRender.Add(Component);
|
|
NumComponentsRendered++;
|
|
}
|
|
}
|
|
else
|
|
if (TotalTexturesToStreamForVisibleGrassMapRender == 0)
|
|
{
|
|
// Force stream in other heightmaps but only if we're not waiting for the textures
|
|
// near the camera to stream in
|
|
DesiredForceStreamedTextures.Add(Component->HeightmapTexture);
|
|
for (auto WeightmapTexture : Component->WeightmapTextures)
|
|
{
|
|
DesiredForceStreamedTextures.Add(WeightmapTexture);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
NumComponentsUnableToRender++;
|
|
}
|
|
}
|
|
if (ComponentsToRender.Num())
|
|
{
|
|
RenderGrassMaps(ComponentsToRender, GrassTypes);
|
|
MarkPackageDirty();
|
|
}
|
|
}
|
|
|
|
TotalComponentsNeedingGrassMapRender -= NumComponentsNeedingGrassMapRender;
|
|
NumComponentsNeedingGrassMapRender = ComponentsNeedingGrassMapRender.Num() - NumComponentsRendered - NumComponentsUnableToRender;
|
|
TotalComponentsNeedingGrassMapRender += NumComponentsNeedingGrassMapRender;
|
|
|
|
// Update resident flags
|
|
for (auto Texture : DesiredForceStreamedTextures.Difference(CurrentForcedStreamedTextures))
|
|
{
|
|
Texture->bForceMiplevelsToBeResident = true;
|
|
}
|
|
for (auto Texture : CurrentForcedStreamedTextures.Difference(DesiredForceStreamedTextures))
|
|
{
|
|
Texture->bForceMiplevelsToBeResident = false;
|
|
}
|
|
|
|
}
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static TSet<UHierarchicalInstancedStaticMeshComponent *> StillUsed;
|
|
StillUsed.Empty(256);
|
|
{
|
|
// trim cached items based on time, pending and emptiness
|
|
double OldestToKeepTime = FPlatformTime::Seconds() - CVarMinTimeToKeepGrass.GetValueOnGameThread();
|
|
uint32 OldestToKeepFrame = GFrameNumber - CVarMinFramesToKeepGrass.GetValueOnGameThread();
|
|
for (FCachedLandscapeFoliage::TGrassSet::TIterator Iter(FoliageCache.CachedGrassComps); Iter; ++Iter)
|
|
{
|
|
const FCachedLandscapeFoliage::FGrassComp& GrassItem = *Iter;
|
|
UHierarchicalInstancedStaticMeshComponent *Used = GrassItem.Foliage.Get();
|
|
bool bOld =
|
|
!GrassItem.Pending &&
|
|
(
|
|
!GrassItem.Key.BasedOn.Get() ||
|
|
!GrassItem.Key.GrassType.Get() ||
|
|
!Used ||
|
|
(GrassItem.LastUsedFrameNumber < OldestToKeepFrame && GrassItem.LastUsedTime < OldestToKeepTime)
|
|
);
|
|
if (bOld)
|
|
{
|
|
Iter.RemoveCurrent();
|
|
}
|
|
else if (Used)
|
|
{
|
|
StillUsed.Add(Used);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
// delete components that are no longer used
|
|
for (UActorComponent* ActorComponent : GetComponents())
|
|
{
|
|
UHierarchicalInstancedStaticMeshComponent* HComponent = Cast<UHierarchicalInstancedStaticMeshComponent>(ActorComponent);
|
|
if (HComponent && !StillUsed.Contains(HComponent))
|
|
{
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassDestoryComp);
|
|
HComponent->ClearInstances();
|
|
HComponent->DetachFromParent(false, false);
|
|
HComponent->DestroyComponent();
|
|
FoliageComponents.Remove(HComponent);
|
|
}
|
|
if (!bForceSync)
|
|
{
|
|
break; // one per frame is fine
|
|
}
|
|
}
|
|
}
|
|
}
|
|
{
|
|
// finish async tasks
|
|
for (int32 Index = 0; Index < AsyncFoliageTasks.Num(); Index++)
|
|
{
|
|
FAsyncTask<FAsyncGrassTask>* Task = AsyncFoliageTasks[Index];
|
|
if (bForceSync)
|
|
{
|
|
Task->EnsureCompletion();
|
|
}
|
|
if (Task->IsDone())
|
|
{
|
|
SCOPE_CYCLE_COUNTER(STAT_FoliageGrassEndComp);
|
|
FAsyncGrassTask& Inner = Task->GetTask();
|
|
AsyncFoliageTasks.RemoveAtSwap(Index--);
|
|
UHierarchicalInstancedStaticMeshComponent* HierarchicalInstancedStaticMeshComponent = Inner.Foliage.Get();
|
|
if (HierarchicalInstancedStaticMeshComponent && StillUsed.Contains(HierarchicalInstancedStaticMeshComponent))
|
|
{
|
|
if (Inner.Builder->InstanceBuffer.Num())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FoliageGrassEndComp_AcceptPrebuiltTree);
|
|
FMemory::Memswap(&HierarchicalInstancedStaticMeshComponent->WriteOncePrebuiltInstanceBuffer, &Inner.Builder->InstanceBuffer, sizeof(FStaticMeshInstanceData));
|
|
HierarchicalInstancedStaticMeshComponent->AcceptPrebuiltTree(Inner.Builder->ClusterTree, Inner.Builder->OutOcclusionLayerNum);
|
|
if (bForceSync && GetWorld())
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FoliageGrassEndComp_SyncUpdate);
|
|
HierarchicalInstancedStaticMeshComponent->RecreateRenderState_Concurrent();
|
|
}
|
|
}
|
|
}
|
|
FCachedLandscapeFoliage::FGrassComp* Existing = FoliageCache.CachedGrassComps.Find(Inner.Key);
|
|
if (Existing)
|
|
{
|
|
Existing->Pending = false;
|
|
Existing->Touch();
|
|
}
|
|
delete Task;
|
|
if (!bForceSync)
|
|
{
|
|
break; // one per frame is fine
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FAsyncGrassTask::DoWork()
|
|
{
|
|
Builder->Build();
|
|
}
|
|
|
|
FAsyncGrassTask::~FAsyncGrassTask()
|
|
{
|
|
delete Builder;
|
|
}
|
|
|
|
static void FlushGrass(const TArray<FString>& Args)
|
|
{
|
|
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
|
|
{
|
|
ALandscapeProxy* Landscape = *It;
|
|
if (Landscape && !Landscape->IsTemplate() && !Landscape->IsPendingKill())
|
|
{
|
|
Landscape->FlushGrassComponents();
|
|
}
|
|
}
|
|
}
|
|
|
|
static void FlushGrassPIE(const TArray<FString>& Args)
|
|
{
|
|
for (TObjectIterator<ALandscapeProxy> It; It; ++It)
|
|
{
|
|
ALandscapeProxy* Landscape = *It;
|
|
if (Landscape && !Landscape->IsTemplate() && !Landscape->IsPendingKill())
|
|
{
|
|
Landscape->FlushGrassComponents(nullptr, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
static FAutoConsoleCommand FlushGrassCmd(
|
|
TEXT("grass.FlushCache"),
|
|
TEXT("Flush the grass cache, debugging."),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&FlushGrass)
|
|
);
|
|
|
|
static FAutoConsoleCommand FlushGrassCmdPIE(
|
|
TEXT("grass.FlushCachePIE"),
|
|
TEXT("Flush the grass cache, debugging."),
|
|
FConsoleCommandWithArgsDelegate::CreateStatic(&FlushGrassPIE)
|
|
);
|
|
|
|
|
|
|
|
#undef LOCTEXT_NAMESPACE |