ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [24] - Constant Buffer
    Graphics 2021. 7. 30. 22:03

    (24) C++ 3D DirectX Tutorial [Constant Buffers] - YouTube

    hw3d/hw3d at T17-End · planetchili/hw3d (github.com)

    이번에는 Vertex들의 Transformation을 실질적으로 구현해보도록 합니다. 이전에 귀엽고 엉성한 육각형을 만들었습니다. 그리고 이전에 이론으로 어떤 Mesh의 Rotation, Translation, Scailing을 배웠습니다. 여기에 추가로 배워야 할 것이 있는데 바로 Constant Buffer라는 것입니다. 이 과정은 GPU가 아닌 CPU쪽에 처리하게 됩니다.

    왜 CPU가 아닌 GPU 쪽에서 이 Transformation을 처리하게 되는 것일까요? 사실 CPU에서 모조리 처리를 할 수 있긴 합니다. 모든 Transformation을 CPU에서 수행하고 매 프레임 마다 GPU로 보내는 것이 가능은 합니다. 그러나 생각해보면 실제 게임에서는 여태 만든 정점 6개 짜리 보잘것없는 Mesh가 아닌 수 천 개에 이르는 정점을 가진 Mesh들이 등장합니다. 이 정점에 대해서 매 프레임마다 계산하여 GPU로 넘겨준다면 보통 일이 아니게 됩니다. 

    대신에 고정된 Mesh를 가지고 Scene에서는 변화하지 않도록 하고 만약 그 Mesh가 움직여야 할 필요가 있다면 Dynamic Const Matrix라는 것을 바꾸게 될 겁니다. 여기서 Const라는 것은 한 번의 Draw의 호출 동안 변하지 않는다는 말입니다. 당연히 프레임 마다 갱신이 될 것입니다. 당연히 수 천 개의 Vertices를 계산하고 넘겨주는 것보다는 이 Matrix만 바꾸는 편이 훨씬 편할 것입니다. 하지만 여태껏 작성한 코드는 매 프레임마다 버퍼를 다시 만드는 과정을 하는데 실제 엔진은 이런 식으로 돌아가지 않습니다.

    이 모든 과정을 GPU에서 하는 또 다른 이유는 단순히 GPU가 대단히 Parallel Processor이기 때문에 수 천 개의 정점을 Transforming한다면 CPU보다는 GPU가 더 빠르게 잘 할 것이기 때문입니다. 이제 등장하는 질문은 대체 어떻게 행렬[Matrix]을 GPU에 보내 줄 것인가 입니다. 

     GPU에 어떤 데이터를 보내는 과정이 한 번 있었습니다. 그 중에 가장 명시적으로 드러난 과정이 바로 Vertex Data를 Shader로 보내는 과정입니다. 그런데 생각해보면 이 방식으로 GPU에 매 프레임마다 변하는 행렬을 16개 곱해준다면 그냥 CPU에서 다 처리해서 보내는 것과 큰 차이가 없습니다. 그렇기 때문에 등장하게 된 것이 Shader Constant Buffer입니다. 이 버퍼가 Shader Stage로 어떤 Constant 값을 Bind할 수 있도록 해줍니다. 이렇게 Bind 된 버퍼는 매 Shader의 invocation마다 유효하게 작용합니다. 이제, 무엇을 먼저 해야 할지 명확해졌습니다. Constant Buffer부터 만들어 보기로 합니다. 

    ConstantBuffer의 구조 안에는 4x4의 배열을 행렬처럼 쓰기로 합니다. 그리고 이 구조의 이름은 Transformation이라고 하기로 합니다. 마침내 이론 시간에 배웠던 내용을 실제로 써먹습니다.

    Row Major Matrix입니다

    한 번 z축을 기준으로 회전하도록 만들어 봅시다. 이제 Resource를 만들 차례입니다. 뭘 만들고 자원을 할당하는 건 Device가 한다고 했습니다. 그러나 그 전에 Buffer에 대한 정보인 Description을 만들어야 합니다.

    약간 차이가 있습니다. 이제 이 버퍼는 당연히 bind constant buffer이고 usage가 dynamic입니다. 매 프레임 마다 이 값은 변화할 것이기 때문입니다. 또 Lock Function을 사용할 수 있게 되는데 나중에 볼 것 같습니다. Defualt를 주고 변화를 주는 것도 가능은 합니다. 사실 이 DrawTriangle이라는 함수에서 매 프레임마다 버퍼를 새로 만들기 때문에 Dynamic을 줄 필요가 없긴 합니다. 그리고 이 함수에 float _angle이라는 Parameter를 추가해야 위의 변환행렬의 삼각함수 안의 값을 채울 수 있습니다.

    다른 Buffer들과는 다르게 Constant Buffer는 Vertex Shader에 직접 Bind합니다. 그리고 이것을 디버그 해보면 다음과 같은 에러 메시지를 받습니다.

    CPUAccessFlags가 잘못되었습니다. Usage가 Dynamic이기 때문에 CPU측에서 해당 리소스가 프레임마다 업데이트 될 것이기 때문입니다. 그래서 쓸 수 있는 (Write)권한을 주도록 합니다.

    그리고 실행을 해보면 이전과 다를 바가 없는 가만히 있는 육각형을 만나게 됩니다. 당연한 결과입니다. 아직 Vertex Shader 측에서 아무런 행동을 취하지 않았습니다. Vertex Shader 해야 할 것은 별 것 없습니다. shader 안에 Constant Buffer를 만들 인스턴스를 만들고 위치 벡터에 행렬을 곱해주면 됩니다. 내장된 mul이라는 함수가 HLSL에 있습니다.

     

    일부러 다른 개념을 설명하기 위해서 위로 늘린 육각형을 만들었습니다. 생각해보면 만든 Window는 Width와 Height가 같지 않았습니다. 그럼에도 불구하고 회전하는 Mesh는 Top과 Left에 모두 닿는 모습을 보이고 있습니다. 조금 이상합니다. 어디에 문제가 있는가 하면 Normalised Devide Coordiates, NDC까지 거슬러 올라갑니다.

    Viewport를 공부했던 이론 장에서 NDC는 2x2x2의 정육면체 공간이었습니다. 그런 완벽한 공간이 우리의 찌그러진 화면에 어떠한 가공도 없이 맵핑이 된 것입니다. 현재 만들어 둔 Window의 Aspect Ratio는 4:3입니다. 그래서 이 비율을 맞추고 싶으면 x방향에 3/4을 곱해줍니다. 그런데 단순히 x좌표에 Scailing을 해서는 변화가 없을 겁니다. 왜냐하면 CPU는 행렬을 Row-Major로 저장하지만 GPU는 Column major로 저장합니다. 따라서 다음과 같은 Scaling은 GPU에서 우리가 원하는 대로 해석이 되지 않았던 겁니다. 해결하는 방안은 여러 개 있습니다. 그 중 가장 편리한 방법은 Shader쪽에서 Row Major임을 명시하도록 하는 방법입니다. 

    Row Major 표현입니다
    row_major임을 알립니다

    다시 실행하면 원형을 유지하며 회전하는 모습을 확인할 수 있습니다. 사실 이 방법은 Column major로 계산하는 것보다는 조금 느릴 수 있다고 합니다. 가능하면 Column major를 쓰는 게 나을 것 같습니다. 마침 몸에 익은 것도 Column major 입니다.

     

     

    'Graphics' 카테고리의 다른 글

    [26] - Z-buffer  (0) 2021.07.31
    [25] - DirectXMath  (0) 2021.07.31
    [23] - Pipeline experiments  (0) 2021.07.30
    [22] - Viewport and clip space  (0) 2021.07.23
    [21] - My First Triangle - 2  (0) 2021.07.22
Designed by Tistory.