GPU 처러 과정에는 특별한 파이프라인 구조가 존재함. 한 스테이지의 output이 다음 스테이지의 input이 됨~
앞으로 이 그래픽스 파이프라인을 하나하나 살펴볼 것임
< 의미 >
~ Shader : 프로그래밍 가능함 나머지는 하드웨어적으로 고정된 함수 사용 진한 초록색 : 무조건 있어야 함 연한 초록색 : 선택적으로 빼도 됨
◆ Input Assembler (IA)
GPU가 CPU로부터 Vertex 정보(VBV, IBV)를 받아서 읽고, 규칙에 따라 처리해서 primitive로 만들어줌.
이 부분은 우리가 코딩하는 부분이 아니고 하드웨어적 요소로 이미 만들어져 있는 부분임
< 용어 >
VBV(Vertex Buffer View) : CPU에서 만들어진 vertex 정보가 쭉 들어가있음에 대한 핸들러(포인터)
IBV(Index Buffer View) : CPU에서 만들어진 Index 정보가 쭉 들어가있음에 대한 핸들러(포인터)
primitive: 기본이 되는 모양 단위. 삼각형, 사각형, 포인트 등
//ID3D11DeviceContext.IASetPrimitiveTopology() 를 사용해 사용자가 primitive type을 지정할 수 있음
void IASetPrimitiveTopology(
[in] D3D11_PRIMITIVE_TOPOLOGY Topology
);
// vertex 데이터 지정 (VBV)
struct CUSTOMVERTEX {
FLOAT x, y, z; // The position
FLOAT nx, ny, nz; // The normal
DWORD color; // RGBA color
FLOAT tu, tv; // The texture coordinates.
};
◆ Vertex Shader
Vertex Shader은 IA로 부터 받은 Vertex 정보들을 기반으로, 각각의 Vertex별로 연산을 처리해서, Vertex들의final position(최종 포지션)을 결정하는 데 사용함.
이때, “내 옆에있는 녀석의 정보는 참조할 수 없다“ 는 점 기억.
왜냐면 한번의 clock cycle 내에서 병렬처리 되기 때문에,
오로지 자기 자신의 정보만을 갖고 처리해서 다음 스테이지로 넘어가야 함.
◇ Vertex Shader 특징
Shader라는 이름이 붙은 순간, Programmable한 스테이지이다. 우리가 직접 코딩해서 어떻게 동작할지 결정할 수 있다는 뜻.
DirectX를 사용하는 Shader를 HLSL이라 하고, OpenGL을 사용하는 Shader를 GLSL이라고 함.
GPU는 코어가 많아서, 병렬 처리를 통해 엄청나게 빠르게 floating point 처리가 가능함. (CPU는 int 연산이 많은 데 반해, GPU는 벡터 연산이 많으므로 부동 소수점 기반의 float 처리에 특화)
내부에서 어떤 처리를 하든 자유롭지만, Input 및 Output에 대한 규약은 엄격하게 지켜줘야 함
VS는 필수적인 Shader임!! 뺄 수 없음.
◆ Vertex Shader 처리 과정
그럼 Vertex Shader가 결정한다는 final position이 뭐냐?
final position까지의 변환 과정을 살펴보자.
Object space → World space →Camera space → Clip space
■ Object space
object의 좌표계.
object의 회전시 축이 함께 회전함
■ World space
3D World의 좌표계.
3D World는 다양한 object들, 캐릭터들, 카메라들을 포함함
■ Camera space
카메라 중심에서의 좌표계.
결국 화면에 보이는 건 카메라 위치로부터 보는 것이기 때문!
3D World 좌표를 카메라 위치와 관련해서 재계산함.
■ Clip space
3D 화면을 2D 화면에 맞게 투영한 좌표계. (실제 클리핑을 수행하는 것은 아님)
시야 범위 내에 들어오는 좌표를 -1에서 1 사이로 표준화 함.
※ Space 사이의 변환 과정(world/view/projection transform)은 모두matrix 형태로 표현 가능하다. 따라서 이 셋을 합쳐서 M_wvp로 만들 수 있다. (Matrix - world view projection)
◆ Vertex Shader 처리 과정 예시
다음 과정을 per-vertex로 처리한다.
VS_OUTPUT main (VS_INPUT input)
{
VS_OUTPUT Output;
float4 pos = float4(input.vPos, 1.0f); //input.vPos를 사용해 float4형식의 pos 정의
pos = mul(pos, mWorld); // world transform (object -> world)
pos = mul(pos, mView); // view tranform (world -> camera)
pos = mul(pos, mProjection); // projection transform (camera -> clip)
Output.Position = pos; // 처리를 마친 최종 vertex들의 position
Output.Color = float4(input.vColor, 1.00f); // 최종 칼라 넣어줌
return Output;
}
float4 형식.. homogeneous coordinate
◆ Vertex Shader - World Transform
각자의 object space에서 정의된 object들을 동일한 World space로 가져옴.
처음에 object를 생성할 때 사용된 object space들은 다른 object space와 관련이 없음.
이들을 한 공간에 모아주는 게 world transform이다!
sclaing, rotation, translation 변환을 통해서 object들을 world space로 가지고 옴.
아핀 변환으로 구성된 World Matrix는 [L|t]로 표현 가능~
◇ Vertex Shader - World Transform - normal
triangle이 [L|t]에 의해 변환되었다면, triangle normal은 L-T로 변환하면 됨!
CPU가 GPU로 vertex 정보를 넘길 때 vertex들의 x,y,z 포지션과 함께 vertex normal xn, yn, zn도 같이 넘김.
(GPU는 병렬 처리를 해서 옆 vertex들을 참조할 수 없기 때문에, CPU에서 미리 vertex normal까지 계산해서 넘겨줌)
triangle에 대한 변환을 [L|t] 라고 하자. 이때 triangle normal의 변환은 어떻게 처리되어야 하는지 알아보자.
normal은 [L|t] 에서 L에만 영향을 받음.
그런데 만약 L이 non-uniform scaling(비율이 다른 scaling)을 포함한다면, 이것은 normal에는 적용될 수 없음. 아래 그림을 보자.
n 벡터에 L변환을 적용한 벡터를 Ln, 이를 정규화한 벡터를 \(\tilde{L}\)n라 하자.
L이 non-uniform scaling인 경우,L에 의해 scaling된 normal은, 더이상 (L에 의해 scaling된) triangle과 직교하지 않음.
=> 따라서 우리는 inverse transpose of L, 즉 L-T를 사용함.
L-T 와 L는 magnitude는 다를 수 있지만 direction은 같음.
따라서 L-T를 사용해 변환하면 L이 non-uniform scaling을 포함하고 있든 아니든 간에 동일하게 연산됨!
결과적으로도 변환된 normal은 변환된 triangle과 직교함.
그러고나서 최종적으로 다시 normalized 해주면 됨~
그러므로 우리는 앞으로 normal 계산시 L-T를 사용할 것이다~
[ 증명 ]
triangle의 각 vertex를 p,q,r이라 하고, triangle normal을 n이라 하자. p-q를 잇는 벡터와 n벡터는 직교한다. 따라서 둘의 dot product 결과는 0이므로, 다음 식을 만족함
(p, q, n이 row vector라 했을 때 dot product 를 위해 n을 전치한 것.)
이제 p, q에 L변환을 한 pL = p`, qL = q`이라고 하자. 양 변에 L-1을 곱하면, p = p`L-1, q = q`L-1이라 할 수 있다.
이를 위 식에 대입하면, 다음과 같은 식이 나온다.
이 식을 다시 Transpose 해주면 다음 식을 만족함. (참고: (AB)T = (BT)(AT))
즉, nL-T 벡터와 (q`-p`) 벡터가 직교한다는 것을 알 수 있음. 풀어서 쓰면, L 변환된 (q`-p`)벡터와 L-T변환된 n 벡터는 직교한다~ 라는 말!
◆ Vertex Shader- View Transform
World space → Camera space로의 변환.
world space에서의 모든 객체들이 camera space 측면에서 새롭게 정의될 수 있다면, 렌더링 알고리즘을 개발하는 것이 훨씬 쉬워질 것~ 이라는 아이디어.
카메라 또한 object이기 때문에, world space에서 정의되었을 것임.
u, v, n은 DirectX 기준. OpenGL은 n이 반대방향
world space에서 카메라 포즈는 다음 요소들에 의해 설명됨
EYE: 카메라 포지션
AT: 카메라가 바라보고 있는 점
UP: 카메라의 윗 부분을 알려주는 벡터(포지션과 바라보는 방향이 같아도, 화면을 돌릴 수도 있으니까. 보통 y축이 UP)
EYE, AT, UP 이 주어지면, u, v, n 벡터를 다음과 같이 계산할 수 있다. (셋다 단위벡터)
n은 EYE에서 AT으로의 방향을 가진 벡터
u는 n과 UP 벡터의 외적 벡터
v는 n과 u벡터의 외적 벡터 (n과 u가 이루는 평행사변형의 넓이는 1이므로, v도 단위벡터)
이 벡터들을 자세히 보자.
어떤 두 벡터를 외적한 벡터는, 그 두 벡터와 각각 수직이라는 점을 기억하자.
u는 UP과 n의 외적으로 정의되어 있음.
즉,u는 UP과도 수직이고 n과도 수직임.
같은 맥락에서,v는 n과도 수직이고 u와도 수직임
...어!? u, v, n은 다 단위벡터고, 다 서로 수직이네? =>u, v, n은 서로 orthonormal한 관계이다.
=> EYE를 원점으로 치면,원점 하나와 orthonormal한 벡터 3개.. 새로운 하나의좌표계를 정의할 수 있다!!
이를camera space라고 함.
EYE, AT, UP을 가지고 camera space라는 새로운 좌표계를 정의한 것이다.
한 point는 각각의 space에서 개별적인 좌표를 가짐.
그림에서는knight의world space 좌표는 (0,2,10)이고,camera space 좌표는 (0,0,10)인 것~
우리는 결국 카메라 기준으로 볼 때를 렌더링 할 것이기 때문에, object들을 카메라 기준의 좌표로 정의한다면 렌더링 알고리즘이 훨씬 쉬워질 것이다. => world space에서 camera sapce로의 변환을 View Transform이라고 한다.