[고도엔진 내부 쉐이더 공부] canvas_item 쉐이더 행렬 구조 및 VERTEX 전달 2 - Whitmem
[고도엔진 내부 쉐이더 공부] canvas_item 쉐이더 행렬 구조 및 VERTEX 전달 2
Game Development
2025-01-20 00:30 게시 653156af5bebf1bf3566

0
0
76
이 페이지는 외부 공간에 무단 복제할 수 없으며 오직 있는 그대로 게시되며 부정확한 내용을 포함할 수 있습니다. 법률이 허용하는 한 가이드 라인에 맞춰 게시 내용을 인용하거나 출처로 표기할 수 있습니다.
This page is not to be distributed to external services; it is provided as is and may contain inaccuracies.
canvas_item 쉐이더
고도 엔진의 2D에서 기본적인 쉐이더를 생성할 때 canvas_item 이라는 옵션으로 쉐이더를 생성할 수 있다.
쉐이더 내에는 기본적인 함수 vertex(), 와 fragment()가 존재하는 것을 확인할 수 있다.
vertex()는 정점의 위치를 지정하는 쉐이더 함수이고, fragment()는 색상을 지정하는 쉐이더 함수라고 보면 된다. 이 두 함수는 콜백 함수라고 보면 편하다. 아무것도 정의된 것이 없으면 고도 엔진 자체에서 정의된 쉐이더로 바로 프리 패스된다고 보면 된다.
vertex() 쉐이더 공간에 다음과 같은 코드를 넣으면 객체가 존재하는 지점의 우측 (20, 10) 으로 추가 이동되는 것을 확인할 수 있다.
VERTEX = VERTEX + vec2(20.0,10.0);
VERTEX 는 정점을 지정하거나 가져오는 예약 변수라고 보면된다.
쉐이더를 처음 입문하면 제일 헷갈리는 것이 4개의 버텍스에 대해 각각 코드를 작성하는 것이 아니라, 하나의 쉐이더 코드를 작성하면 4개의 버텍스에 동시에 적용되는 것이다. 이는 GPU 코드이기 때문이다. 각각의 정점에 대해 병렬적으로 처리된다고 보면 된다.
따라서 위 예시에서 정점은 4개가 존재하지만 VERTEX를 (0, 0) 으로 지정하면 아예 사라져버린다. 그 이유는 각 4개의 점이 같은 코드로 병렬 실행되면서 모두 (0, 0) 으로 지정되어버리기 때문이다. 따라서 이 점에 유의하고 쉐이더 코드를 작성해야 앞으로 작성하게 되는 코드들을 덜 헷갈리고 쉽게 이해할 수 있다.
한편, 처음 섹션에서 기본적으로 쉐이더 프로그래밍은 변환 행렬 처리를 한 결과를 내뱉어 줘야 화면 상에 표시된다고 하였다. 위 사진에서 주황색 객체 테두리가 존재하는 좌표는 사실상 월드 좌표에 존재하는 것이며, 이를 카메라 좌표로 변환하고 화면 공간에 투영하기 위해서는 변환 행렬을 거쳐야 한다고 했다. 이러한 변환 작업을 하는 코드들을 쉐이더에 작성해 줘야 하지만, 고도 엔진canvas_item 타입은 이를 자동으로 처리해준다고 한다.
따라서 VERTEX 에 기입하는 좌표 정점은 월드 공간이 아니라, 객체의 로컬 공간을 의미하며 객체가 존재하는 지점의 좌측 위인 빨간 점이 (0,0)가 되고, 객체의 우측 끝이 최대 점이 된다. 이를 VERTEX에 지정하면, 로컬 공간에 존재하는 좌표를 월드 공간으로 이동하고 월드 공간에 존재하는 좌표를 카메라 공간으로 이동하고 카메라 공간에 존재하는 좌표를 비로소 화면 좌표에 투영까지 하는 행렬이 내장되어 있다. 즉 우리는 VERTEX 라는 공간에 객체의 시작 지점을 0,0으로 하는 상대 좌표만 연산 해 주면 된다.
예를 들어 쉐이더 안에는 내장 함수가 존재하는데 나누고 난 나머지를 의미하는 mod 함수가 있다. 그리고 내장 변수인 TIME이 존재하는데, TIME에는 게임이 실행되고 나서의 연속적인 시간인 초 단위의 시간 정보가 계속 들어온다. 즉 초를 1로 나누고 난 나머지인 소수 (0~1)까지의 범위에 10을 곱하면, 1초라는 시간 동안 0~10까지의 범위가 계속 반복될 것이고, 이 크기를 VERTEX의 x, y 좌표에 계속 더하여 표시할 것이다. 즉 1초동안 x,y 좌표로 +10 반복하다가 다시 제 자리로 돌아오는 코드를 완성할 수 있다.
위 코드에서 두 코드는 사실상 동일한 것이다. VERTEX는 x,y 의 두 개 데이터를 담고 있는 구조체라고 볼 수 있다. (실제 구조체는 아니다.) 이 안에서 x 값에 원하는 값을 더하거나 y 값에 원하는 값을 더하면 된다. 하지만 VERTEX 자체에 어떤 실수를 더하는 경우 x, y 두 개 모두 해당 연산이 수행된다. 즉 VERTEX에 5를 더하는 경우 x, y에 자동으로 +5 된다.
행렬 이해하기
여기서는 기본적으로 로컬 공간의 좌표만 신경을 쓰면 된다고 했는데, 그래도 제대로 된 이해를 하기 위해서는 행렬을 직접 곱해보는 것이 좋다고 생각했다.
고도 엔진의 2D 공간에는 총 3개의 행렬을 우리가 건들 수 있는 것으로 보인다. in mat4 MODEL_MATRIX in mat4 CANVAS_MATRIX in mat4 SCREEN_MATRIX
MODEL_MATRIX
이 행렬은 로컬 공간에서 월드 공간으로 옮기는 행렬이다. 객체 자체는 로컬 좌표계에 정의된 것이고, 우리는 아까 이 공간에서 덧셈 연산을 해서 우측 아래로 움직여보았다. 이 좌표는 객체의 로컬 좌표인데, 이 객체가 아무리 월드 공간의 어딘가에 있더라도 이 행렬을 곱해주지 않으면 그 공간에 존재하는 것이 아니다. 단순히 이 객체의 형상 정보에 필요한 필수적인 좌표 정보인 로컬 공간을 객체의 월드 좌표에 이동하기 위한 행렬이다.
* 이전 게시글을 보고 왔다면, 로컬 좌표계가 약간 헷갈릴 수 있는데, 이전 게시글에서 각 4개의 정점을 월드 좌표계 기준으로 (100,100) 이런식으로 작성하였다. 하지만 각 객체는 상대적인 로컬 좌표가 따로 존재한다. 사실 객체의 정 사각형은 (-1,-1) (1,-1) (1,1) (-1,1)에 존재하는 것이고 이를 약 (200,200) 중앙에 존재한다는 월드 좌표, 회전 정보인 월드 행렬을 곱해서 (100,100)에 있다고 볼 수 있는 것이다.
CANVAS_MATRIX
이것이 월드 좌표에 존재하는 객체를 실제 카메라 영역으로 이동하는 행렬이라고 보면된다. 이전 게시글에서 월드 공간에 있는 어떤 좌표를 카메라 공간으로 이동하기 위해 카메라의 좌표를 빼야 한다는 얘기를 했었다. 이외 카메라가 회전되어 있거나 줌 아웃이 되어있더라도 좌표 정점을 제대로 맞춰주는 연산 처리를 한번에 해주는 행렬이라고 보면 된다.
SCREEN_MATRIX
마지막으로 캔버스 영역에 존재하는 모든 정점들을 정규화된 -1 ~ 1 사이의 공간에 배치하는 행렬이다. 비로소 -1 ~ 1 공간에 있는 정점들이 실제 화면에 스케일링 돼서 출력된다고 보면된다.
각 행렬은 교환 법칙이 성립하지 않는다. 마음대로 교환하면 순서가 변하기 때문에 순서가 제일 중요하다. 1) 먼저 로컬 좌표계인 오브젝트에 대해서 MODEL_MATRIX 를 먼저 곱하여 월드 공간으로 이동하고, 2) 월드 공간으로 이동된 좌표를 다시 CANVAS_MATRIX에 곱하여 카메라 공간으로 이동하고, 3) 카메라 공간에 있는 좌표를 최종적으로 표준화된 공간으로 이동하기 위해 SCREEN_MATRIX를 곱한다. 그러면 나머지는 알아서 고도 엔진이 게임창에 표시를 해 준다.
즉 최종적으로 쉐이더에서 변환 행렬이 관여하는 부분은 아래 이미지의 분홍 영역이다.
우리는 VERTEX에 로컬 좌표만 넣으면 고도 엔진이 안에서 3개의 변환 행렬을 곱해서 최종 위치를 연산한다.
역행렬 이해하기
이전 섹션에서는 전반적으로 고도 엔진 내부에서 어떻게 행렬이 처리되는지 알아보았다.
즉 우리는 VERTEX라는 공간에 로컬 좌표만 넣어주면, 이 다음 영역에서 알아서 행렬 3가지를 연산하고 최종적으로 게임창에 출력을 해준다고 하였다.
반대로 말하면 이 행렬을 처리하지 않는 경우, VERTEX에 입력하는 정점이 바로 표준 화면 스크린에 표시될 것이다.
행렬 처리를 안할 방법은 없을까? 행렬은 수학적인 영역이기 때문에 행렬이 처리된 후 다시 역행렬을 곱해주면 원본으로 돌아온다. 즉 VERTEX의 로컬 공간에 애초에 역행렬들을 곱해서 보내주면 정상적으로 행렬을 곱하는 과정에서 다시 로컬 공간으로 돌아올 것이다.
즉, VERTEX에 로컬 좌표에 SCREEN_MATRIX의 역행렬을 곱하고, 다음에 CANVAS_MATRIX의 역행렬을 곱하고, 다음에 MODEL_MATRIX의 역행렬을 곱해주면 결과적으로 엔진 내부에서는 순차적으로 MODEL_MATRIX를 곱하고, 다음 CANVAS_MATRIX를 곱하고, 다음으로 SCREEN_MATRIX를 곱하면서 소거되어 최종적으로 로컬 좌표가 표준화된 스크린 공간에 바로 출력될 것이다.
즉, REVERSE_MODEL_MATRIX( REVERSE_CANVAS_MATRIX( REVERSE_SCREEN_MATRIX( 로컬좌표 ) ) ) 을 넘겨주면, 엔진 내부에서는 SCREENMATRIX( CANVAS_MATRIX( MODEL_MATRIX( REVERSE_MODEL_MATRIX( REVERSE_CANVAS_MATRIX( REVERSE_SCREEN_MATRIX( 로컬좌표 ) ) ) ) ) 는 최종적으로 다시 소거되어 로컬 좌표만 남을 것이다.
객체의 월드 좌표에 관련 없이 무조건 뷰포트 중앙에 고정되어 표시되는 것을 확인할 수 있다.
댓글 0개
댓글은 일회용 패스워드가 발급되며 사이트 이용 약관에 동의로 간주됩니다.
확인
Whitmemit 개인 일지 블로그는 개인이 운영하는 정보 공유 공간으로 사용자의 민감한 개인 정보를 직접 요구하거나 요청하지 않습니다. 기본적인 사이트 방문시 처리되는 처리 정보에 대해서는 '사이트 처리 방침'을 참고하십시오. 추가적인 기능의 제공을 위하여 쿠키 정보를 사용하고 있습니다. Whitmemit 에서 처리하는 정보는 식별 용도로 사용되며 기타 글꼴 및 폰트 라이브러리에서 쿠키 정보를 사용할 수 있습니다.
이 자료는 모두 필수 자료로 간주되며, 사이트 이용을 하거나, 탐색하는 경우 동의로 간주합니다.