Agrarsense
InstancedRenderer.cpp
Go to the documentation of this file.
1// Copyright (c) 2023 FrostBit Software Lab at the Lapland University of Applied Sciences
2//
3// This work is licensed under the terms of the MIT license.
4// For a copy, see <https://opensource.org/licenses/MIT>.
5
6#include "InstancedRenderer.h"
10
11#include "UObject/ConstructorHelpers.h"
12#include "Kismet/GameplayStatics.h"
13#include "Materials/Material.h"
14#include "TimerManager.h"
15#include "Engine/World.h"
16
18
19AInstancedRenderer::AInstancedRenderer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
20{
21 RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("SceneRoot"));
22 RootComponent->SetMobility(EComponentMobility::Static);
23 PrimaryActorTick.bCanEverTick = false;
24
25 // Load a default cube mesh asset
26 static ConstructorHelpers::FObjectFinder<UStaticMesh> DefaultMesh(TEXT("/Engine/BasicShapes/Cube.Cube"));
27 if (DefaultMesh.Succeeded())
28 {
29 ProxyMesh = DefaultMesh.Object;
30 }
31}
32
34{
35 Super::BeginPlay();
36
37 SetActorEnableCollision(false);
38
39 if (Instance == nullptr)
40 {
41 Instance = this;
42 }
43 else
44 {
45#if WITH_EDITOR
46 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Instance already exists. Destroying this actor.."));
47#endif
48 Destroy();
49 }
50
51 UWorld* World = GetWorld();
52
54 if (Settings)
55 {
58 }
59
61 if (Weather)
62 {
65 }
66
67#if WITH_EDITOR
68 FTimerHandle Handle;
69 World->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this]
70 {
71 LogInfo();
72 }), 1.0f, false);
73#endif
74}
75
77{
78#if WITH_EDITOR
79 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Unique mesh entries: %d | Total Instance Count: %d"), InstanceEntries.Num(), TotalInstanceCount);
80#endif
81}
82
83void AInstancedRenderer::EndPlay(const EEndPlayReason::Type EndPlayReason)
84{
85 Super::EndPlay(EndPlayReason);
86
87 if (Instance == this)
88 {
89 Instance = nullptr;
90 }
91 else
92 {
93 return;
94 }
95
96 UWorld* World = GetWorld();
97
99 if (Settings)
100 {
102 }
103
105 if (Weather)
106 {
108 }
109
110 // Destroy all Instanced entries
111 for (FInstanceEntry& Entry : InstanceEntries)
112 {
113 if (Entry.InstancedStaticMeshComponent)
114 {
115 Entry.InstancedStaticMeshComponent->ClearInstances();
116 Entry.InstancedStaticMeshComponent->UnregisterComponent();
117 Entry.InstancedStaticMeshComponent->DetachFromComponent(FDetachmentTransformRules::KeepRelativeTransform);
118 Entry.InstancedStaticMeshComponent->DestroyComponent();
119 Entry.InstancedStaticMeshComponent = nullptr;
120 }
121
122 Entry.Materials.Empty();
123 Entry.Mesh.Reset();
124 }
125
126 InstanceEntries.Empty();
127}
128
130{
131 if (InstancedActor == nullptr)
132 {
133#if WITH_EDITOR
134 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Actor is null!"));
135#endif
136 return false;
137 }
138
139 UStaticMeshComponent* StaticMeshComponent = InstancedActor->GetStaticMeshComponent();
140 if (StaticMeshComponent == nullptr)
141 {
142#if WITH_EDITOR
143 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent is null!"), *InstancedActor->GetName());
144#endif
145 return false;
146 }
147
148 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
149 if (Mesh == nullptr)
150 {
151#if WITH_EDITOR
152 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: AInstancedActor %s UStaticMeshComponent mesh is null!"), *InstancedActor->GetName());
153#endif
154 return false;
155 }
156
157 // Find or create InstancedStaticMeshComponents array index for this instance
158 int32 ComponentIndex = FindOrAddUniqueMesh(StaticMeshComponent, InstancedActor);
159
160 // Check index validity
161 if (!InstanceEntries.IsValidIndex(ComponentIndex))
162 {
163#if WITH_EDITOR
164 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: ComponentIndex is not valid!"));
165#endif
166 return false;
167 }
168
169 UInstancedStaticMeshComponent* InstancedStaticMeshComponent = InstanceEntries[ComponentIndex].InstancedStaticMeshComponent;
170 if (!InstancedStaticMeshComponent)
171 {
172#if WITH_EDITOR
173 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: UInstancedStaticMeshComponent is nullptr!"));
174#endif
175 return false;
176 }
177
178 // Get the StaticMeshComponent transform and add instance
179 FTransform ActorTransform = StaticMeshComponent->GetComponentTransform();
180 InstancedStaticMeshComponent->AddInstance(ActorTransform);
181
182 int32 InstanceIndex = InstancedStaticMeshComponent->GetNumRenderInstances() - 1;
183
184 // Notify the actor that instance has been added successfully.
185 InstancedActor->InstanceAdded(ComponentIndex, InstanceIndex);
186 //InstancedActor->Destroy();
187
189
190 return true;
191}
192
193int32 AInstancedRenderer::FindOrAddUniqueMesh(UStaticMeshComponent* StaticMeshComponent, AInstancedActor* InstancedActor)
194{
195 if (InstancedActor == nullptr || StaticMeshComponent == nullptr)
196 {
197#if WITH_EDITOR
198 UE_LOG(LogTemp, Warning, TEXT("StaticInstancedRenderer.cpp: StaticMeshComponent or InstancedActor is null!"));
199#endif
200 // return absurd number which should fail adding this instance to this renderer in above function.
201 return 99999999;
202 }
203
204 UStaticMesh* Mesh = StaticMeshComponent->GetStaticMesh();
205
206 if (!InstancedActor->HasAlternativeMaterial())
207 {
208 const int num = InstanceEntries.Num();
209 for (int32 Index = 0; Index < num; ++Index)
210 {
211 if (InstanceEntries[Index].Mesh == Mesh)
212 {
213 return Index;
214 }
215 }
216 }
217
218 const int count = StaticMeshComponent->GetNumMaterials();
219
220 TArray<UMaterialInterface*> MeshMaterials;
221 MeshMaterials.Reserve(count);
222
223 for (int32 i = 0; i < count; i++)
224 {
225 MeshMaterials.Add(StaticMeshComponent->GetMaterial(i));
226 }
227
228 const int Size = InstanceEntries.Num();
229 for (int32 Index = 0; Index < Size; ++Index)
230 {
231 if (InstanceEntries[Index].Mesh == Mesh)
232 {
233 bool bMaterialsMatch = true;
234
235 for (int32 MaterialIndex = 0; MaterialIndex < InstanceEntries[Index].Materials.Num(); ++MaterialIndex)
236 {
237 UMaterialInterface* UniqueMeshMaterial = InstanceEntries[Index].Materials[MaterialIndex];
238 UMaterialInterface* MeshMaterial = MeshMaterials[MaterialIndex];
239 if (UniqueMeshMaterial != MeshMaterial)
240 {
241 bMaterialsMatch = false;
242 }
243 }
244
245 if (bMaterialsMatch)
246 {
247 return Index;
248 }
249 }
250 }
251
252 // Else this is completely new mesh and/or mesh with alternative material,
253 // we need to create new FInstanceEntry and UInstancedStaticMeshComponent for it
254
255 // Create and setup new UInstancedStaticMeshComponent
256 UInstancedStaticMeshComponent* InstancedStaticMeshComponent = NewObject<UInstancedStaticMeshComponent>(this);
257
258 // Add Actor component list
259 InstancedStaticMeshComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform);
260 AddInstanceComponent(InstancedStaticMeshComponent);
261
262 InstancedStaticMeshComponent->bEditableWhenInherited = false;
263 InstancedStaticMeshComponent->RegisterComponent();
264 InstancedStaticMeshComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
265 InstancedStaticMeshComponent->CreatePhysicsState(false);
266 InstancedStaticMeshComponent->SetSimulatePhysics(false);
267 InstancedStaticMeshComponent->SetStaticMesh(Mesh);
268 InstancedStaticMeshComponent->SetMobility(EComponentMobility::Static);
269 InstancedStaticMeshComponent->SetHiddenInGame(false);
270 InstancedStaticMeshComponent->SetVisibility(true);
271 InstancedStaticMeshComponent->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
272 InstancedStaticMeshComponent->InstanceStartCullDistance = InstancedActor->GetInstanceStartCullDistance();
273 InstancedStaticMeshComponent->InstanceEndCullDistance = InstancedActor->GetInstanceEndCullDistance();
274
275 // Set WPO distance and evaluation
276 InstancedStaticMeshComponent->SetWorldPositionOffsetDisableDistance(WorldPositionOffsetDistance);
277 /*InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(true);
278 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(true);*/
279
280 bool AllowWorldPositionOffsetDisable = InstancedActor->GetAllowWorldPositionOffsetDisable();
281
282 // For whatever reason this only works when done a bit later..
283 FTimerHandle Handle;
284 GetWorld()->GetTimerManager().SetTimer(Handle, FTimerDelegate::CreateLambda([this, InstancedStaticMeshComponent, AllowWorldPositionOffsetDisable]
285 {
286 if (InstancedStaticMeshComponent && AllowWorldPositionOffsetDisable && CurrentWeatherParameters.WindIntensity < 0.05f)
287 {
288 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(false);
289 InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(false);
290 }
291 }), 0.350f, false);
292
293 // Thermal and Semantic camera use custom depth.
294 // RenderCustomDepth is set to false, if no Thermal or Semantic camera exists in the level,
295 // and is automatically set to true/false for all instances when they are spawned/destroyed.
296 // Doing this saves a lot rendering performance.
297 InstancedStaticMeshComponent->SetRenderCustomDepth(true);
298 InstancedStaticMeshComponent->SetCustomDepthStencilValue(StaticMeshComponent->CustomDepthStencilValue);
299
300 // Huge performance implication, recommend is Rigid (especially for Foliage with WPO).
301 // https://docs.unrealengine.com/5.3/en-US/PythonAPI/class/ShadowCacheInvalidationBehavior.html
302 InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = EShadowCacheInvalidationBehavior::Rigid;
303
304 // Create new FInstanceEntry
305 FInstanceEntry InstanceEntry;
306 InstanceEntry.InstancedStaticMeshComponent = InstancedStaticMeshComponent;
307 InstanceEntry.ActorClass = InstancedActor->GetClass();
308 InstanceEntry.Mesh = Mesh;
309 InstanceEntry.Materials = MeshMaterials;
310 InstanceEntry.InstanceEntryIndex = InstanceEntries.Num();
311 InstanceEntry.CustomDepthStencilValue = StaticMeshComponent->CustomDepthStencilValue;
312 InstanceEntry.AllowWorldPositionOffsetDisable = AllowWorldPositionOffsetDisable;
313 InstanceEntry.IsTree = InstancedActor->IsTreeActor();
314
315 // Set materials to newly created UInstancedStaticMeshComponent
316 const int32 MaterialCount = MeshMaterials.Num();
317 for (int32 MaterialIndex = 0; MaterialIndex < MaterialCount; ++MaterialIndex)
318 {
319 UMaterialInterface* MaterialInterface = InstanceEntry.Materials[MaterialIndex];
320
321 if (MaterialInterface)
322 {
323 InstancedStaticMeshComponent->SetMaterial(MaterialIndex, MaterialInterface);
324
325#if WITH_EDITOR
326 // Validate in Editor that material has bUsedWithInstancedStaticMeshes on
327 UMaterial* Material = Cast<UMaterial>(MaterialInterface);
328 if (Material)
329 {
330 if (!Material->bUsedWithInstancedStaticMeshes)
331 {
332 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Material '%s' bUsedWithInstancedStaticMeshes is not set to true!"), *Material->GetName());
333 }
334 }
335#endif
336 }
337 }
338
339 InstanceEntries.Add(InstanceEntry);
340
341 return InstanceEntries.Num() - 1;
342}
343
344void AInstancedRenderer::UpdateInstanceTransform(int32 ComponentIndex, int32 InstanceNumber, const FTransform& NewTransform)
345{
346 if (!InstanceEntries.IsValidIndex(ComponentIndex))
347 {
348 return;
349 }
350
351 FInstanceEntry& Entry = InstanceEntries[ComponentIndex];
352
354 {
355 Entry.InstancedStaticMeshComponent->UpdateInstanceTransform(InstanceNumber, NewTransform, true, true);
356 }
357}
358
359void AInstancedRenderer::RemoveInstance(int32 ComponentIndex, int32 InstanceNumber)
360{
361 if (!InstanceEntries.IsValidIndex(ComponentIndex))
362 {
363 return;
364 }
365
366 FInstanceEntry& Entry = InstanceEntries[ComponentIndex];
367
369 {
370 Entry.InstancedStaticMeshComponent->RemoveInstance(InstanceNumber);
371 }
372
374}
375
377{
378 Rendering = ShouldRender;
379 for (const FInstanceEntry& Entry : InstanceEntries)
380 {
381 if (Entry.InstancedStaticMeshComponent)
382 {
383 Entry.InstancedStaticMeshComponent->SetVisibility(Rendering);
384 }
385 }
386
387#if WITH_EDITOR
388 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Rendering: %s"), (Rendering ? TEXT("true") : TEXT("false")));
389#endif
390}
391
393{
394 if (!InstanceEntries.IsValidIndex(Index))
395 {
396#if WITH_EDITOR
397 UE_LOG(LogTemp, Warning, TEXT("InstancedRenderer.cpp: Index %d is not valid!"), Index);
398#endif
399 return;
400 }
401
402 FInstanceEntry& Entry = InstanceEntries[Index];
403
405 {
406 Entry.InstancedStaticMeshComponent->SetVisibility(Visible);
407 }
408}
409
411{
414}
415
417{
418 // When weather changes, we check current wind intensity to
419 // enable or disable certain instances world position offset evaluation.
420 // Disabling WPO completely when wind is 0.0 saves a lot of performance with foliage.
422 {
423 CurrentWeatherParameters = WeatherParameters;
424 bool ShouldRenderWPO = CurrentWeatherParameters.WindIntensity > 0.05f;
425 SetRenderWorldPositionOffet(ShouldRenderWPO);
426 }
427}
428
430{
431 for (FInstanceEntry& Entry : InstanceEntries)
432 {
433 if (Entry.InstancedStaticMeshComponent)
434 {
435 Entry.InstancedStaticMeshComponent->SetRenderCustomDepth(Enabled);
436 }
437 }
438}
439
441{
442 if (WorldPositionOffsetDistance == NewWPODistance)
443 {
444 return;
445 }
446
447 WorldPositionOffsetDistance = NewWPODistance;
448
449 for (FInstanceEntry& Entry : InstanceEntries)
450 {
451 if (Entry.InstancedStaticMeshComponent)
452 {
453 Entry.InstancedStaticMeshComponent->SetWorldPositionOffsetDisableDistance(WorldPositionOffsetDistance);
454 }
455 }
456}
457
458void AInstancedRenderer::SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
459{
460 for (FInstanceEntry& Entry : InstanceEntries)
461 {
462 if (Entry.InstancedStaticMeshComponent)
463 {
464 Entry.InstancedStaticMeshComponent->ShadowCacheInvalidationBehavior = ShadowCacheInvalidationBehaviour;
465 }
466 }
467}
468
470{
471 for (FInstanceEntry& Entry : InstanceEntries)
472 {
473 if (Entry.AllowWorldPositionOffsetDisable && Entry.InstancedStaticMeshComponent)
474 {
475 Entry.InstancedStaticMeshComponent->SetEvaluateWorldPositionOffset(RenderWPO);
476 Entry.InstancedStaticMeshComponent->SetEvaluateWorldPositionOffsetInRayTracing(RenderWPO);
477 }
478 }
479}
480
481void AInstancedRenderer::DebugMaterialImpact(bool SetUnrealDefaultMaterial)
482{
483 UMaterialInterface* DefaultMaterial = nullptr;
484 if (SetUnrealDefaultMaterial)
485 {
486 DefaultMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain(0));
487 }
488
489 for (FInstanceEntry& Entry : InstanceEntries)
490 {
491 if (Entry.AllowWorldPositionOffsetDisable && Entry.InstancedStaticMeshComponent)
492 {
493 if (SetUnrealDefaultMaterial)
494 {
495 for (int32 i = 0; i < Entry.InstancedStaticMeshComponent->GetNumMaterials(); ++i)
496 {
497 UMaterialInterface* CurrentMaterial = Entry.InstancedStaticMeshComponent->GetMaterial(i);
498 Entry.Materials.Add(CurrentMaterial);
499 }
500 for (int32 i = 0; i < Entry.InstancedStaticMeshComponent->GetNumMaterials(); ++i)
501 {
502 Entry.InstancedStaticMeshComponent->SetMaterial(i, DefaultMaterial);
503 }
504 }
505 else
506 {
507 if (Entry.Materials.Num() > 0)
508 {
509 for (int32 i = 0; i < Entry.Materials.Num(); ++i)
510 {
511 UMaterialInterface* OriginalMaterial = Entry.Materials[i];
512 Entry.InstancedStaticMeshComponent->SetMaterial(i, OriginalMaterial);
513 }
514 }
515 }
516 }
517 }
518}
519
521{
522#if WITH_EDITOR
523 for (FInstanceEntry& Entry : InstanceEntries)
524 {
525 auto& ISMComponent = Entry.InstancedStaticMeshComponent;
526
527 if (ISMComponent)
528 {
529 if (!Entry.UsingProxy)
530 {
531 ISMComponent->SetStaticMesh(ProxyMesh.Get());
532 Entry.UsingProxy = true;
533 }
534 else
535 {
536 ISMComponent->SetStaticMesh(Entry.Mesh.Get());
537 Entry.UsingProxy = false;
538 }
539 }
540 }
541#endif
542}
543
545{
546#if WITH_EDITOR
547 UWorld* World = GetWorld();
548
549 if (InstanceEntries.IsEmpty())
550 {
551 return;
552 }
553
554 for (FInstanceEntry& Entry : InstanceEntries)
555 {
556 UInstancedStaticMeshComponent* ISMComponent = Entry.InstancedStaticMeshComponent;
557 if (!ISMComponent)
558 {
559 continue;
560 }
561
562 ISMComponent->SetVisibility(false);
563
564 // Extract transforms from ISM component into TArray
565 TArray<FTransform> Transforms;
566
567 const int32 InstanceCount = ISMComponent->GetInstanceCount();
568
569 for (int32 i = 0; i < InstanceCount; i++)
570 {
571 FTransform Transform;
572 ISMComponent->GetInstanceTransform(i, Transform);
573 Transforms.Add(Transform);
574 }
575
576 // Spawn actors to level
577 UClass* Class = Entry.ActorClass;
578 for (const FTransform& Transform : Transforms)
579 {
580 AActor* SpawnedActor = World->SpawnActorDeferred<AActor>(Class, Transform);
581 if (SpawnedActor)
582 {
583 SpawnedActor->FinishSpawning(Transform);
584 }
585 }
586
587 ISMComponent->DestroyComponent();
588 }
589
590 InstanceEntries.Empty();
591#endif
592}
593
594bool AInstancedRenderer::SpawnInstanceBackToActor(UInstancedStaticMeshComponent* ISM, int32 Index)
595{
596 if (!ISM || Index < 0 || Index >= ISM->GetInstanceCount())
597 {
598 return false;
599 }
600
601 UWorld* World = GetWorld();
602 if (!World)
603 {
604 return false;
605 }
606
607 // Get the transform for the specified instance
608 FTransform InstanceTransform;
609 ISM->GetInstanceTransform(Index, InstanceTransform);
610
611 // Get the actor class from the associated entry
612 FInstanceEntry* Entry = InstanceEntries.FindByPredicate([ISM](const FInstanceEntry& InEntry)
613 {
614 return InEntry.InstancedStaticMeshComponent == ISM;
615 });
616
617 if (!Entry)
618 {
619 return false;
620 }
621
622 UClass* ActorClass = Entry->ActorClass;
623 if (!ActorClass)
624 {
625 return false;
626 }
627
628 // Spawn the actor
629 AActor* SpawnedActor = World->SpawnActorDeferred<AActor>(ActorClass, InstanceTransform);
630 if (SpawnedActor)
631 {
632 AInstancedActor* InstancedActor = Cast<AInstancedActor>(SpawnedActor);
633 if (InstancedActor)
634 {
635 InstancedActor->AddToInstancedRenderer = false;
636 }
637
638 SpawnedActor->FinishSpawning(InstanceTransform);
639
640 // Remove this ISM instance.
641 return ISM->RemoveInstance(Index);
642 }
643
644 return false;
645}
void InstanceAdded(int32 CompIndex, int32 InstanceNum)
int GetInstanceEndCullDistance() const
int GetInstanceStartCullDistance() const
bool HasAlternativeMaterial() const
UStaticMeshComponent * GetStaticMeshComponent() const
bool GetAllowWorldPositionOffsetDisable() const
bool IsTreeActor() const
TWeakObjectPtr< UStaticMesh > ProxyMesh
bool AddActorToInstancedRendering(AInstancedActor *InstancedActor)
void RemoveInstance(int32 ComponentIndex, int32 InstanceNumber)
void OnWeatherParametersChanged(FWeatherParameters WeatherParameters)
void SetShadowCacheBehaviour(EShadowCacheInvalidationBehavior ShadowCacheInvalidationBehaviour)
virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override
void UpdateInstanceTransform(int32 ComponentIndex, int32 InstanceNumber, const FTransform &NewTransform)
void SetInstancedRendering(bool ShouldRender)
virtual void BeginPlay() override
void OnGraphicsSettingsChanged(FGlobalGraphicsSettings GraphicsSettings)
bool SpawnInstanceBackToActor(UInstancedStaticMeshComponent *ISM, int32 Index)
TArray< FInstanceEntry > InstanceEntries
void SetWorldPositionOffsetDistance(int32 NewWPODistance)
void SetRenderWorldPositionOffet(bool RenderWPO)
FWeatherParameters CurrentWeatherParameters
int32 FindOrAddUniqueMesh(UStaticMeshComponent *StaticMeshComponent, AInstancedActor *InstancedActor)
void SetRenderCustomDepth(bool Enabled)
AInstancedRenderer(const FObjectInitializer &ObjectInitializer)
void DebugMaterialImpact(bool SetUnrealDefaultMaterial)
void SetComponentIndexVisibility(int32 Index, bool Visible)
static AInstancedRenderer * Instance
const FWeatherParameters & GetCurrentWeather() const
Definition: Weather.h:76
FLevelEventDelegate_WeatherChanged OnWeatherChanged
Definition: Weather.h:85
int32 GetWorldPositionOffsetRenderDistance() const
FGraphicsSettingsDelegate OnGraphicsSettingsChanged
static UAgrarsenseSettings * GetAgrarsenseSettings()
static AWeather * GetWeatherActor(const UObject *WorldContextObject)
EShadowCacheInvalidationBehavior FoliageShadowCacheInvalidationBehaviour
TWeakObjectPtr< UStaticMesh > Mesh
TArray< UMaterialInterface * > Materials
int32 CustomDepthStencilValue
bool AllowWorldPositionOffsetDisable
TSubclassOf< AActor > ActorClass
UInstancedStaticMeshComponent * InstancedStaticMeshComponent