개요
지금까지는 메쉬의 큰 변화없이 트랜스폼만을 이용하여 오브젝트들을 움직였다. 이제는 뼈대들의 계층구조를 이용하여 좀 더 다채로운 애니메이션을 만들어보자.
뼈대 계층구조
우리는 부모 자식 관계로 트리 형태의 계층구조를 가지는 뼈대 계층구조를 이용하여 애니메이션을 만들 것이다. 여기에서의 핵심은 부모의 트랜스폼이 변화하면 자식의 트랜스폼도 따라서 변화한다는 점이다. 예를 들어 우리 몸의 관절을 표현한 뼈대 계층구조가 있다고하자. 그 중 오른팔의 부분을 보면 윗팔, 아랫팔, 손이라는 계층구조가 존재한다. 이때 손이 움직이면 손만 움직이지만 아랫팔이 움직이면 그의 하위 계층인 손도 따라움직이게 된다. 윗팔이 움직이면 아랫팔, 손 모두가 움직일 것이다. 이러한 계층구조로 우리는 다양한 뼈대 애니메이션을 구현할 것이다.
수학적 관점의 뼈대 계층구조
뼈대 계층구조를 수학적으로 생각하면 다음과 같다. 한 뼈대는 한 좌표계로 표현되며 그 좌표계에는 자식 좌표계와 정점들이 존재한다. 좌표계에 속한 정점은 그 뼈대의 직접적인 메쉬(피부)에 해당하게 되며 자식 좌표계들은 자식 뼈대들이 되게 된다. 만약 그 좌표계가 움직이게 되면 그에 속한 자식 좌표계들과 정점들이 움직이게 되며 이는 부모 뼈대의 움직임이 그 뼈대의 피부와 자식 뼈대들의 움직임으로 전이되는 것에 해당된다.
뼈대에서의 좌표변환
애니매이션에서 한 시점에서 관절들의 상태는 각 관절의 부모 좌표계시점에서의 트랜스폼들에 의해 표현된다. 즉 자식 좌표계가 부모 좌표계위에 어떤 위치, 회전, 크기로 존재하는지의 모음으로 모든 관절이 현재 상태가 나타나게 된다. 우리는 이러한 정보가 주어졌을 때 한 뼈대의 뼈대 좌표계 기준의 점의 좌표를 해당 오브젝트가 존재하는 오브젝트 공간 기준의 좌표계로 변환할 필요가 있다.
이는 좌표 변환 행렬로 구현할 수 있다. 우선 각 뼈대에서 그 뼈대의 부모 좌표계로의 변환 행렬은 쉽게 구할 수 있다. 우리가 기존에 오브젝트 좌표계를 월드좌표계로 변환할 때 썼던 월드행렬을 그 오브젝트 좌표계의 트랜스폼을 이용하여 구하였듯이 자식 좌표계를 오브젝트 좌표계로, 부모 좌표계를 월드 좌표계로 생각하여 그대로 적용시키면 자식에서 부모좌표계로 가는 부모 변환 행렬을 구할 수 있다. 이때 뿌리 뼈대의 부모 변환 행렬은 오브젝트 좌표계로 가는 행렬이라고 보면 된다.(뿌리 뼈대 좌표계를 오브젝트 좌표계와 동일하게 설정한다면 항등행렬로 사용할 수 있다.) 이렇게 각 뼈대의 부모 변환 행렬을 구했다면 각 뼈대의 부모 변환 행렬에 그 부모의 부모 변환 행렬을 곱하면 조부모 변환 행렬을 얻을 수 있다. 이런 식으로 반복하면 모든 뼈대의 오브젝트 좌표 변환 행렬을 구할 수 있다.
이를 좀더 효율적으로 하려면 뿌리 뼈대에서부터 부모가 자식보다 먼저오게 하여 오브젝트 좌표 변환 행렬을 구하면된다. 뿌리 뼈대는 부모 변환 행렬이 곧 오브젝트 좌표 변환 행렬(이후 로컬 행렬이라고 표현)이다. 이후 다른 뼈대들에 자신의 부모 변환 행렬 * 부모의 로컬 행렬을 하면 각자의 로컬 행렬을 쉽게 구할 수 있다.
이렇게 각 뼈대의 로컬 행렬을 구하면 각 뼈대의 좌표계 기준으로 표현됬던 정점을 로컬 좌표계 기준의 뼈대로 바꿀 수 있으며 이를 통해 관절 애니메이션에 따른 로컬 메쉬 정점들을 구할 수 있게 된다.
뼈대 별 메쉬 렌더링 방식
관절 애니메이션을 하는 오브젝트를 만드는데는 대표적으로 두가지 방법이 있다. 첫째는 각 뼈대의 메쉬를 별개의 메쉬로 만들고 계층구조를 만들어 렌더링하는 방식, 두번째는 하나의 메쉬로 오브젝트 전체의 메쉬를 만들고 따로 만든 관절을 합치는 방식이다. 후자의 경우 메쉬의 정점들이 따로 만든 관절의 몇번째 뼈대에 어떤 가중치로 영향을 받는지에 대한 정보를 포함하여야한다.
이중 전자의 방식을 먼저 살펴보자. 이는 우선 각 메쉬를 뼈대 계층구조를 정의하여 배치한다. 그후 애니메이션 클립에서 키프레임 별로 각 뼈대가 부모 뼈대 좌표계 상에 어떤 트랜스폼으로 위치하는지를 전체 오브젝트 로컬 좌표계 기준 트랜스폼으로 작성한다. 이후 각 뼈대 메쉬들을 렌더링 할때 해당 프레임에서의 보간된 키프레임 값으로 각 뼈대 별 부모 변환 행렬을 구하고 이를 이용하여 로컬 행렬을 구한다. 이후 각 뼈대 메쉬를 그 뼈대의 로컬 행렬을 곱하여 오브젝트 좌표를 구하고 (해당 관절 오브젝트의 모든 뼈대에 공유되는)월드행렬을 곱해 월드 좌표를 구한다. 이렇게 각 메쉬를 렌더링하면 계층구조에 의한 오브젝트가 나타나며 매 프레임 애니메이션 값들이 바뀌며 애니메이션이 진행된다.
메시 스키닝 렌더링 방식
후자의 방법이 바로 메시 스키닝 방법이다. 메시 스키닝은 각 뼈대의 메쉬가 하나의 메쉬로써 만들어져 있으며 그와 동시에 그 오브젝트 공간에서 정의된 관절과 그 메쉬의 각 정점에 저장된 그 정점이 어느 뼈대에(어떤 가중치로) 영향을 받는지에 대한 정보로 애니메이션을 진행하는 방법이다. 여기서 가중치는 각 정점이 한 뼈대에만 영향을 받아 딱딱하게 움직이는 것이 아닌 여러 뼈대에 다양한 가중치로 영향을 받아 부드러운 피부같은 애니메이션을 구현하는 웨이팅 기법을 쓸때 사용된다. 우선은 이러한 웨이팅 기법없이 각 정점이 한 뼈대에 영향을 받는다고 가정하고 설명을 진행 하겠다.
우선 메쉬의 각 정점은 어떤 뼈대에 영향을 받는지를 정점 데이터로 가지고 있으며 기본 자세에 해당하는 위치를 가지고 있다. 그리고 각 뼈대는 해당 뼈대가 기본상태일때 오브젝트 공간에서 어떤 트랜스폼으로 존재하는지와 계층구조를 가진 상태로 존재한다. 이때 각 뼈대의 트랜스폼은 오프셋 행렬로 표현되는데 오프셋 행렬은 오브젝트 공간의 한 점을 기본 상태의 해당 뼈대의 좌표계로 좌표 변환하는 변환 행렬이다. 즉 오프셋 행렬은 오브젝트 공간상에서의 해당 뼈대의 트랜스폼을 대신할 수 있다.(오프셋 행렬은 메쉬를 디자인 하는 시점에서 생성하는데 계층 구조를 따라 로컬 행렬을 구한뒤 역행렬로 구할 수 도 있겠지만 그냥 오브젝트 공간상의 트랜스폼으로 좌표 변환 행렬을 구하면 바로 구할 수 있다.) 또한 뼈대 계층 구조는 배열의 각 인덱스에 해당 인덱스의 뼈대의 부모 인덱스를 넣음으로써 각 뼈대의 부모가 누군지에 대한 정보로 표현한다.
이러한 정보가 생성된 상태에서 애니메이션 클립은 각 키프레임에 각 뼈대의 부모 뼈대 좌표계 기준 해당 뼈대의 트랜스폼을 기록하여 저장된다. 이러면 특정 시점의 프레임에서 키프레임을 보간하여 해당 시점의 각 뼈대 부모 기준 트랜스폼을 구할 수 있고 이를 이용하여 각 뼈대의 해당 시점 부모 변환 행렬과 로컬 행렬을 구할 수 있다.
이를 이용하여 애니메이션을 구현하려면 매 프레임 해당 시점의 각 뼈대에 대한 로컬 행렬을 구하고 그것들을 각 뼈대의 오프셋 행렬에 곱하여 최종 변환 행렬을 구한다. 이 행렬에 오브젝트 공간의 기본 자세 메쉬 정점의 위치를 곱하면 오프셋 행렬에 의해 정점이 오브젝트 좌표계에서 기본 자세 관절 기준의 해당 관절 좌표계로 변환된 후 로컬 행렬에 의하여 현재 프레임의 관절 애니메이션에 따른 해당 정점의 오브젝트 공간 좌표로 변환 해준다. 즉 기본 자세의 메쉬 정점을 i번째 뼈대 최종 변환 행렬에 곱한다는 것은 i번째 뼈대에 해당 정점이 속했다고 했을 때 현재 프레임의 애니메이션에서 해당 정점의 오브젝트 공간 좌표가 나오게 된다.
이렇게 현재 프레임의 각 뼈대의 최종 변환 행렬을 모두 구한 뒤 이를 자원으로 달아 전체 메쉬를 기하구조로 넣어 렌더링 파이프라인을 실행한다. 이후 정점 셰이더에서 각 정점의 기본자세 로컬 위치, 법선, 접선을 해당 정점이 속한 뼈대의 최종 변환 행렬에 곱해 애니메이션 된 후 의 로컬 위치, 법선, 접선을 구한다. 이후 월드 변환등의 기존 연산을 그대로 수행하면 애니메이션된 메쉬가 렌더링되게 된다.
웨이팅
앞서 말한 방식으로 애니메이션을 하게 되면 각 부위의 메쉬는 관절을 따라 단단하게 애니메이션된다. 즉 피부같은 부드러운 느낌은 나지않는다. 이를 위해서는 메쉬의 각 정점이 한 뼈대가 아닌 여러 뼈대에 영향을 받도록 하고 각 뼈대에 합쳐서 1이 되도록 가중치를 주면된다. 예를 들어 한 정점이 인근 네개의 뼈대에 (0.2,0.3,0.3,0.2)로 영향을 받는다고하면 해당 정점의 최종 오브젝트 좌표를 계산하기 위해서는 기본 로컬 위치, 법선 접선에 각 네개의 뼈대의 최종 변환 행렬을 각각 수행하고 그 네개의 결과를 가중치로 가중 평균하면 된다. 이렇게 하면 여러 뼈대에 영향을 받는 부드러운 애니메이션이 수행된다.
메쉬 부분집합
메쉬를 렌더링할때 메쉬의 각 부분 별로 다른 재질(텍스처 포함)이나 쉐이더를 사용하고 싶을 수 있다. 이를 위해 메쉬를 저장할 때 메쉬의 정점과 삼각형들을 다른 재질이나 쉐이더를 사용하는 부분들로 나누어 모아 저장하고 그 경계를 기록하여 저장한다. 그리고 각 부분 집합이 어떤 재질과 쉐이더를 사용하는지에 대한 정보도 같이 저장한다. 이후 그 메쉬를 렌더링 할때는 각 부분집합을 하나의 렌더 타겟으로 생각하여 그 부분집합의 재질, 텍스처자원과 쉐이더를 이용하여 렌더링 하면된다. 이때 해당 메쉬의 최종 변환 행렬은 모두 공유하여 사용하도록 한다. 이렇게 하면 부분 집합의 각 정점이 자신이 영향을 받는 뼈대의 인덱스와 가중치만 그대로 잘 적어놨다면 부분 집합들을 따로 렌더링 하더라도 최종 변환 행렬의 배열로 부터 자신의 뼈대들에 대한 정보를 잘 가져와 적절한 오브젝트 공간으로 변환 될테고 부분적으로 잘 렌더링 될것이다.이러한 부분들이 합쳐져 정상적으로 애니메이션된 하나의 오브젝트가 완성되게 된다.
참고서적
DirectX 12를 이용한 3D 게임 프로그래밍 입문
'그래픽스 > DirectX12' 카테고리의 다른 글
[Directx12][22장]사원수 (0) | 2023.03.21 |
---|---|
[Directx12][21장]주변광 차폐 (0) | 2023.03.21 |
[Directx12][20장]그림자 매핑 (0) | 2023.03.21 |
[Directx12][19장]법선 매핑 (0) | 2023.03.21 |
[Directx12][18장]입방체 매핑 (0) | 2023.03.21 |