icon안동민 개발노트

기본 수학 함수와 벡터 연산


 나이아가라 스크립트에서 기본 수학 함수와 벡터 연산은 복잡한 파티클 동작을 구현하는 데 필수적인 도구입니다.

 이러한 연산들을 효과적으로 활용하면 다양하고 역동적인 파티클 효과를 만들 수 있습니다.

기본 수학 함수

 삼각 함수

  • sin(x), cos(x), tan(x) : 기본 삼각 함수
  • asin(x), acos(x), atan(x) : 역삼각 함수
  • atan2(y, x): 2인자 아크탄젠트 함수

 예제 : 원형 움직임 생성

void CircularMotion(inout float3 Position, float Time, float Radius)
{
    Position.x = cos(Time) * Radius;
    Position.y = sin(Time) * Radius;
}

 지수와 로그 함수

  • pow(x, y) : x의 y승
  • exp(x) : e의 x승
  • log(x), log2(x), log10(x) : 자연로그, 밑이 2인 로그, 밑이 10인 로그

 예제 : 지수적 감소

float ExponentialDecay(float InitialValue, float DecayRate, float Time)
{
    return InitialValue * exp(-DecayRate * Time);
}

 보간 함수

  • lerp(a, b, t) : 선형 보간
  • smoothstep(a, b, t) : 부드러운 에르미트 보간

 예제 : 색상 변화

float4 ColorChange(float4 StartColor, float4 EndColor, float Progress)
{
    return lerp(StartColor, EndColor, smoothstep(0.0, 1.0, Progress));
}

 기타 유용한 함수

  • abs(x) : 절대값
  • min(x, y), max(x, y) : 최소값, 최대값
  • clamp(x, min, max) : 값을 특정 범위로 제한
  • saturate(x) : 값을 0과 1 사이로 제한 (clamp(x, 0, 1)와 동일)

 예제 : 범위 제한 움직임

float3 LimitedMovement(float3 Position, float3 MinBounds, float3 MaxBounds)
{
    return clamp(Position, MinBounds, MaxBounds);
}

벡터 연산

 기본 벡터 연산

  • 덧셈 : float3 result = a + b;
  • 뺄셈 : float3 result = a - b;
  • 스칼라 곱 : float3 result = a * scalar;

 내적 (Dot Product)

  • dot(a, b) : 두 벡터의 내적

 예제 : 방향 벡터 사이의 각도 계산

float AngleBetweenVectors(float3 a, float3 b)
{
    return acos(dot(normalize(a), normalize(b)));
}

 외적 (Cross Product)

  • cross(a, b) : 두 벡터의 외적

 예제 : 수직 벡터 생성

float3 GeneratePerpendicularVector(float3 Normal)
{
    return normalize(cross(Normal, float3(0, 1, 0)));
}

 정규화 (Normalization)

  • normalize(v) : 벡터를 단위 벡터로 변환

 예제 : 방향 벡터 생성

float3 GetDirectionVector(float3 Start, float3 End)
{
    return normalize(End - Start);
}

파티클 움직임 제어

 복잡한 파티클 움직임은 이러한 수학 함수와 벡터 연산을 조합하여 구현할 수 있습니다.

 예제 : 나선형 움직임

void SpiralMotion(inout float3 Position, float Time, float Radius, float Height, float Frequency)
{
    float Angle = Time * Frequency;
    Position.x = cos(Angle) * Radius;
    Position.y = sin(Angle) * Radius;
    Position.z = Height * Time;
}

 예제 : 중력 영향을 받는 파티클

void ApplyGravity(inout float3 Velocity, float3 GravityDirection, float GravityStrength, float DeltaTime)
{
    Velocity += GravityDirection * GravityStrength * DeltaTime;
}

복잡한 파티클 동작 구현

 여러 수학 함수와 벡터 연산을 조합하여 더욱 복잡한 동작을 만들 수 있습니다.

 예제 : 소용돌이 효과

void VortexEffect(inout float3 Position, inout float3 Velocity, float3 VortexCenter, float VortexStrength, float UpwardForce, float DeltaTime)
{
    float3 ToCenter = VortexCenter - Position;
    float Distance = length(ToCenter);
    float3 TangentialDir = normalize(cross(float3(0, 0, 1), ToCenter));
    
    // 회전력 적용
    Velocity += TangentialDir * (VortexStrength / max(Distance, 1.0)) * DeltaTime;
    
    // 상승력 적용
    Velocity.z += UpwardForce * DeltaTime;
    
    // 속도 적용
    Position += Velocity * DeltaTime;
}

 이 예제에서는 파티클이 중심점 주위를 회전하면서 상승하는 소용돌이 효과를 만듭니다. cross 함수를 사용하여 접선 방향을 계산하고, 거리에 따라 회전력을 조절합니다.

성능을 고려한 효율적인 수학 연산 팁

  1. 사전 계산 활용 : 자주 사용되는 값은 미리 계산하여 저장
float SinTime = sin(Time);
float CosTime = cos(Time);
// 여러 번 사용되는 SinTime과 CosTime 재활용
  1. 복잡한 함수 근사 : 고비용 함수를 간단한 다항식으로 근사
// 예: 빠른 역제곱근 근사 (Fast Inverse Square Root)
float FastInvSqrt(float x)
{
    float xhalf = 0.5f * x;
    int i = *(int*)&x;
    i = 0x5f3759df - (i >> 1);
    x = *(float*)&i;
    x = x * (1.5f - xhalf * x * x);
    return x;
}
  1. 벡터 연산 최적화 : 불필요한 정규화 피하기
// 방향만 필요한 경우, 길이가 1이 아니어도 됨
float3 Direction = Target - Position;
float DistanceSq = dot(Direction, Direction);
// normalize(Direction) 대신 Direction 직접 사용
  1. 조건문 대신 수학 식 사용 : 분기 없이 연산으로 해결
// 조건문 대신
float Sign = step(0, Value) * 2 - 1; // -1 또는 1
  1. SIMD 활용 : 벡터 연산을 통한 병렬 처리
float4 Result = float4(1, 2, 3, 4) * float4(5, 6, 7, 8);
// 4개의 곱셈을 동시에 수행

실제 적용 예시 : 불꽃놀이 효과

 다양한 수학 함수와 벡터 연산을 조합하여 불꽃놀이 효과를 만들어 보겠습니다.

void InitializeFirework(out float3 Position, out float3 Velocity, float3 LaunchPosition, float LaunchSpeed)
{
    Position = LaunchPosition;
    Velocity = float3(0, 0, LaunchSpeed);
}
 
void UpdateFirework(inout float3 Position, inout float3 Velocity, inout float4 Color, 
                    float Time, float Lifetime, float3 Gravity, float ExplosionTime)
{
    if (Time < ExplosionTime)
    {
        // 상승 단계
        Velocity += Gravity * DeltaTime;
        Position += Velocity * DeltaTime;
        Color = float4(1, 0.5, 0, 1); // 밝은 노란색
    }
    else
    {
        // 폭발 단계
        float ExplosionProgress = (Time - ExplosionTime) / (Lifetime - ExplosionTime);
        float ExplosionRadius = sin(ExplosionProgress * PI) * 50.0;
        float Angle = rand(Position.xy) * 2 * PI;
        float Elevation = rand(Position.yz) * PI;
        
        Velocity = float3(
            cos(Angle) * sin(Elevation),
            sin(Angle) * sin(Elevation),
            cos(Elevation)
        ) * ExplosionRadius;
        
        Position += Velocity * DeltaTime;
        
        // 색상 변화
        Color = lerp(float4(1, 0, 0, 1), float4(0, 0, 1, 0), ExplosionProgress);
    }
}

 이 스크립트는 초기 발사, 상승, 폭발, 그리고 소멸의 단계를 거치는 불꽃놀이 효과를 구현합니다. 삼각 함수, 벡터 연산, 보간 함수 등을 활용하여 복잡한 움직임과 시각 효과를 만들어냅니다.

 나이아가라 스크립트에서 수학 함수와 벡터 연산을 능숙하게 활용하면, 복잡하고 아름다운 파티클 효과를 구현할 수 있습니다. 이러한 기본적인 연산들을 조합하고 최적화하여 효율적이면서도 시각적으로 인상적인 효과를 만들어보세요. 지속적인 실험과 최적화를 통해 더욱 발전된 파티클 시스템을 구축할 수 있을 것입니다.