정점 셰이더 단계(Vertex Shader)

WanJu Kim·2022년 12월 28일

Direct3D

목록 보기
11/29

입력 조립기 단계에서 기본도형들을 조립했으면, 정점들이 정점 셰이더 단계로 입력된다. 정점 셰이더는 마치 정점 하나를 받아서 정점 하나를 출력하는 함수로 간주해도 된다. 모든 정점들이 정점 셰이더 단계를 거쳐 간다. 그 함수의 구체적인 내용은 프로그래머가 구현해서 GPU에 제출한다. GPU와 CPU는 계산 방식이 다르다. 예를 들어 1000개의 원소를 가진 배열을 계산한다고 했을 때, CPU는 1부터 1000까지 차례대로 계산한다. 하지만 GPU는 1000개를 한번에 한다. 그래서 정점들을 다루는 것도 GPU가 유리하다. 정점 셰이더에서는 변환, 조명, 변위 매핑 등 수많은 특수 효과를 다룰 수 있지만, 처음이니까 변환만 다뤄본다.

국소 공간과 세계 공간

게임의 어떤 맵에 사과를 배치하고 싶다고 해보자. 그 맵의 특정 장소에는 특정 좌표(x,y,z)가 있을 것이다. 이번에는 사과의 크기를 늘리고 싶다고 해보자. 사과의 크기는, 사과의 정 중앙(씨 있는 부분)을 기준으로 늘어나게 하는 것이 편할 것이다. 이 기준은 방금 말한 맵의 기준과는 다르다. 맵의 기준으로 사과를 늘린다면, 프로그래머가 원하는 모양이 안 나올 것이다. 이처럼 물체 자신이 기준이 되는 좌표계를 '국소 공간(local space) 혹은 지역 공간), 그리고 맵을 기준으로 하는 좌표계를 세계 공간(world space)라 부른다. 국소 공간에서 3차원 모형의 정점들을 모두 정의했다면, 그것들을 세계 공간에 적절한 위치와 방향으로 배치해야 한다. 이를 위해서는 국소 공간과 세계 공간의 관계를 정의할 수 있어야 한다. 더 구체적으로는, 국소 공간에서 세계 공간으로 좌표 변경 변환을 할 수 있어야 한다. 이 과정을 '세계 변환(world transform)'이라 부르고, 이것을 가능케하는 변환 행렬을 '세계 행렬(world matrix)'이라 부른다. 세계 행렬을 구하기 위해서는, 세계 공간을 기준으로 한 국소 공간 원점 및 축들의 좌표를 알아야 한다. 하지만 이것은 쉽지 않다. 더 쉬운 방법으로는, 세계 행렬 W를 각각의 행렬의 곱 SRT로 정의하는 것이다. 각각 S는 크기비례행렬, R는 회전행렬, T는 이동행렬이다. 이를 행렬로 각각 표현하는 것은 대수학 행렬의 특성을 이용하면 된다.

시야 공간

3D 공간에 이것저것 물체들을 배치했다고 하자. 이제 그 3D 중 일부를 2D로 보여줘야 한다. 어느 부분을 어떤 각도로 보여줄 것인가? 이 문제를 해결하기 위해 보여주고 싶은 공간을 마치 카메라가 촬영한듯한 방법을 활용할 수 있다. 그리고 카메라에 새로운 좌표계를 부여한다. 이 좌표계를 바로 시야 공간(view space), 시점 공간(eye space), 카메라 공간(camera space)라고 한다. 그리고 세계 공간에서 시야 공간으로의 좌표 변경 변환을 시야 변환(view transform)이라 부르고, 해당 변환 행렬을 시야 행렬(view matrix)라 부른다.

시야 행렬은 어떻게 구하는가? 다음은 XNA Math라이브러리가 제공하는 시야 행렬을 계산하는 함수다.

XMMATRIX XMMatrixLookAtLH(	// 계산된 시야 행렬 반환.
	FXMVECTOR EyePosition,	// 카메라의 위치.
    FXMVECTOR FocusPosition,// 카메라의 시선.
    FXMVECTOR UpDirection);	// 카메라의 머리 방향.

이 세 가지 변수만 정하면, 한 공간을 바라보는 카메라가 정의된다. 다음은 시야 행렬을 구하는 예시다.

XMVECTOR pos = XMVectorSet(5, 3, -10, 1.0f);
XMVECTOR target = XMVectorZero();
XMVECTOR up = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

XMMATRIX V = XMMatrixLookAtLH(pos, target, up);

투영과 동차 절단 공간

카메라를 서술하는 요소는 사실 하나 더 있다. 바로 카메라가 바라보는 공간 영역이다. 이 영역은 마치 하나의 절두체처럼 정의된다.

가까운 평면과 먼 평면의 사이에 있는 물체들을, 가까운 평면에, 그 비례에 맞게 투영시켜야 한다. 이걸 '원근투영법(perspective projection)'이라 한다.

저 가까운 평면(투영 창)의 가로 세로를, 후면 버퍼의 가로, 세로의 비율과 일치시키는 게 좋을 것이다. 그렇지 않으면 이미지가 왜곡돼 보일테니 말이다. 그 작업을 편하게 해주는 것이 투영 창을 정규화시키는 것이다. 정규화 시키고 난 후의 x, y 좌표를 정규화된 장치 좌표(normalized device coordinates, NDC)라고 부른다. 정규화시키면, 투영된 점의 x좌표 성분이 [-r,r]에서 [-1,1]으로 된다. 깊게 들어가면 수식이 많이 나오니 여기까지만 하겠다.

XNA Math 라이브러리는 원근투영 행렬을 구축해주는 함수를 제공한다. 다음은 그 코드와 예이다.

XMMATRIX XMMatrixPerspectiveFovLH {	// 투영 행렬을 돌려준다.
	FLOAT FovAngleY,	// 수직 시야각(라디안 단위).
    FLOAT AspectRatio,	// 종횡비 = 너비 / 높이.
    FLOAT NearZ,		// 가까운 평면까지의 거리.
    FLOAT FarZ);		// 먼 평면까지의 거리.
    
XMMATRIX P = XMMatrixPerspectiveFovLH(0.25f*MathX::Pi,
	static_cast<float>(mClientWIdth) / mClientHeight, 1.0f, 1000.0f);
profile
Question, Think, Select

0개의 댓글