Make Unreal REAL.
article thumbnail
이득우의 게임 수학

 

3차원 공간에서의 회전 변환에 대해 생각해보자.

 

회전 변환은 물체의 형태를 보존해주는 강체 변환의 성질을 갖는다.

  • 따라서, 세 표준 기저 벡터는 동일한 크기와 직교성을 유지한 상태로 함께 움직여야 한다.

 

회전 변환으로 달라진 세 표준 기저 벡터를 알 수 있다면, 이를 열벡터로 만들어 회전 변환 행렬 R을 만들 수 있다.

 

 

하지만 이러한 방법은 매번 3개의 변화된 표준 기저 벡터 값을 계산해야 하기 때문에 비효율적이다.

 

따라서, 3차원 공간에서 회전을 지정할 때는 일반적으로 회전하는 중심축과 각으로 표현된 회전량을 지정하는 방식을 사용한다.

  • 2차원 공간의 극좌표계 방식과 유사하다.

 

하나의 평면만 존재하는 2차원과 달리 3차원은 무수히 많은 평면으로 구성되기 때문에, 새로운 오일러 각 방식이 필요하다.

 

 

오일러 각은 3차원 공간에서 물체가 놓인 방향을 3개의 각을 사용해 표시하는 방법이다.

 

표준 기저 벡터를 중심으로 회전하는 각의 크기로 지정된다.

 

 

그런데 프로그램마다 x, y, z축의 용도가 다르기 때문에, 축 대신 회전의 움직임으로 회전 동작을 구분해 각을 지정하는 Pitch, Roll, Yaw 방식을 사용한다.

  • 이 방식을 사용하면 서로 다른 프로그램 간에도 데이터를 쉽게 변환할 수 있다.

 

 

책의 예제인 CK소프트렌더러에는 Rotator 구조체를 통해 오릴러 각을 관리한다.

 

// 책의 예제인 CK소프트렌더러에 정의된 오일러 각
struct Rotator
{
public:
    FORCEINLINE void Clamp()
    {
        Yaw = GetAxisClampedValue(Yaw);
        Roll = GetAxisClampedValue(Roll);
        Pitch = GetAxisClampedValue(Pitch);
    }

    // 각의 범위를 [0°, 360°]로 제한한다.
    FORCEINLINE float GetAxisClampedValue(float InRotatorValue)
    {
        float angle = Math::FMod(InRotatorValue, 360.f);
        if (angle < 0.f)
        {
            angle += 360.f;
        }

        return angle;
    }

public:
    float Yaw = 0.f;
    float Roll = 0.f;
    float Pitch = 0.f;
};

 

오일러 각은 표준 기저 벡터를 중심으로 진행되는 세 번의 연속적인 회전을 뜻한다.

 

x축 회전은 yz 평면의 회전을 의미하는데, 이 때 x값은 변하지 않고 오직 y, z축의 값만 변한다.

 

 

이때 y축 회전만 sinα의 부호가 반대인 이유는, 3차원 공간에서는 x - y - z - x - y 순으로 순환되기 때문에 y축의 직교 평면은 xz 평면이 아니라 zx 평면이다.

  • x축의 직교 평면: yz평면
  • y축의 직교 평면: zx평면
  • z축의 직교 평면: xy평면

 

 

각 기저 축의 회전 행렬을 구했으니 이제 순서를 적용해 최종 회전 행렬을 만들어야 한다.

  • 책의 예제인 CK소프트렌더러에서는 언리얼 엔진의 오일러 각과 동일한 Roll - Pitch - Yaw 순서를 사용한다.

 

 

이렇게 계산된 회전 행렬 R의 열벡터는 표준 기저 벡터 xₑ, yₑ, zₑ가 회전 변환된 로컬 축 xₑ', yₑ', zₑ'을 의미한다.

 

 

책의 예제인 CK소프트렌더러에서는 위 식을 사용해 오일러 각으로부터 세 개의 로컬 축을 계산한다.

 

// 책의 예제인 CK소프트렌더러에서 오일러 각으로부터 로컬 축을 계산하는 함수
FORCEINLINE void GetLocalAxes(Vector3& OutRight, Vector3& OutUp, Vector3& OutForward)
{
    float cy = 0.f, sy = 0.f, cp = 0.f, sp = 0.f, cr = 0.f, sr = 0.f;
    Math::GetSinCos(sy, cy, Yaw);
    Math::GetSinCos(sp, cp, Pitch);
    Math::GetSinCos(sr, cr, Roll);

    OutRight   = Vector3(cy * cr + sy * sp * sr, cp * sr, -sy * cr + cy * sp * sr);
    OutUp      = Vector3(-cy * sr + sy * sp * cr, cp * cr, sy * sr + cy * sp * cr);
    OutForward = Vector3(sy * cp, -sp, cy * cp);
}

 

이렇게 얻은 로컬 축 벨터를 열벡터로 만든 회전 행렬 R은 다음과 같다.

 

 

책의 예제인 CK소프트렌더러에서 매 프레임마다 오일러 각으로부터 로컬 축 정보를 갱신하는 부분이다.

 

// 책의 예제인 CK소프트렌더러에서 매 프레임마다 오일러 각에 대응하는 로컬 축을 갱신하는 부분
class TransformComponent
{
public:
    void AddYawRotation(float InDegree)          { _Rotation.Yaw += InDegree; Update(); }
    void AddRollRotation(float InDegree)         { _Rotation.Roll += InDegree; Update(); }
    void AddPitchRotation(float InDegree)        { _Rotation.Pitch += InDegree; Update(); }
    void SetRotation(const Rotator & InRotation) { _Rotation = InRotation; Update(); }

    const Vector3& GetLocalX() const { return _Right; }
    const Vector3& GetLocalY() const { return _Up; }
    const Vector3& GetLocalZ() const { return _Forward; }
    void SetLocalAxes(const Vector3 & InRight, const Vector3 & InUp, const Vector3 & InForward)
    {
        _Right   = InRight;
        _Up      = InUp;
        _Forward = InForward;
    }

private:
    FORCEINLINE void Update()
    {
        _Rotation.Clamp();
        _Rotation.GetLocalAxes(_Right, _Up, _Forward);
    }

    Vector3 _Position = Vector3::Zero;
    Rotator _Rotation;
    Vector3 _Scale = Vector3::One;

    Vector3 _Right   = Vector3::UnitX;
    Vector3 _Up      = Vector3::UnitY;
    Vector3 _Forward = Vector3::UnitZ;
};

 

아래 변환 행렬들을 크기 - 회전 - 이동 순으로 곱해 만든 모델링 행렬 M은 다음과 같다.

 

 

책의 예제인 CK소프트렌더러에서 변환 행렬들의 값을 통해 모델링 행렬을 생성하는 부분이다.

 

// 책의 예제인 CK소프트렌더러에서 모델링 행렬의 생성
FORCEINLINE Matrix4x4 GetModelingMatrix() const;
{
    return Matrix4x4(
        Vector4(_Right * _Scale.X, false), 
        Vector4(_Up * _Scale.Y, false), 
        Vector4(_Forward * _Scale.Z, false),
        Vector4(_Position, true)
    );
}

'게임 수학 > 이득우의 게임 수학' 카테고리의 다른 글

짐벌 락(Gimbal Lock)  (0) 2023.05.07
카메라 공간  (0) 2023.05.06
3차원 공간의 설계  (0) 2023.05.04
카메라 시스템  (0) 2023.05.03
렌더링 파이프라인  (0) 2023.05.02
profile

Make Unreal REAL.

@diesuki4

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

검색 태그