icon안동민 개발노트

VFX 그래프와의 연동


 언리얼 엔진의 Niagara VFX 시스템은 강력하고 유연한 비주얼 이펙트 제작 도구입니다.

 이 절에서는 C++를 사용하여 Niagara 시스템을 제어하고 확장하는 방법을 살펴보겠습니다.

Niagara 시스템의 프로그래밍적 접근

 Niagara 시스템에 프로그래밍적으로 접근하려면 UNiagaraSystemUNiagaraComponent 클래스를 사용합니다.

##include "NiagaraSystem.h"
##include "NiagaraComponent.h"
##include "NiagaraFunctionLibrary.h"
 
UCLASS()
class MYGAME_API AMyVFXActor : public AActor
{
    GENERATED_BODY()
 
public:
    AMyVFXActor();
 
    UPROPERTY(VisibleAnywhere, Category = "VFX")
    UNiagaraComponent* NiagaraComponent;
 
    UFUNCTION(BlueprintCallable, Category = "VFX")
    void ActivateVFX();
};
 
AMyVFXActor::AMyVFXActor()
{
    NiagaraComponent = CreateDefaultSubobject<UNiagaraComponent>(TEXT("NiagaraVFX"));
    RootComponent = NiagaraComponent;
}
 
void AMyVFXActor::ActivateVFX()
{
    if (NiagaraComponent)
    {
        NiagaraComponent->Activate();
    }
}

C++에서 VFX 시스템 생성 및 제어

 런타임에 동적으로 VFX 시스템을 생성하고 제어할 수 있습니다.

void AMyGameMode::SpawnDynamicVFX(FVector Location)
{
    UNiagaraSystem* NiagaraSystem = LoadObject<UNiagaraSystem>(nullptr, TEXT("/Game/VFX/NS_Explosion"));
    if (NiagaraSystem)
    {
        UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
            this,
            NiagaraSystem,
            Location,
            FRotator::ZeroRotator,
            FVector::OneVector,
            true,
            true,
            ENCPoolMethod::AutoRelease
        );
 
        // VFX 시스템 추가 설정
        if (NiagaraComponent)
        {
            NiagaraComponent->SetVariableFloat(FName("ExplosionSize"), 200.0f);
        }
    }
}

커스텀 Niagara 모듈 구현

 C++에서 커스텀 Niagara 모듈을 구현하여 VFX 시스템의 기능을 확장할 수 있습니다.

##include "NiagaraDataInterfaceSimpleCounter.h"
 
UCLASS()
class MYGAME_API UNiagaraDICustomLogic : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
    virtual bool CanExecuteOnTarget(ENiagaraSimTarget SimTarget) const override { return SimTarget == ENiagaraSimTarget::GPUComputeSim; }
 
    DECLARE_FUNCTION(CalculateCustomValue);
};
 
void UNiagaraDICustomLogic::GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions)
{
    FNiagaraFunctionSignature Sig;
    Sig.Name = TEXT("CalculateCustomValue");
    Sig.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Input")));
    Sig.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetFloatDef(), TEXT("Output")));
    Sig.bMemberFunction = true;
    Sig.bRequiresContext = false;
    OutFunctions.Add(Sig);
}
 
void UNiagaraDICustomLogic::CalculateCustomValue(FVectorVMContext& Context)
{
    VectorVM::FUserPtrHandler<UNiagaraDICustomLogic> InstanceData(Context);
    VectorVM::FExternalFuncInputHandler<float> InputParam(Context);
    VectorVM::FExternalFuncRegisterHandler<float> OutValue(Context);
 
    for (int32 i = 0; i < Context.NumInstances; ++i)
    {
        float Input = InputParam.GetAndAdvance();
        float Result = FMath::Sin(Input) * 0.5f + 0.5f; // 예시 계산
        *OutValue.GetDestAndAdvance() = Result;
    }
}

VFX 파라미터의 동적 업데이트

 게임플레이 중 VFX 시스템의 파라미터를 동적으로 업데이트할 수 있습니다.

void AMyCharacter::UpdateVFXIntensity(float Intensity)
{
    if (AuraVFXComponent)
    {
        AuraVFXComponent->SetVariableFloat(FName("Intensity"), Intensity);
    }
}

게임플레이 이벤트와 VFX 시스템 연동

 게임플레이 이벤트에 따라 VFX 시스템을 트리거하고 제어할 수 있습니다.

void AMyCharacter::OnTakeDamage(float Damage)
{
    // 기존 데미지 처리 로직
 
    // VFX 트리거
    if (DamageVFXSystem)
    {
        UNiagaraComponent* DamageVFX = UNiagaraFunctionLibrary::SpawnSystemAttached(
            DamageVFXSystem,
            GetMesh(),
            NAME_None,
            FVector::ZeroVector,
            FRotator::ZeroRotator,
            EAttachLocation::SnapToTarget,
            true
        );
 
        if (DamageVFX)
        {
            DamageVFX->SetVariableFloat(FName("DamageAmount"), Damage);
        }
    }
}

대규모 파티클 시스템의 최적화 전략

  1. GPU 시뮬레이션 활용
  2. LOD (Level of Detail) 시스템 구현
  3. 인스턴싱 및 풀링 사용
UCLASS()
class MYGAME_API UVFXOptimizer : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "VFX Optimization")
    void OptimizeVFXSystem(UNiagaraComponent* NiagaraComponent, float DistanceFromCamera);
 
private:
    UPROPERTY(EditAnywhere, Category = "VFX Optimization")
    TArray<float> LODDistances;
 
    UPROPERTY(EditAnywhere, Category = "VFX Optimization")
    TArray<int32> LODParticleCounts;
};
 
void UVFXOptimizer::OptimizeVFXSystem(UNiagaraComponent* NiagaraComponent, float DistanceFromCamera)
{
    if (NiagaraComponent)
    {
        int32 LODLevel = LODDistances.Num() - 1;
        for (int32 i = 0; i < LODDistances.Num(); ++i)
        {
            if (DistanceFromCamera < LODDistances[i])
            {
                LODLevel = i;
                break;
            }
        }
 
        NiagaraComponent->SetVariableInt(FName("ParticleCount"), LODParticleCounts[LODLevel]);
    }
}

VFX 그래프 에셋 로드 및 인스턴스화

 VFX 그래프 에셋을 동적으로 로드하고 인스턴스화할 수 있습니다.

void AMyVFXManager::LoadAndSpawnVFX(FString VFXAssetPath, FVector Location)
{
    UNiagaraSystem* NiagaraSystem = LoadObject<UNiagaraSystem>(nullptr, *VFXAssetPath);
    if (NiagaraSystem)
    {
        UNiagaraComponent* NiagaraComponent = UNiagaraFunctionLibrary::SpawnSystemAtLocation(
            this,
            NiagaraSystem,
            Location
        );
 
        // 필요한 경우 추가 설정
    }
}

런타임 VFX 속성 수정

 게임 중 VFX 시스템의 속성을 동적으로 수정할 수 있습니다.

void AMyVFXController::ModifyVFXProperties(UNiagaraComponent* NiagaraComponent, float Intensity, FLinearColor Color)
{
    if (NiagaraComponent)
    {
        NiagaraComponent->SetVariableFloat(FName("Intensity"), Intensity);
        NiagaraComponent->SetVariableLinearColor(FName("Color"), Color);
    }
}

VFX 시스템의 성능 모니터링 및 디버깅

 VFX 시스템의 성능을 모니터링하고 디버깅하기 위한 도구를 구현할 수 있습니다.

UCLASS()
class MYGAME_API UVFXDebugger : public UObject
{
    GENERATED_BODY()
 
public:
    UFUNCTION(BlueprintCallable, Category = "VFX Debug")
    void LogVFXPerformance(UNiagaraComponent* NiagaraComponent);
};
 
void UVFXDebugger::LogVFXPerformance(UNiagaraComponent* NiagaraComponent)
{
    if (NiagaraComponent)
    {
        int32 ParticleCount = NiagaraComponent->GetSystemInstanceCount();
        float SimulationTime = NiagaraComponent->GetSystemSimulationTime();
 
        UE_LOG(LogTemp, Log, TEXT("VFX Performance - Particles: %d, Simulation Time: %f ms"), ParticleCount, SimulationTime);
    }
}

고급 VFX 기법 구현

 GPU 시뮬레이션

 GPU 시뮬레이션을 활용하여 대규모 파티클 시스템의 성능을 향상시킬 수 있습니다.

UCLASS()
class MYGAME_API UNiagaraDataInterfaceGPUSimulation : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    virtual bool InitializePerInstanceData(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance) override;
    virtual bool PerInstanceTick(void* PerInstanceData, FNiagaraSystemInstance* SystemInstance, float DeltaSeconds) override;
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
 
    // GPU 시뮬레이션 관련 함수 선언
};

 메시 파티클

 복잡한 형태의 파티클을 위해 메시 파티클을 구현할 수 있습니다.

UCLASS()
class MYGAME_API UNiagaraDataInterfaceMeshParticle : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    UPROPERTY(EditAnywhere, Category = "Mesh Particle")
    UStaticMesh* ParticleMesh;
 
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
 
    // 메시 파티클 관련 함수 선언
};

VFX 시스템과 물리 엔진의 통합

 VFX 시스템을 물리 엔진과 통합하여 더욱 사실적인 효과를 만들 수 있습니다.

UCLASS()
class MYGAME_API UNiagaraDataInterfacePhysics : public UNiagaraDataInterface
{
    GENERATED_BODY()
 
public:
    virtual void GetFunctions(TArray<FNiagaraFunctionSignature>& OutFunctions) override;
 
    UFUNCTION(BlueprintCallable, Category = "VFX Physics")
    void ApplyForceToParticles(UNiagaraComponent* NiagaraComponent, FVector Force);
};
 
void UNiagaraDataInterfacePhysics::ApplyForceToParticles(UNiagaraComponent* NiagaraComponent, FVector Force)
{
    if (NiagaraComponent)
    {
        NiagaraComponent->SetVariableVec3(FName("ExternalForce"), Force);
    }
}

 C++를 통해 Niagara VFX 시스템을 효과적으로 제어하고 확장할 수 있습니다.

 게임플레이와 VFX를 긴밀하게 연동하고, 성능을 최적화하며, 고급 VFX 기법을 구현할 수 있습니다.