본문 바로가기
College Study/OpenGL

[그래픽스] 투영 행렬, Look-At 행렬, GLSL 변환 행렬 함수 (Projection Matrices, Look-At Matrices, GLSL Functions for Transformation Matrices)

by 2den 2022. 3. 7.
728x90

* 이 게시물은 Computer Graphics Programming in OpenGL with C++ 책의 일부를 번역 및 재해석한 게시물입니다. 의역 또는 오역이 있을 수 있으니 참고하시고, 피드백은 댓글을 남겨주세요.

투영 행렬 (Projection Matrices)

우리가 카메라를 설치하였기 때문에, 프로젝션 행렬을 검토할 수 있습니다. 우리가 이제 검토할 두 가지 중요한 프로젝션 행렬은 원근(perspective)과 직교(orthographic)입니다.


1. 원근 투영 행렬 (Perspective Projection Matrices)

원근 투영은 원근의 개념을 활용함으로써 우리가 현실 세계를 볼 때 보는 것을 모방하여 2D 그림을 3D처럼 보이게 합니다. 가까운 오브젝트는 멀리 떨어져 있는 오브젝트보다 더 크게 나타나며, 경우에 따라 3D 공간에서 평행한 선들이 원근감 있게 그려질 때에는 평행하지 않게 나타납니다.

원근은 1400~1500년대 르네상스 시대의 위대한 발견들 중 하나였으며, 예술가들은 이전보다 더 리얼리즘으로 그림을 그리기 시작했습니다.

훌륭한 예는 Carlo Crivelli의 그림 "The Annunciation, with Saint Emidius"에서 볼 수 있습니다. 이 그림에는 원근을 강력하게 사용했습니다. 오른쪽에 있는 건물의 뒤를 향하는 왼쪽 벽면을 이루는 선들은 서로를 향해 극단적으로 기울어져 있습니다. 이것은 3D의 깊이감과 공간감의 환상을 만듭니다. 현실에 평행한 선들은 그림에선 평행하지 않습니다. 또한, 앞에 있는 사람들은 뒤에 있는 사람들보다 큽니다. 오늘날 우리는 이러한 장치를 당연하게 여기지만, 이것을 구현하기 위해서 변환 행렬을 찾는 데에는 몇 가지 수학적 분석이 필요합니다.

평행한 선을 평행하지 않은 선으로 적절히 변환하는 행렬 변환을 사용합니다. 이러한 행렬은 원근 행렬 (perspective matrix) 또는 원근 변환(perspective transform)이라고 하며 view volume의 네 개의 매개변수 종횡비(aspect ratio), 시야(field of view), 투영 평면(혹은 근거리 절단 평면, projection plane or near clipping plane), 원거리 절단 평면(far clipping plane)을 정의하여 빌드됩니다.

근거리 절단 평면과 원거리 절단 평면 사이에 있는 오브젝트들만이 렌더됩니다. 근거리 절단 평면은 오브젝트들이 투영되는 평면이기도 하며, 일반적으로 눈이나 카메라 가까이에 위치합니다. 시야(field of view)는 볼 수 있는 공간의 수직 각도입니다. 종횡비(aspect ratio)는 근거리 절단 평면과 원거리 절단 평면의 너비/높이 비율입니다. 이 요소글로 인해 생성되고 보여지는 모양을 frustrum이라고 합니다. (사각뿔대 모양)

원근 행렬은 3D 공간의 점을 근거리 절단 평면(투영 평면)의 적절한 위치로 변환하는 데 사용됩니다. 일단 값 q, A, B, C을 계산하고, 다음과 같이 행렬을 구성하여 그 위치를 구합니다.

원근 변환 행렬을 생성하는 것은 4x4 행렬 셀에 수식을 계산하여 대입하기만 하면 되는 간단한 문제입니다. GLM 라이브러리에는 원근 행렬을 구축하기 위한 함수 glm::perspective()가 있습니다.

 

 

2. 직각 투영 행렬 (Orthographic Projection Matrix)

 

직각 투영에서 평행한 선은 평행을 유지합니다. 즉, 원근은 사용되지 않습니다. 대신 view volume 안에 있는 오브젝트들은 카메라로부터의 거리에 따른 크기 조정 없이 바로 투영됩니다.

 

 

직각 투영은 모든 투영이 투영 평면과 직각을 이루는 평행한 투영입니다. 직각 행렬은 다음과 같은 parameter를 정의하여 만들어집니다 :

1. 카메라에서 투영 평면까지의 거리 Znear

2. 카메라에서 원거리 절단 평면까지의 거리 Zfar

3. 투영 평면의 왼쪽과 오른쪽 경계의 X 좌표에 대응하는 L과 R, 투영 평면의 위쪽과 아래쪽 경계의 Y 좌표에 대응하는 T와 B

 

모든 평행 투영이 직각 투영은 아니지만, 다른 투영 방식은 다루지 않습니다.

 

평행 투영은 눈으로 실제 세상을 볼 때 보는 것과 일치하지 않습니다. 그러나 그림자를 만들거나 3D 클리핑을 할 때, 그리고 CAD(Computer Aided Design)에서 등 다양한 상황에서 유용합니다. (CAD에서는 오브젝트의 배치와 상관 없이 측정 값을 유지하기 때문입니다.) 책의 대부분의 예시에서는 원근 투영을 사용합니다.

 

 

Look-At 행렬

마지막으로 소개할 행렬은 Look-At 행렬입니다. 이 행렬은 특정 위치에 카메라를 배치하고 다른 특정 위치를 바라보고 싶을 때 사용하기 편리합니다.

물론, 지금까지 살펴본 다른 행렬들로도 이것을 구현할 수 있지만, 흔히 사용되는 연산이기 때문에 하나의 행렬을 구축해 놓는 것도 유용할 것입니다.

 

Look-At 변환도 카메라 방향을 결정해야 합니다. 일반적인 방향(예를 들어, world space의 Y축 방향)에 근접한 벡터로 카메라 방향을 지정합니다. 일반적으로 외적을 반복하여 원하는 카메라 방향을 위한 적절한 forward, side, up 벡터를 생성할 수 있습니다. 아래의 연산은 카메라(눈)의 위치, 타겟의 위치, 초기 up 벡터 Y축으로 시작하여 Look-At 행렬을 만드는 것을 보여줍니다.

 

카메라 위치, 타겟 위치, 초기 up 벡터 Y 변수에 구체적인 지정 값을 대입함으로써 Look-At 행렬을 만드는 간단한 C++/OpenGL 유틸리티 함수를 만들 수 있습니다. GLM이 Look-At 행렬을 만들기 위한 함수 glm::lookAt()을 포함하므로 간단하게 이것을 사용하면 됩니다. 이 기능은 특히 그림자를 생성할 때 유용합니다.

 

 

GLSL 변환 행렬 함수

GLM은 translation, rotation, scale과 같이 많은 3D 변환을 수행하기 위한 정의된 함수가 포함되어 있지만, GLSL은 addition, concatenation 등과 같은 기본적인 행렬 연산만 포함합니다. 따라서 쉐이더에서 특정 3D 연산을 수행해야 할 때 3D 변환 행렬을 구축하기 위한 자체적인 GLSL 유틸리티 기능을 작성해야 하는 경우가 있습니다. GLSL에서 이러한 행렬을 담을 수 있는 적절한 데이터 형은 mat4입니다.

 

GLSL에서 mat4 행렬을 초기화하기 위한 syntax는 값을 열(column) 단위로 로드하는 것입니다. 첫 4개의 값은 첫번째 열에, 다음 4개의 값은 두번째 열에 들어갑니다.

translation 행렬

 

Program 3.1은 4x4 translation, rotation, scale 행렬을 구축하기 위한 5개의 GLSL 기능이 포함되어 있으며, 각 기능은 위에서 소개한 수식에 해당합니다.

 

[Program 3.1 Building Transformation Matrices in GLSL]

//builds and returns a translation matrix
mat4 buildTranslate(float x, float y, float z)
{
	mat4 trans = mat4(	1.0, 0.0, 0.0, 0.0,
                        0.0, 1.0, 0.0, 0.0,
                        0.0, 0.0, 1.0, 0.0,
                        x, y, z, 1.0);
	return trans;
}

//builds and returns a matrix that performs a rotation around the X axis
mat4 buildRotateX(float rad)
{
	mat4 xrot = mat4 (	1.0, 0.0, 0.0, 0.0,
                        0.0, cos(rad), -sin(rad), 0.0,
                        0.0, sin(rad), cos(rad), 0.0,
                        0.0, 0.0, 0.0, 1.0);
    return xrot;
}

//builds and returns a matrix that performs a rotation around the Y axis
mat4 buildRotateY(float rad)
{
	mat4 yrot = mat4 (	cos(rad), 0.0, sin(rad), 0.0,
                        0.0, 1.0, 0.0, 0.0,
                        -sin(rad), 0.0, cos(rad), 0.0,
                        0.0, 0.0, 0.0, 1.0);
    return yrot;
}

//builds and returns a matrix that performs a rotation around the Z axis
mat4 buildRotateZ(float rad)
{
	mat4 zrot = mat4 (	cos(rad), -sin(rad), 0.0, 0.0,
                        sin(rad), cos(rad), 0.0, 0.0,
                        0.0, 0.0, 1.0, 0.0,
                        0.0, 0.0, 0.0, 1.0);
	return zrot;
}

//builds and returns a scale matrix
mat4 buildScale(float x, float y, float z)
{
	mat4 scale = mat4(	x, 0.0, 0.0, 0.0,
                        0.0, y, 0.0, 0.0,
                        0.0, 0.0, z, 0.0,
                        0.0, 0.0, 0.0, 1.0);
	return scale;
}

 

728x90

댓글