Last active
March 7, 2025 13:36
-
-
Save alfredbaudisch/7978f1913e76640e2dc25b770ffa6ae1 to your computer and use it in GitHub Desktop.
Unreal Engine OcclusionAwarePlayerController to make actors/meshes transparent when they block the Camera. This is a simple see-through solution that does not require any changes to your scene or actors. To learn more details and instructions on how to use, check my post: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-act…
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* !! NOTICE !! | |
* Instructions: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-actors-transparent-block-camera-occlusion-see-through/ | |
*/ | |
// OcclusionAwarePlayerController.h | |
// By Alfred Reinold Baudisch (https://github.com/alfredbaudisch) | |
#pragma once | |
#include "CoreMinimal.h" | |
#include "GameFramework/PlayerController.h" | |
#include "Camera/CameraComponent.h" | |
#include "Components/CapsuleComponent.h" | |
#include "GameFramework/SpringArmComponent.h" | |
#include "OcclusionAwarePlayerController.generated.h" | |
USTRUCT(BlueprintType) | |
struct FCameraOccludedActor | |
{ | |
GENERATED_USTRUCT_BODY() | |
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) | |
const AActor* Actor; | |
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) | |
UStaticMeshComponent* StaticMesh; | |
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) | |
TArray<UMaterialInterface*> Materials; | |
UPROPERTY(VisibleAnywhere, BlueprintReadOnly) | |
bool IsOccluded; | |
}; | |
/** | |
* | |
*/ | |
UCLASS() | |
class YOURGAME_API AOcclusionAwarePlayerController : public APlayerController | |
{ | |
GENERATED_BODY() | |
public: | |
AOcclusionAwarePlayerController(); | |
protected: | |
// Called when the game starts | |
virtual void BeginPlay() override; | |
/** How much of the Pawn capsule Radius and Height | |
* should be used for the Line Trace before considering an Actor occluded? | |
* Values too low may make the camera clip through walls. | |
*/ | |
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Occlusion", | |
meta=(ClampMin="0.1", ClampMax="10.0") ) | |
float CapsulePercentageForTrace; | |
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Materials") | |
UMaterialInterface* FadeMaterial; | |
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components") | |
class USpringArmComponent* ActiveSpringArm; | |
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components") | |
class UCameraComponent* ActiveCamera; | |
UPROPERTY(BlueprintReadWrite, Category="Camera Occlusion|Components") | |
class UCapsuleComponent* ActiveCapsuleComponent; | |
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion") | |
bool IsOcclusionEnabled; | |
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category="Camera Occlusion|Occlusion") | |
bool DebugLineTraces; | |
private: | |
TMap<const AActor*, FCameraOccludedActor> OccludedActors; | |
bool HideOccludedActor(const AActor* Actor); | |
bool OnHideOccludedActor(const FCameraOccludedActor& OccludedActor) const; | |
void ShowOccludedActor(FCameraOccludedActor& OccludedActor); | |
bool OnShowOccludedActor(const FCameraOccludedActor& OccludedActor) const; | |
void ForceShowOccludedActors(); | |
__forceinline bool ShouldCheckCameraOcclusion() const | |
{ | |
return IsOcclusionEnabled && FadeMaterial && ActiveCamera && ActiveCapsuleComponent; | |
} | |
public: | |
UFUNCTION(BlueprintCallable) | |
void SyncOccludedActors(); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* !! NOTICE !! | |
* Instructions: https://alfredbaudisch.com/blog/gamedev/unreal-engine-ue/unreal-engine-actors-transparent-block-camera-occlusion-see-through/ | |
*/ | |
// OcclusionAwarePlayerController.cpp | |
// By Alfred Reinold Baudisch (https://github.com/alfredbaudisch) | |
#include "OcclusionAwarePlayerController.h" | |
#include "Kismet/GameplayStatics.h" | |
#include "Kismet/KismetSystemLibrary.h" | |
#include "Containers/Set.h" | |
AOcclusionAwarePlayerController::AOcclusionAwarePlayerController() | |
{ | |
CapsulePercentageForTrace = 1.0f; | |
DebugLineTraces = true; | |
IsOcclusionEnabled = true; | |
} | |
void AOcclusionAwarePlayerController::BeginPlay() | |
{ | |
Super::BeginPlay(); | |
if (IsValid(GetPawn())) | |
{ | |
ActiveSpringArm = Cast< | |
USpringArmComponent>(GetPawn()->GetComponentByClass(USpringArmComponent::StaticClass())); | |
ActiveCamera = Cast<UCameraComponent>(GetPawn()->GetComponentByClass(UCameraComponent::StaticClass())); | |
ActiveCapsuleComponent = Cast<UCapsuleComponent>( | |
GetPawn()->GetComponentByClass(UCapsuleComponent::StaticClass())); | |
} | |
} | |
void AOcclusionAwarePlayerController::SyncOccludedActors() | |
{ | |
if (!ShouldCheckCameraOcclusion()) return; | |
// Camera is currently colliding, show all current occluded actors | |
// and do not perform further occlusion | |
if (ActiveSpringArm->bDoCollisionTest) | |
{ | |
ForceShowOccludedActors(); | |
return; | |
} | |
FVector Start = ActiveCamera->GetComponentLocation(); | |
FVector End = GetPawn()->GetActorLocation(); | |
TArray<TEnumAsByte<EObjectTypeQuery>> CollisionObjectTypes; | |
CollisionObjectTypes.Add(UEngineTypes::ConvertToObjectType(ECC_WorldStatic)); | |
TArray<AActor*> ActorsToIgnore; // TODO: Add configuration to ignore actor types | |
TArray<FHitResult> OutHits; | |
auto ShouldDebug = DebugLineTraces ? EDrawDebugTrace::ForDuration : EDrawDebugTrace::None; | |
bool bGotHits = UKismetSystemLibrary::CapsuleTraceMultiForObjects( | |
GetWorld(), Start, End, ActiveCapsuleComponent->GetScaledCapsuleRadius() * CapsulePercentageForTrace, | |
ActiveCapsuleComponent->GetScaledCapsuleHalfHeight() * CapsulePercentageForTrace, CollisionObjectTypes, true, | |
ActorsToIgnore, | |
ShouldDebug, | |
OutHits, true); | |
if (bGotHits) | |
{ | |
// The list of actors hit by the line trace, that means that they are occluded from view | |
TSet<const AActor*> ActorsJustOccluded; | |
// Hide actors that are occluded by the camera | |
for (FHitResult Hit : OutHits) | |
{ | |
const AActor* HitActor = Cast<AActor>(Hit.GetActor()); | |
HideOccludedActor(HitActor); | |
ActorsJustOccluded.Add(HitActor); | |
} | |
// Show actors that are currently hidden but that are not occluded by the camera anymore | |
for (auto& Elem : OccludedActors) | |
{ | |
if (!ActorsJustOccluded.Contains(Elem.Value.Actor) && Elem.Value.IsOccluded) | |
{ | |
ShowOccludedActor(Elem.Value); | |
if (DebugLineTraces) | |
{ | |
UE_LOG(LogTemp, Warning, | |
TEXT("Actor %s was occluded, but it's not occluded anymore with the new hits."), *Elem.Value.Actor->GetName()); | |
} | |
} | |
} | |
} | |
else | |
{ | |
ForceShowOccludedActors(); | |
} | |
} | |
bool AOcclusionAwarePlayerController::HideOccludedActor(const AActor* Actor) | |
{ | |
FCameraOccludedActor* ExistingOccludedActor = OccludedActors.Find(Actor); | |
if (ExistingOccludedActor && ExistingOccludedActor->IsOccluded) | |
{ | |
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s was already occluded. Ignoring."), | |
*Actor->GetName()); | |
return false; | |
} | |
if (ExistingOccludedActor && IsValid(ExistingOccludedActor->Actor)) | |
{ | |
ExistingOccludedActor->IsOccluded = true; | |
OnHideOccludedActor(*ExistingOccludedActor); | |
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s exists, but was not occluded. Occluding it now."), *Actor->GetName()); | |
} | |
else | |
{ | |
UStaticMeshComponent* StaticMesh = Cast<UStaticMeshComponent>( | |
Actor->GetComponentByClass(UStaticMeshComponent::StaticClass())); | |
FCameraOccludedActor OccludedActor; | |
OccludedActor.Actor = Actor; | |
OccludedActor.StaticMesh = StaticMesh; | |
OccludedActor.Materials = StaticMesh->GetMaterials(); | |
OccludedActor.IsOccluded = true; | |
OccludedActors.Add(Actor, OccludedActor); | |
OnHideOccludedActor(OccludedActor); | |
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s does not exist, creating and occluding it now."), *Actor->GetName()); | |
} | |
return true; | |
} | |
void AOcclusionAwarePlayerController::ForceShowOccludedActors() | |
{ | |
for (auto& Elem : OccludedActors) | |
{ | |
if (Elem.Value.IsOccluded) | |
{ | |
ShowOccludedActor(Elem.Value); | |
if (DebugLineTraces) UE_LOG(LogTemp, Warning, TEXT("Actor %s was occluded, force to show again."), *Elem.Value.Actor->GetName()); | |
} | |
} | |
} | |
void AOcclusionAwarePlayerController::ShowOccludedActor(FCameraOccludedActor& OccludedActor) | |
{ | |
if (!IsValid(OccludedActor.Actor)) | |
{ | |
OccludedActors.Remove(OccludedActor.Actor); | |
} | |
OccludedActor.IsOccluded = false; | |
OnShowOccludedActor(OccludedActor); | |
} | |
bool AOcclusionAwarePlayerController::OnShowOccludedActor(const FCameraOccludedActor& OccludedActor) const | |
{ | |
for (int matIdx = 0; matIdx < OccludedActor.Materials.Num(); ++matIdx) | |
{ | |
OccludedActor.StaticMesh->SetMaterial(matIdx, OccludedActor.Materials[matIdx]); | |
} | |
return true; | |
} | |
bool AOcclusionAwarePlayerController::OnHideOccludedActor(const FCameraOccludedActor& OccludedActor) const | |
{ | |
for (int i = 0; i < OccludedActor.StaticMesh->GetNumMaterials(); ++i) | |
{ | |
OccludedActor.StaticMesh->SetMaterial(i, FadeMaterial); | |
} | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Late to this, but translucent materials are not compatible with nanite