icon안동민 개발노트

스크립트 최적화 기본 기법


 나이아가라 스크립트의 최적화는 효율적이고 고성능의 파티클 시스템을 구현하는 데 핵심적입니다.

 이 절에서는 스크립트 성능을 향상시키기 위한 다양한 기법을 살펴보겠습니다.

일반적인 최적화 전략

 불필요한 계산 줄이기

  • 조건문을 사용하여 필요한 경우에만 계산 수행
  • 루프 최소화 및 효율적인 루프 구조 사용

 예제

void OptimizedCalculation(inout float3 Position, float3 Velocity, float DeltaTime, bool ShouldUpdate)
{
    if (ShouldUpdate)
    {
        Position += Velocity * DeltaTime;
    }
}

 캐싱 활용

  • 자주 사용되는 값을 미리 계산하여 저장
  • 복잡한 연산 결과를 재사용

 예제

void CachedCalculation(inout float3 Position, float3 Velocity, float DeltaTime)
{
    static float CachedValue = 0;
    static float LastTime = 0;
    
    if (LastTime != Time)
    {
        CachedValue = ComplexFunction(Time);
        LastTime = Time;
    }
    
    Position += Velocity * DeltaTime * CachedValue;
}

 효율적인 데이터 구조 선택

  • 적절한 데이터 타입 사용 (예 : float vs. half)
  • 벡터화된 연산 활용

 예제

void VectorizedOperation(inout float4 Properties)
{
    Properties *= float4(1.1, 1.2, 1.3, 1.4);
}

스크립트 프로파일링

 1. 나이아가라 디버거 사용

  • 파티클 시스템의 각 모듈 실행 시간 분석
  • 병목 지점 식별

 2. 커스텀 프로파일링 코드 삽입

void ProfiledFunction()
{
    float StartTime = GetCurrentTime();
    // 실행할 코드
    float EndTime = GetCurrentTime();
    PrintDebug("Function execution time: " + (EndTime - StartTime));
}

 3. 언리얼 엔진의 내장 프로파일링 도구 활용

  • CPU 및 GPU 사용량 모니터링
  • 프레임 시간 분석

병목 지점 개선 기법

 1. 고비용 연산 최적화

  • 삼각 함수, 제곱근 등을 근사화된 빠른 버전으로 대체
float FastSin(float x)
{
    return x - x * x * x / 6.0f; // 테일러 급수 근사
}

 2. 조건문 최소화

  • 분기 예측이 어려운 조건문을 수학적 표현으로 대체
float ConditionalValue = lerp(Value1, Value2, step(Threshold, InputValue));

 3. 루프 최적화

  • 루프 언롤링 적용
  • 병렬 처리 가능한 구조로 변경

GPU 연산 활용

 1. 컴퓨트 셰이더 활용

  • 대량의 파티클 데이터를 병렬로 처리
[numthreads(64, 1, 1)]
void ComputeParticlePositions(uint3 ThreadID : SV_DispatchThreadID)
{
    // 각 스레드에서 파티클 위치 계산
}

 2. GPU 기반 시뮬레이션

  • 물리 시뮬레이션, 충돌 검사 등을 GPU에서 수행

 3. 텍스처 기반 연산

  • 복잡한 함수를 미리 계산된 텍스처로 대체
float ComplexFunction(float Input)
{
    return Texture2DSample(LookupTexture, Input, 0).r;
}

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

 1. Level of Detail (LOD) 시스템 구현

  • 거리에 따라 파티클 복잡도 조절
void UpdateParticleWithLOD(inout float3 Position, float3 Velocity, float DistanceFromCamera)
{
    float LODFactor = saturate(1.0 - DistanceFromCamera / 1000.0);
    Position += Velocity * DeltaTime * LODFactor;
}

 2. Instancing 활용

  • 유사한 파티클을 그룹화하여 일괄 처리
void UpdateParticleGroup(inout float3 GroupPosition, int ParticleCount)
{
    GroupPosition += GroupVelocity * DeltaTime;
    // 그룹 내 개별 파티클 업데이트
}

 3. Spatial Partitioning

  • 공간을 분할하여 상호작용 계산 최적화
void InteractWithinGrid(inout float3 Force, float3 Position, float3 GridCenter, float GridSize)
{
    if (distance(Position, GridCenter) < GridSize)
    {
        // 상호작용 로직
    }
}

메모리 사용 최적화

 1. 데이터 패킹

  • 여러 작은 값을 하나의 큰 데이터 타입으로 압축
uint PackData(float Value1, float Value2, float Value3, float Value4)
{
    return (f32tof16(Value1) << 24) | (f32tof16(Value2) << 16) | 
            (f32tof16(Value3) << 8)  | f32tof16(Value4);
}

 2. 메모리 정렬

  • 데이터 구조를 캐시 라인에 맞게 정렬
struct AlignedData
{
    float3 Position;
    float Padding;
    float3 Velocity;
    float Lifetime;
};

 3. 동적 할당 최소화

  • 가능한 정적 메모리 사용
  • 오브젝트 풀링 기법 활용

런타임 성능과 시각적 품질 균형

 1. 적응형 품질 조절

  • 프레임 레이트에 따라 파티클 수 조절
void AdjustParticleCount(inout int SpawnCount, float CurrentFPS, float TargetFPS)
{
    float Ratio = CurrentFPS / TargetFPS;
    SpawnCount = int(SpawnCount * saturate(Ratio));
}

 2. 중요도 기반 최적화

  • 화면 중앙이나 중요 객체 주변의 파티클에 더 많은 리소스 할당
float CalculateImportance(float2 ScreenPosition)
{
    return 1.0 - length(ScreenPosition - float2(0.5, 0.5)) * 2.0;
}

 3. 프리셋 시스템 구현

  • 다양한 성능 설정 프리셋 제공
  • 사용자가 성능과 품질 사이에서 선택할 수 있도록 함

최적화 사례 연구 : 복잡한 화염 효과

 다음은 최적화된 화염 효과 스크립트의 예입니다.

void OptimizedFlameEffect(inout float3 Position, inout float3 Velocity, 
                          inout float4 Color, inout float Size, 
                          float Age, float Lifetime, float3 NoiseTexture)
{
    float LifeRatio = Age / Lifetime;
    float2 NoiseUV = Position.xy * 0.1 + Age * 0.2;
    
    // 텍스처 기반 노이즈로 복잡한 계산 대체
    float3 Noise = NoiseTexture.SampleLevel(NoiseUV, 0).rgb;
    
    // 벡터화된 연산
    Velocity += (Noise - 0.5) * 10.0 * DeltaTime;
    Position += Velocity * DeltaTime;
    
    // LUT를 사용한 색상 변화
    Color = ColorGradientLUT.SampleLevel(LifeRatio, 0);
    
    // 단순화된 크기 계산
    Size = lerp(1.0, 0.1, LifeRatio) * (1.0 + Noise.x * 0.2);
    
    // LOD 기반 추가 효과
    if (DistanceFromCamera < 100.0)
    {
        // 근거리에서만 추가적인 복잡한 효과 적용
        ApplyDetailedFlameEffect(Position, Velocity, Color);
    }
}

 이 예제는 텍스처 기반 노이즈, 벡터화된 연산, LUT(Look-Up Table)를 사용한 색상 변화, 단순화된 크기 계산, 그리고 LOD 기반의 세부 효과 적용 등 다양한 최적화 기법을 적용하여 효율적이면서도 시각적으로 풍부한 화염 효과를 구현합니다.

 나이아가라 스크립트 최적화는 지속적인 프로파일링, 분석, 그리고 개선의 과정입니다.

 성능 병목 지점을 식별하고, 적절한 최적화 기법을 적용하며, 동시에 시각적 품질을 유지하는 것이 중요합니다.

 최적화된 스크립트는 더 많은 파티클, 더 복잡한 효과, 그리고 더 나은 전반적인 게임 성능을 가능하게 합니다.