포스트 프로세스 이펙트 개요
포스트 프로세스 이펙트는 게임의 시각적 품질을 크게 향상시킬 수 있는 강력한 도구입니다.
이 절에서는 언리얼 엔진의 포스트 프로세스 시스템을 C++에서 활용하고 확장하는 방법을 살펴보겠습니다.
APostProcessVolume 클래스 사용법
APostProcessVolume 클래스를 사용하여 특정 영역에 포스트 프로세스 효과를 적용할 수 있습니다.
UCLASS()
class MYGAME_API AMyPostProcessVolume : public APostProcessVolume
{
GENERATED_BODY()
public:
AMyPostProcessVolume();
UFUNCTION(BlueprintCallable, Category = "Post Process")
void SetBloomIntensity(float Intensity);
};
AMyPostProcessVolume::AMyPostProcessVolume()
{
bUnbound = true; // 전체 레벨에 적용
}
void AMyPostProcessVolume::SetBloomIntensity(float Intensity)
{
Settings.bOverride_BloomIntensity = true;
Settings.BloomIntensity = Intensity;
}
C++에서 포스트 프로세스 설정 제어
게임 로직에 따라 동적으로 포스트 프로세스 설정을 변경할 수 있습니다.
void AMyGameMode::UpdatePostProcessSettings(float DeltaTime)
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
// 시간에 따라 변화하는 비네트 효과
float VignetteIntensity = FMath::Sin(GetWorld()->GetTimeSeconds()) * 0.5f + 0.5f;
PPVolume->Settings.bOverride_VignetteIntensity = true;
PPVolume->Settings.VignetteIntensity = VignetteIntensity;
}
}
커스텀 포스트 프로세스 머티리얼
커스텀 포스트 프로세스 머티리얼을 생성하고 적용하는 방법
UCLASS()
class MYGAME_API UMyPostProcessManager : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Post Process")
void ApplyCustomPostProcess(UMaterial* PostProcessMaterial);
};
void UMyPostProcessManager::ApplyCustomPostProcess(UMaterial* PostProcessMaterial)
{
if (PostProcessMaterial)
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
PPVolume->Settings.WeightedBlendables.Array.Empty();
FWeightedBlendable NewBlendable;
NewBlendable.Object = PostProcessMaterial;
NewBlendable.Weight = 1.0f;
PPVolume->Settings.WeightedBlendables.Array.Add(NewBlendable);
}
}
}
포스트 프로세스 효과의 프로그래밍 제어
블룸, 톤 매핑, 색수차 등의 효과를 프로그래밍 방식으로 제어할 수 있습니다.
void AMyPostProcessController::UpdateEffects(float DeltaTime)
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
// 블룸 제어
PPVolume->Settings.bOverride_BloomIntensity = true;
PPVolume->Settings.BloomIntensity = FMath::Sin(GetWorld()->GetTimeSeconds()) * 2.0f + 3.0f;
// 톤 매핑 제어
PPVolume->Settings.bOverride_AutoExposureMethod = true;
PPVolume->Settings.AutoExposureMethod = EAutoExposureMethod::AEM_Manual;
PPVolume->Settings.bOverride_AutoExposureBias = true;
PPVolume->Settings.AutoExposureBias = FMath::Clamp(PlayerHealth / 100.0f, -1.0f, 1.0f);
// 색수차 제어
PPVolume->Settings.bOverride_SceneFringeIntensity = true;
PPVolume->Settings.SceneFringeIntensity = FMath::Max(0.0f, PlayerSpeed / MaxSpeed - 0.5f) * 5.0f;
}
}
동적 포스트 프로세스 효과 전환 구현
게임 상황에 따라 포스트 프로세스 효과를 부드럽게 전환할 수 있습니다.
UCLASS()
class MYGAME_API UPostProcessTransitionManager : public UObject
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Post Process")
void TransitionToEffect(UMaterialInterface* TargetEffect, float TransitionTime);
private:
UPROPERTY()
UMaterialInstanceDynamic* CurrentEffect;
UPROPERTY()
UMaterialInstanceDynamic* TargetEffect;
float TransitionProgress;
float TransitionDuration;
void UpdateTransition(float DeltaTime);
};
void UPostProcessTransitionManager::TransitionToEffect(UMaterialInterface* NewEffect, float TransitionTime)
{
if (NewEffect)
{
TargetEffect = UMaterialInstanceDynamic::Create(NewEffect, this);
TransitionProgress = 0.0f;
TransitionDuration = TransitionTime;
}
}
void UPostProcessTransitionManager::UpdateTransition(float DeltaTime)
{
if (CurrentEffect && TargetEffect)
{
TransitionProgress += DeltaTime / TransitionDuration;
if (TransitionProgress >= 1.0f)
{
CurrentEffect = TargetEffect;
TargetEffect = nullptr;
}
else
{
// 두 효과 사이의 블렌딩 수행
CurrentEffect->SetScalarParameterValue("BlendFactor", 1.0f - TransitionProgress);
TargetEffect->SetScalarParameterValue("BlendFactor", TransitionProgress);
}
}
}
성능을 고려한 포스트 프로세스 최적화
- 효과의 복잡도 조절 : 플랫폼에 따라 효과의 품질 조정
- 선택적 적용 : 필요한 상황에서만 고비용 효과 활성화
- LOD (Level of Detail) 시스템 구현 : 카메라 거리에 따라 효과 품질 조정
void AMyPostProcessOptimizer::OptimizeEffects()
{
UGameUserSettings* Settings = UGameUserSettings::GetGameUserSettings();
ESettingsDOF QualityLevel = Settings->GetPostProcessingQuality();
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
switch (QualityLevel)
{
case ESettingsDOF::Low:
PPVolume->Settings.bOverride_BloomQuality = true;
PPVolume->Settings.BloomQuality = 0;
break;
case ESettingsDOF::Medium:
PPVolume->Settings.bOverride_BloomQuality = true;
PPVolume->Settings.BloomQuality = 1;
break;
case ESettingsDOF::High:
PPVolume->Settings.bOverride_BloomQuality = true;
PPVolume->Settings.BloomQuality = 2;
break;
// 기타 품질 레벨 처리
}
}
}
커스텀 포스트 프로세스 셰이더 작성
HLSL을 사용하여 커스텀 포스트 프로세스 셰이더를 작성할 수 있습니다.
// MyCustomPostProcess.usf
##include "/Engine/Public/Platform.ush"
float4 MainPS(in float2 UVs : TEXCOORD0) : SV_Target0
{
float4 SceneColor = Texture2DSample(PostprocessInput0, PostprocessInput0Sampler, UVs);
// 흑백 효과 적용
float Luminance = dot(SceneColor.rgb, float3(0.299f, 0.587f, 0.114f));
return float4(Luminance, Luminance, Luminance, SceneColor.a);
}
이 셰이더를 머티리얼에서 사용하려면 Custom 노드를 추가하고 HLSL 코드를 직접 입력합니다.
포스트 프로세스 스택 관리
여러 포스트 프로세스 효과를 관리하고 우선순위를 지정할 수 있습니다.
UCLASS()
class MYGAME_API UPostProcessStackManager : public UObject
{
GENERATED_BODY()
public:
void PushEffect(UMaterialInterface* Effect, float Priority);
void PopEffect(UMaterialInterface* Effect);
void UpdateStack();
private:
TArray<TPair<UMaterialInterface*, float>> EffectStack;
};
void UPostProcessStackManager::PushEffect(UMaterialInterface* Effect, float Priority)
{
EffectStack.Add(TPair<UMaterialInterface*, float>(Effect, Priority));
EffectStack.Sort([](const TPair<UMaterialInterface*, float>& A, const TPair<UMaterialInterface*, float>& B) {
return A.Value > B.Value;
});
}
void UPostProcessStackManager::PopEffect(UMaterialInterface* Effect)
{
EffectStack.RemoveAll([Effect](const TPair<UMaterialInterface*, float>& Item) {
return Item.Key == Effect;
});
}
void UPostProcessStackManager::UpdateStack()
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
PPVolume->Settings.WeightedBlendables.Array.Empty();
for (const auto& EffectPair : EffectStack)
{
FWeightedBlendable NewBlendable;
NewBlendable.Object = EffectPair.Key;
NewBlendable.Weight = 1.0f;
PPVolume->Settings.WeightedBlendables.Array.Add(NewBlendable);
}
}
}
하드웨어 스펙별 포스트 프로세스 설정
하드웨어 성능에 따라 포스트 프로세스 설정을 자동으로 조정할 수 있습니다.
void AMyGameMode::ConfigurePostProcessForHardware()
{
UGameUserSettings* Settings = UGameUserSettings::GetGameUserSettings();
FIntPoint Resolution = Settings->GetScreenResolution();
int32 PixelCount = Resolution.X * Resolution.Y;
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
if (PixelCount > 2073600) // 1080p 이상
{
PPVolume->Settings.bOverride_MotionBlurAmount = true;
PPVolume->Settings.MotionBlurAmount = 1.0f;
}
else
{
PPVolume->Settings.bOverride_MotionBlurAmount = true;
PPVolume->Settings.MotionBlurAmount = 0.5f;
}
// GPU 성능에 따른 추가 설정
// ...
}
}
실시간 포스트 프로세싱 기반 게임플레이
포스트 프로세스 효과를 게임플레이 요소로 활용하면 플레이어의 경험을 크게 향상시킬 수 있습니다.
다음은 몇 가지 구체적인 예시와 구현 방법입니다.
1. 플레이어 체력 표현 : 체력이 낮을 때 비네트 효과 강화
2. 스피드 감각 : 캐릭터 속도에 따른 모션 블러 조절
3. 수중 효과 : 수중에 있을 때의 색상 변화 및 왜곡 효과
4. 히트 피드백 : 피격 시 화면 가장자리 효과
5. 시간 조작 : 슬로우 모션 효과
UCLASS()
class MYGAME_API AMyCharacter : public ACharacter
{
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, Category = "Post Process")
void UpdatePostProcessEffects();
private:
UPROPERTY(EditAnywhere, Category = "Character")
float MaxHealth = 100.0f;
UPROPERTY(VisibleAnywhere, Category = "Character")
float CurrentHealth;
UPROPERTY(EditAnywhere, Category = "Post Process")
UMaterialInterface* UnderwaterPostProcessMaterial;
UPROPERTY()
UMaterialInstanceDynamic* DynamicPPMaterial;
bool bIsUnderwater = false;
};
void AMyCharacter::UpdatePostProcessEffects()
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
// 1. 체력에 따른 비네트 효과
float HealthPercentage = CurrentHealth / MaxHealth;
PPVolume->Settings.bOverride_VignetteIntensity = true;
PPVolume->Settings.VignetteIntensity = FMath::Lerp(1.0f, 0.0f, HealthPercentage);
// 2. 속도에 따른 모션 블러
float Speed = GetVelocity().Size();
float MaxSpeed = GetCharacterMovement()->MaxWalkSpeed;
PPVolume->Settings.bOverride_MotionBlurAmount = true;
PPVolume->Settings.MotionBlurAmount = FMath::Lerp(0.0f, 1.0f, Speed / MaxSpeed);
// 3. 수중 효과
if (bIsUnderwater)
{
if (!DynamicPPMaterial)
{
DynamicPPMaterial = UMaterialInstanceDynamic::Create(UnderwaterPostProcessMaterial, this);
}
PPVolume->Settings.WeightedBlendables.Array.Empty();
FWeightedBlendable NewBlendable;
NewBlendable.Object = DynamicPPMaterial;
NewBlendable.Weight = 1.0f;
PPVolume->Settings.WeightedBlendables.Array.Add(NewBlendable);
}
else
{
PPVolume->Settings.WeightedBlendables.Array.Empty();
}
// 4. 히트 피드백 (예: 최근 피격 시간에 따른 효과)
float TimeSinceLastHit = GetWorld()->GetTimeSeconds() - LastHitTime;
if (TimeSinceLastHit < 0.5f)
{
float HitIntensity = FMath::Lerp(1.0f, 0.0f, TimeSinceLastHit / 0.5f);
PPVolume->Settings.bOverride_SceneFringeIntensity = true;
PPVolume->Settings.SceneFringeIntensity = HitIntensity * 5.0f;
}
else
{
PPVolume->Settings.bOverride_SceneFringeIntensity = false;
}
// 5. 시간 조작 효과
if (bIsSlowMotionActive)
{
PPVolume->Settings.bOverride_DepthOfFieldFocalDistance = true;
PPVolume->Settings.DepthOfFieldFocalDistance = 1000.0f;
PPVolume->Settings.bOverride_DepthOfFieldFstop = true;
PPVolume->Settings.DepthOfFieldFstop = 2.0f;
}
else
{
PPVolume->Settings.bOverride_DepthOfFieldFocalDistance = false;
PPVolume->Settings.bOverride_DepthOfFieldFstop = false;
}
}
}
이 UpdatePostProcessEffects
함수는 캐릭터의 상태에 따라 다양한 포스트 프로세스 효과를 실시간으로 조정합니다.
이 함수는 매 프레임 또는 필요에 따라 호출될 수 있습니다.
6. 스텔스 모드 : 플레이어가 스텔스 모드일 때 윤곽선 강조 효과
void AMyCharacter::ToggleStealthMode()
{
bIsStealthMode = !bIsStealthMode;
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
if (bIsStealthMode)
{
PPVolume->Settings.bOverride_AmbientOcclusionIntensity = true;
PPVolume->Settings.AmbientOcclusionIntensity = 2.0f;
PPVolume->Settings.bOverride_ColorGradingIntensity = true;
PPVolume->Settings.ColorGradingIntensity = 0.5f;
}
else
{
PPVolume->Settings.bOverride_AmbientOcclusionIntensity = false;
PPVolume->Settings.bOverride_ColorGradingIntensity = false;
}
}
}
7. 환경 상호작용 : 플레이어가 특정 영역에 진입할 때 환경에 따른 시각 효과
UCLASS()
class MYGAME_API AEnvironmentZone : public AActor
{
GENERATED_BODY()
public:
UPROPERTY(EditAnywhere, Category = "Post Process")
UMaterialInterface* ZonePostProcessMaterial;
UFUNCTION()
void OnPlayerEnterZone(AActor* OverlappedActor, AActor* OtherActor);
UFUNCTION()
void OnPlayerExitZone(AActor* OverlappedActor, AActor* OtherActor);
};
void AEnvironmentZone::OnPlayerEnterZone(AActor* OverlappedActor, AActor* OtherActor)
{
AMyCharacter* PlayerCharacter = Cast<AMyCharacter>(OtherActor);
if (PlayerCharacter)
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume && ZonePostProcessMaterial)
{
UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(ZonePostProcessMaterial, this);
PPVolume->Settings.WeightedBlendables.Array.Empty();
FWeightedBlendable NewBlendable;
NewBlendable.Object = DynamicMaterial;
NewBlendable.Weight = 1.0f;
PPVolume->Settings.WeightedBlendables.Array.Add(NewBlendable);
}
}
}
void AEnvironmentZone::OnPlayerExitZone(AActor* OverlappedActor, AActor* OtherActor)
{
AMyCharacter* PlayerCharacter = Cast<AMyCharacter>(OtherActor);
if (PlayerCharacter)
{
APostProcessVolume* PPVolume = Cast<APostProcessVolume>(UGameplayStatics::GetActorOfClass(GetWorld(), APostProcessVolume::StaticClass()));
if (PPVolume)
{
PPVolume->Settings.WeightedBlendables.Array.Empty();
}
}
}
이러한 실시간 포스트 프로세싱 효과를 게임플레이와 연동함으로써, 플레이어에게 더욱 몰입감 있는 경험을 제공할 수 있습니다.
캐릭터의 상태, 환경 변화, 특수 능력 등 다양한 게임플레이 요소를 시각적으로 강화하여 게임의 전반적인 품질을 향상시킬 수 있습니다.
주의할 점은 과도한 포스트 프로세스 효과 사용은 성능에 영향을 줄 수 있으므로, 적절한 최적화와 함께 사용해야 합니다.
또한 플레이어가 효과를 조절하거나 비활성화할 수 있는 옵션을 제공하는 것도 좋은 방법입니다.