DirectX/개념

[DX] ##5. 직교투영, Screen Space ( Window Space )

코딩하는상후니 2022. 8. 2. 22:03

 

 


 

 

*Clip Space

 
=> 대표적인 투영 방법
원근 투영   /   직교 투영
 
 
 
 

*직교 투영 ( Orthogonal Projection )

 

 

=> 평면상 수직으로 투영된다.
=> 원근감이 존재하지 않는다.
=> UI ( User Interface ) 를 랜더링할 때 많이 쓰임.
 
 
 
 
@viewport width  :  w
 
@viewport height  : h
 
 
@near  plane :  n
 
 
@far  plane :  f

 

 

 

 

 

 

* 직교 투영 행렬

 

 

 

 

=> 직교 투영 행렬은 원근 투영 행렬보다 비교적 어렵지 않게 구할 수 있다.
몇 가지만 짚고 넘어가자.
 
 
 
 
 
 
* 왜 2 / w ,  2 / h  인가 ??
 
=> 투영좌표계에선 x, y 가 (-1, 1) 이기 때문.
 
가운데 ( 0,0 ) 에서,
x 기준  :   1,4 사분면  /  2,3 사분면
y 기준  :  1,2 사분면  /  3,4 사분면
 
으로 각각 나누어줘야함.
 
 
 
 
 
 
 
 
* 왜 깊이를 설정해야하는가 ??
 
 
=> 원근 투영과 마찬가지로, NDC 공간으로 선형변환하는 것이기에
NDC  :   x,y ( -1, 1 ), z ( 0, 1 ) 범위로 맞춰주어야함.
 
 
=> 계산의 편의성을 위해서
직교 투영을 하는 물체들끼리 깊이 계산이 필요할 수도 있다.
( 보통은 깊이를 0 으로 둘 것이다. )
 
 
참고할 것은
직교 행렬을 적용하게 되면 바로 NDC 공간에 가게 된다.
( z 를 나누어주는 과정이 없기 때문. )
 
 
 

 

 

 
 
 
 
 
* A, B 를 구해보자.
 
=> 원근 투영에서 했던 것과는 다르게,
직교 투영에서는 행렬에 z 를 나누는 부분이 없기 때문에
 
최종 식은
z'  =  Az + B 가 된다.
 
g (z) = Az + B
g (n) = 0
g (f) = 1
 
해당 방정식을 풀게 되면 A, B 를 구할 수 있다.
 
A = -1 / n - f   ->  ( 1 / f - n )
B = n  /  n - f
 
 
 

 

 

 

 

 
 

* XMMatrixOrthographicLH

 
=> 위에서 설명한대로 DX 에 제공되는 함수도 같은 방식을 사용하는 것을 볼 수 있다.
=> 하나씩 해당 행들을 M 에 넣으면서 마지막에 M 을 리턴한다.

 

inline XMMATRIX XM_CALLCONV XMMatrixOrthographicLH 
( 
    float ViewWidth, 
    float ViewHeight, 
    float NearZ, 
    float FarZ 
)


XMMATRIX M; 
float fRange = 1.0f / (FarZ-NearZ); 
// Note: This is recorded on the stack 
XMVECTOR rMem = { 
    2.0f / ViewWidth, 
    2.0f / ViewHeight, 
    fRange, 
    -fRange * NearZ 
};

// Copy from memory to SSE register 
XMVECTOR vValues = rMem; 
XMVECTOR vTemp = _mm_setzero_ps();

// Copy x only 
vTemp = _mm_move_ss(vTemp,vValues); 
// 2.0f / ViewWidth,0,0,0 
M.r[0] = vTemp;
// 0,2.0f / ViewHeight,0,0 
vTemp = vValues; 
vTemp = _mm_and_ps(vTemp,g_XMMaskY); 
M.r[1] = vTemp; 
// x=fRange,y=-fRange * NearZ,0,1.0f 
vTemp = _mm_setzero_ps(); 
vValues = _mm_shuffle_ps(vValues,g_XMIdentityR3,_MM_SHUFFLE(3,2,3,2)); 
// 0,0,fRange,0.0f 
vTemp = _mm_shuffle_ps(vTemp,vValues,_MM_SHUFFLE(2,0,0,0)); 
M.r[2] = vTemp; 
// 0,0,-fRange * NearZ,1.0f 
vTemp = _mm_shuffle_ps(vTemp,vValues,_MM_SHUFFLE(3,1,0,0)); 
M.r[3] = vTemp; 
return M;

 

 

 


 

 

*ScreenSpace ( Window Space )

 

 

 

 

*viewport
 
=> 후면버퍼의 한 직사각형 영역.
=> 전체영역은 아님.
=> 여러 개의 viewport 생성 가능.
 
예를 들어, 1p, 2p 의 화면... 등
 
 

 

=> 이 변환을 마치고 나면 x,y 성분은 픽셀 단위의 값이 된다.
 
 
 
 
 
 
 
 

* ScreenSpace 행렬 만들기

 
 
Width, Height  :  viewport 의 가로,세로 길이
Left, Top   :  viewport 의 왼쪽 상단의 시작점 위치
Min/Max Depth  :  viewport 의 최대, 최소 깊이
 
 
 
=> 해당 데이터를 기준으로 행렬을 만들어 갈 것.
 
 
 
x 좌표를 기준으로 생각할 때,
NDC 의 가장 왼쪽은 -1 이다.
viewport 의 가장 왼쪽은 0 이다.
 
-1 -> 0 으로 비례해야하기 때문에
w / 2 만큼 곱하고 다시 w / 2 더하게 되면 0 으로 비례하게 된다.
이어서,
viewport 의 Left 지점까지 고려해야하므로 +Left 를 해주어야 한다.
 
 
정리하면,
x'  =  x * w / 2 + w / 2 + Left
 
 
해당 식을 도출하기 위해  ( x,y,z,1 ) * ( 행렬 ) 의
1열은 (  w / 2 , 0, 0, Left + w / 2 )
 
y 부분도 같은 방식으로 도출 가능.
 
다만,
NDC 에서 y 의 1 은 viewport 에서 0 이기 때문에
1 -> 0 으로 비례해야한다. 따라서, ( -1 ) 을 곱해주어야 한다.
 
 
 
 
따라서, 아래의 행렬을 구할 수 있겠다.

 

 

 

여기서 A, B 를 비워둔 이유는
이전과 마찬가지로 Z 값을 조작하기 위해서인데 Min/Max Depth 에 관한 값이다.
 
 
보통은 z 값을 그대로 [ 0, 1 ] 사용하겠지만, 특정한 상황에서 사용할 수 있다.
 
예를 들어,
다수의 viewport 를 사용할 때,
viewport 간 Depth 차이를 구별하기 위해서이다. ( RenderTarget )
 
 

 

 

 

 

 

* Screen Space 로 선형변환 행렬

 

 

 

 


 

참고 자료

 

 

- 프랭크 D.루나. ( DX11 을 활용한 3D 게임 프로그래밍 입문 ),  류광(역)