기록공간

[DirectX 12] 렌더링 파이프라인 - 1 본문

DirectX/기초

[DirectX 12] 렌더링 파이프라인 - 1

입코딩 2020. 4. 7. 11:55
반응형

우리가 사용하는 컴퓨터의 모니터는 평면이다. 홀로그램이 아닌 이상은 직접 3차원의 세계를 모니터에 그대로 구현할수 있는 방법은 없다. 카메라에 비친 3차원 장면을 2차원 이미지로 생성하는 데 필요한 일련의 단계들을 렌더링 파이프라인(rendering-pipeline)이라고 부른다.

 

위 그림에서 왼쪽 이미지는 3차원 세계에 배치된 물체들과 그것을 바라보는 카메라를 옆에서 본 모습이고, 중간 이미지는 위쪽에서 바라본 모습이다. 그리고 오른쪽 이미지는 카메라 시점에서 바라본 3차원 세계를 2차원으로 만들어낸 모습이다.

 

3차원의 환상

3차원 그래픽을 살펴보기 전에 간단하지만 중요한 질문 하나에 답할 필요가 있다. 바로, "3차원 세계의 깊이와 부피를 어떻게 평평한 2차원 모니터 화면에 나타낼 것인가?"라는 질문이다. 이 문제는 오래전부터 연구되어왔던 주제이다. 이미 수세기 동안 화가들은 3차원의 장면을 2차원 화폭에 담아왔다.

 

직선으로 멀리 뻗어 있는 철로를 생각해 보다. 철로의 두 궤도는 시작부터 끝까지 평행하지만, 철도 방향을 바라보면 두 궤도가 점점 가까워지다 결국 먼 지점에서 하나로 합쳐지는 모습을 볼 수 있다.

 

사람이 깊이감을 느끼는 또 다른 요인은 물체의 크기가 깊이에 따라 감소하는 현상, 즉, 가까운 물체가 멀리 있는 것보다 더 크게 보이는 현상이다. 왼쪽 이미지는 기둥들이 평행하게 줄지어 있는 장면이다. 이 기둥들은 모두 같은 크기지만, 깊이가 증가함에 따라 점점 작게 나타난다. 그리고 오른쪽 이미지 처럼 물체가 그 뒤에 있는 물체의 일부 또는 전체를 가리는 물체 겹침(object overlap)도 3차원을 체험할 수 있는 현상이다. 

 

다음은, 조명이라는 단서를 살펴보자. 왼쪽 이미지에서 왼쪽 모형은 조명을 가하지 않은 구이고 오른쪽은 조명을 가한 것이다. 왼쪽 모형은 상당히 평평해보인다. 그냥 2차원 원처럼 보인다. 이처럼 조명과 음영은 3차원 물체의 입체적 형태와 부피를 묘사하는데 아주 중요한 역할을 한다.

 

마지막은 그림자이다. 오른쪽 이미지에 그 예가 나와있다. 그림자는 3차원 환상에 대한 핵심 단서 두 가지를 제공한다. 첫째로, 그림자는 장면에서 광원이 있는 위치를 말해준다. 둘째로, 그림자는 물체가 얼마나 떠 있는지를 대략 제시해준다.

 

우리는 현실에서 이미 체험중이므로 당연하게 느껴질 것이다. 이것들을 머릿속에 담아 두면 3차원 그래픽을 학습하고 실천하는데 도움이 된다.

 

모형의 표현

일반적으로 Direct3D 응용 프로그램에서는 고형(solid)의 3차원 물체삼각형 메시(mesh)로 근사(=거의 같게)해서 표현한다. 우리가 모형화하는 물체의 기본적인 구축 요소는 삼각형이다.

 

위 그림처럼, 실제 세상의 어떤 3차원 물체도 감각형 메시로 근사할 수 있다. 일반적으로 삼각형을 더 많이 사용할수록 세부 사항을 좀 더 잘 본뜰 수 있기 때문에 물체를 더 잘 근사할 수 있다. 하지만 삼각형이 많을수록 메시에 대한 처리량도 늘어난다. 따라서 프로그래머는 하드웨어 능력에 근거해서 적절한 균형점을 찾을 필요가 있다. (삼각형 외에 선과 점도 물체를 근사하는 데 유용할 때가 있다. 곡선은 한 픽셀 굵기의 일련의 짧은 선분들로 근사할 수 있다)

 

3차원 모형의 삼각형들을 일일이 나열한다는 것은 대단히 힘들고 성가신 일이다. 기본적인 도형을 표현하는게 아닌 이상, 3차원 물체의 모형을 생성하고 조작할 때는 3D 모델러(modeler)라고 하는 3차원 모형 제작 응용 프로그램을 사용한다. 모델러는 복잡하고 사실적인 메시를 만드는데 필요한 다양한 도구들이 갖추어져있기 때문에, 훨씬 수월하게 모형을 제작할 수 있게 해준다. 흔히 사용되는 유명한 모델러는 3D Max, Maya, Blender 등이 있다.

 

컴퓨터 색상의 기본 개념

모니터는 픽셀마다. 빨간색, 녹색, 파란색 빛을 섞어서 방출한다. 세 가지 빛의 혼합 비율이 다르면 우리는 다른 색을 인식하게 된다.

 

위 그림에서 적, 녹, 청색광을 섞었을 때 나오는 여러 가지 색상의 예가 나와 있다. 또한 빛의 세기(intensity)에 따라서 더 다양한 색을 표현할 수도 있다. 이렇게하여 사실적인 이미지를 표시하는 데 필요한 모든 색상을 묘사할 수 있다. 적(red), 녹(green), 청(blue) 세 가지 색상 성분을 통칭해서 RGB라고 부른다. 

 

모니터가 방출하는 적, 녹, 청색광의 세기는 상한선이 존재한다. 빛의 세기를 나타낼 때에는 0에서 1까지로 정규화된 값(clamping)을 사용하는 것이 편리하다. 0은 빛이 전혀 없는 것이고 1은 빛의 세기가 최대인 것이다. 그 중간의 값은 중간 세기를 뜻한다. 예를 들어 (0.25, 0.67, 1.0)은 25% 세기의 적색광과 67% 세기의 녹생광, 100% 세기의 청색광의 혼합을 뜻한다. 

(0 <= r, g, b <= 1)

 

색상 연산

벡터 연산은 색상 벡터에도 적용된다. 두 색상 벡터를 더해서 새로운 색상을 얻을 수 있다. 

(0.0, 0.5, 0) + (0, 0.0, 0.25) = (0.0, 0.5, 0.25)

중간 세기 족색에 어두운 청색을 더했더니 좀 더 탁한 녹색이 되었다.

한 색상에서 다른 색상을 빼서 새 색상을 얻는 것도 가능하다.

(1, 1, 1) - (1, 1, 0) = (0, 0, 1)

이 예에서는 흰색에서 적색 성분과 녹색 성분을 빼서 청색을 얻었다.

스칼라 곱셈도 의미 있는 결과를 낸다.

0.5(1, 1, 1) = (0.5, 0.5, 0.5)

흰색에 0.5를 곱했더니 중간 수준의 회색이 되었다. 2를 스칼라 곱하여 세기를 두 배로 만들 수도 있다. 

내적이나 외적 같은 연산은 색상 연산에 별로 의미가 없다. 그러나 색상 벡터만의 연산도 있는데, 변조(modulation)라고도 하는 성분별 곱셈이 바로 그것이다.

(r1, g1, b1) X (r2, g2, b2) = (r1r2, g1g2, b1b2)

이 연산은 조명 공식에 주로 쓰인다. 예를 들어 입사광선의 색상이 (r, g, b)라고 하자. 그리고 표면이 입사광선의 적색광을 50%, 75%, 25% 반사하고 나머지는 흡수한다고 하자. 그러면 표면에 반사된 광선의 색은 다음과 같이 주어진다.

(r, g, b) X (0.5, 0.75, 0.25) = (0.5r, 0.75g, 0.25b)

이는 표면을 때린 광선의 세기가 다소 약해짐을 보여준다. 표면이 빛의 일부를 흡수했기 때문이다.

 

128비트 색상

그래픽에서 색을 다룰 때에는 흔히 적, 녹, 청, 외에 알파라고 부르는 색상 성분 하나를 사용한다. 알파 성분은 주로 색상의 불투명도를 나타내는 데에 쓰인다. 불투명도는 색상 혼합에 유용하다. 알파 성분의 도입은 하나의 색상을 4차원의 색상 벡터(r, g, b, a)로 표현한다는 뜻이다. (여기서  0 <= r, g, b, a <= 1 이다) 각 성분을 32비트 부동소수점값 하나로 표현한다면, 하나의 색상을 표현하는데 128비트가 필요하다. XMVECTOR 형식을 이용해서 색상을 표현할 수 있다. 그러면 DirectXMath 라이브러리의 벡터 함수들로 색상 연산을 수행할 때 SIMD(병렬로 동시에 계산)의 혜택을 받게 된다. 

XMVECTOR XM_CALLCONV XMColorModulate( // C1 X C2
FXMVECTOR C1,
FXMVECTOR C2
)

 

32비트 색상

각 성분당 1바이트를 달당해서 하나의 색상을 32비트로 표현할 수도 있다. 성분당 8비트이므로 한 성분에 대해 총 256가지의 세기를 표현할 수 있다. 0은 해당 성분이 전혀 없는 것이고 255는 최대, 그리고 그 중간은 중간 세기이다. 성분당 256가지라 적어 보이겠지만, 세 성분의 조합은 256 * 256 * 256 = 16,777,216으로 32비트 색상으로 천만 가지 이상의 색을 표현할 수 있다. DirectXMath 라이브러리는 32비트 색상의 저장을 위해 XMCOLOR라는 구조체를 제공한다.

 

정수 구간 0 ~ 255를 0 ~ 1로 사상함으로써 32비트 색상을 128비트로 변환하는 것이 가능하다. 0 <= n / 255 <= 1 이런 식으로 말이다. 반대로, 128비트 색상을 32비트 색상으로 변환할 때에는 각 성분에 255를 곱하고 그 결과를 가장 가까운 정수로 올리면 된다.

 

DirectXMath 라이브러리는 XMCOLOR 하나를 받아서 XMVECTOR를 돌려주는 메서드를 제공한다. 

XMVECTOR XM_CALLCONV PackedVector::XMLoadColor(
const XMCOLOR* pSource);

XMCOLOR는 ARGB배치를 사용한다. XMVECTOR 색상을 XMCOLOR로 변환하는 메서드도 제공한다.

void XM_CALLCONV PackedVector::XMStoreColor(
XMCOLOR* pDestination,
FXMVECTOR V);

대체로 128비트 색상은 정밀도가 높은 색상 연산이 필요한 곳(픽셀 셰이더)에 쓰인다. 128비트 색상은 유효 자릿수가 많아서 산술 정밀도가 높다. 이 덕분에 산술 오차가 과도하게 누적되는 일이 없다. 하지만 최종적인 픽셀 색상은 백버퍼에 32비트 색상으로 저장된다. 모니터와 같은 디스플레이 장치들은 고해상도 색상의 장점을 취하지 못하기 때문이다.

 

렌더링 파이프라인의 개요

 

그림은 렌더링 파이프라인을 구성하는 단계(stage)들(왼쪽)과 관련 GPU 메모리 자원들(오른쪽)을 도식화한 것이다. 자원에서 파이프라인 단계로 가는 화살표는 그 단계가 자원을 입력으로 사용할 수 있음을 뜻한다. 단계에서 자원으로 가는 화살표는 그 단계가 GPU 자원에 자료를 기록할 수 있음을 뜻한다.

 

출력 병합기 단계는 후면 버퍼(렌더 타겟)나 깊이 스텐실 버퍼 같은 텍스처에 자료를 기록한다. 출력 병합기 단계의 화살표는 양방향인데, 이는 이 단계가 GPU 자원을 읽기도 하고 쓰기도 함을 뜻한다.

 

대부분의 단계는 GPU 자원에 자료를 기록하지 않는다. 대신 단계의 출력은 그냥 파이프라인의 다음 단계의 입력으로 들어간다. 예를 들어 정점 셰이더 단계는 입력 조립기 단계로부터 자료를 받아서 작업을 수행하고 그 결과를 기하 셰이더 단계로 출력한다. 다음 장 부터 각 단계별로 자세하게 살펴보겠다.

반응형
Comments