Post Lists

2018년 6월 24일 일요일

Getting Started - Transformation (8) 번역

위의 자료를 번역한 내용입니다.
--------------------------------------

Transformations
우리는 이제 어떻게 객체를 생성하고 그것들에 색을 입히고 또는 그들에게 텍스쳐를 사용하여 디테일한 모습을 주는 방법을 알지만, 그것들은 여전히 흥미롭지 않다. 왜냐하면 정적인 오브젝트이기 때문이다. 우리는 그들의 정점을 바꾸고, 매 프레임마다 버퍼를 재설정하여 그들을 움직이게 할 수 있다. 하지만, 그것은 번거롭고 꽤 많은 연산량을 소비한다. 객체를 transform시키는 더 좋은 방법들이 있다. 그것은 다양한 matrix 객체들을 사용하는 것이다. 이것은 우리가 쿵푸와 큰 디지털 인공 세계에대해서 말할 것이라는 것을 의미하는 것이 아니다.

행렬은 처음에는 무서운 것처럼 보이는 매우 강력한 수학적 구성이다. 그러나 너가 그것들에 익숙해지기만 한다면, 그것들은 매우 유용하다고 입증될 것이다. 행렬에 대해 이야기 할 때, 우리는 조금 수학에 뛰어들어야만 할 것이다. 수학적으로 깊은 독자들에게 나는 부가적인 자료들을 나중의 읽기를 위해 덧 붙일 것이다.

그러나, 완전히 transformation을 이해하기 위해서, 우리는 처음에 행렬에 대해 토론하기 전에 벡터로 좀 더 깊게 파고들어야만 한다. 이 챕터의 초점은 너에게 우리가 나중에 요구할 주제들에 있는 기본적인 수학 배경지식을 주는 것이다. 만약 주제가 어렵다면, 그것들을 너가 할 수 있는 한 이해하려고 노력하고, 이 페이지에 다시 돌아와서 너가 그것들을 필요할 때마다 개념들을 복습해라.

Vectors
그것의 가장 기본적인 정의에서, vector들은 방향들일 뿐이다. 벡터는 방향과 크기를 (또한 힘 또는 길이라고 알려진) 가지고 있다. 너는 보물 지도에 있는 방향처럼 벡터를  생각할 수 있다. 여기에서 '왼쪽'은 방향이고, '10걸음'은 벡터의 크기이다. 보물지도에 대한 방향은 따라서 3개의 벡터를 포함하고 있다. 벡터들은 어떤 차원이든지 가질 수 있다. 그러나 우리는 보통 2에서 4차원 까지를 가지고 작업한다. 만약 한 벡터가 2차원을 가지고 있다면, 그것은 평면에서 (2d 그래픽을 생각해보아라) 방향을 나타낸다. 그것의 3차원을 나타낼 때, 그것은 3D 세계에서의 어떤 방향을 나타낼 수 있다.

아래에서 너는 각 벡터가 2d 그래프에서 화살표로서 (x, y)로 표기되어져있는 3 개의 벡터들을 볼 것이다. (3d에서 보다) 2ㅇ에서 벡터들을 보여주는 것이 더욱 직관적이기 때문에, 너는 2d 벡터들을 z좌표가 0인 3d 벡터들로 생각할 수 있다. 벡터들은 방향을 나타내기 때문에, 벡터의 origin은 그것의 값을 바꾸지 않는다. 아래의 그래프에서 우리는 벡터 v와 w가 그것들의 원점이 다를지라도 동일하다는 것을 알 수 있다. 

벡터들을 묘사할 때, 수학자들은 일반적으로 벡터들을 v-처럼 그들의 머리위에 작은 바가 올려진 문자 상징으로서 벡터들을 묘사하는 것을 선호한다. 또한, 공식에서 벡터들을 보여줄 때, 일반적으로 다음과 같이 보여진다.

v = ( x
      y
      z )

벡터들을 방향으로서 명시되기 때문에, 그것들을 위치로서 시각화하는 것은 어렵다. 우리가 기본적으로 시각화 하는 것은 우리가 방향의 원점을 (0,0,0)으로 설정하고 그 점을 명시하는 어떤 방향쪽으로 point한다는 것이다. 그리고 이것은 position vector를 만든다. (우리는 또한 다른 원점을 명시하고 말할 수 있따: '이 벡터는 이 원점으로부터 공간에서의 그 점을 가리킨다.) position vector (3,5)는 그러면 (0,0)인 원점에서 그래프에서 (3,5)를 가리킬 것이다. 벡터를 사용하여 우리는 따라서 방향과 위치를 2D와 3D공간에서 묘사할 수 있다.

평범한 수처럼 우리는 또한 벡터에 대한 몇 가지 연산을 정의할 수 있다. (몇몇 사람들을 이미 봤을 것이다.)

Scalar vector operations
scalar는 단일의 숫자이다. (또는 너가 vector 세계에 머무르고 싶다면 한 개의 요소를 가진 벡터이다.) 한 벡터를 한 개의 스칼라를 가지고 더하거나/빼거나/곱하거나/나누거나 할 때, 우리는 간단히 스칼라로 그 벡터의 각 요소를 더하고/빼고/곱하고/나눌 수 있다. 덧셈을 하기 위해, 이것 처럼 보일 것이다.

+는  +, -, *, /가 될 수 있고, *는 곱 연산자이다. 명심해라, -와 / 연산자 순서에 대해, 반대 순서는 정의되어있지 않다.

Vector negation
벡터의 부호를 바꾸는 것은 반대 방향의 벡터를 만든다. 북동쪽을 가리키는 벡터는 negation후에 남서쪽을 가리킬 것이다. 벡터의 부호를 바꾸기 위해, 우리는 마이너스 사인을 각 요소에 더한다. (너는 또한 그것을 -1의 스칼라값으로 스칼라-벡터 곱으로서 나타낼 수 있다.)

Addition and subtraction
두 개의 벡터를 더하는 것은 컴포넌트방향으로 더해지는 것으로 정의된다. 즉, 한 벡터의 각 컴포넌트는 다른 벡터의 같은 컴포넌트에 더해진다는 것이다.

시각적으로, 그것은 벡터 v(4,2)와 k(1,2)에 대해 이렇게 보인다.

일반적인 덧셈과 뺄셈처럼, 벡터 뺄셈은 두 번째 벡터를 negate하여 더한 것과 같다.

두 벡터를 서로에 대해 빼는 것은 두 벡터가 가리키고 있는 위치의 차이인 벡터를 만들어 낸다. 이것은 우리가 두 점 사이의 차이인 벡터를 알아낼 필요가 있는 경우에 유용하다고 입증된다.

Length
한 벡터의 길이또는 크기를 알아내려면, 우리는 너의 수학 시간으로부터 기억할지도 모르는 피타고라스 정리를 사용한다. 한 벡터는 너가 그것의 x와 y요소를 한 삼각형의 두 변으로 시각화할 때 삼각형을 형성한다.

두 변 (x,y)의 길이는 알려져있고, 우리는 그 경사진 변 v-의 길이를 알고 싶기 때문에, 우리는 그것을 피타고라스 정리를 사용하여 계산할 수 있다.

||v-|| = root(x^2 + y^2)

||v-||는 벡터 v-의 길이로 표기된다. 이것은 쉽게 z^2을 그 방정식에 더하여 3D에 확장될 수 있따.

이 경우에 벡터 (4,2)의 길이는 root(20) = 4.47이다.

또한 우리가 단위 벡터라고 부르는 특수한 벡터 종류가 있다. 단위 벡터는 하나의 추가적인 특징을 가진다. 그것은 그것의 길이가 정확히 1이라는 것이다. 우리는 어떤 벡터이든지 그것의 길이로 벡터의 요소들을 각각 나누어서 단위 벡터 n^를 계산할 수 있다.

n^ = v- / ||v-||

우리는 이것을 벡터를 표준화(normalizing)한다고 부른다. 단위벡터들은 그것들의 머리위에 작은 지붕과함꼐 보여진다. 그리고 일반적으로 작업하기에 더 쉽다. 특히 우리가 그것들의 방향을 신경써야 할 때. (방향은 우리가 벡터의 길이를 바꾼다면 변하지 않는다.)

Vector-vector multiplication
 두 개의 벡터들을 곱하는 것은 조금 이상한 경우이다. 일반적인 곱은 벡터에 대해 정의됮 않는다. 어떠한 시각적 의미를 가지고 있지 않기 때문이다. 그러나, 우리는 곱할 때 선택할 수 있는 두 가지 특정한 경우를 가진다. 하나는  로 표기되는 dot product(점곱, 내적)이고 다른 하나는 로 표기되는 cross product(가위곱, 외적)이다.

Dot product
두 벡터의 내적은 그들의 길이의 스칼라곱을 두 벡터 사이의 각의 cosine을 곱한 것이다. 만약 이것이 혼란 스럽게 들린다면 그것의 공식을 보아라.


여기에서 그 벡터들 사이의 각은 세타()로 표기된다. 이것이 왜 흥미로운것인가? 와 가 단위벡터라고 가정하면 그것들의 길이는 1로 동일할 것이다. 이것은 효과적으로 공식을 다음과 같이 줄인다.


이제 내적은 오직 두 벡터 사이의 각을 정의한다. 너는 cosine 또는 cos 함수가 각도가 90도일 때 0이 되고 0일 때 1이라는 것을 기억할지도 모른다. 이것은 우리가 두 벡터가 orthogonal(직교) 하는지 또는 서로 평행하는지를 테스트할 수 있게 해준다. 내적을 사용해서. (orthogonal은 벡터들이 서로 직각으로 만난다는 것을 의미한다.) 너가 sin 또는 cosine 함수들에 대해 더 알기를 원하는 경우에, 나는 기본 삼각법에 대한 다음의 Khan Academy video를 제안한다.

!Green Box
너는 단위벡터가 아닌 두 개의 벡터들 사이의 각을 또한 계산할 수 있다.그러나 그러면 너는 두 벡터의 길이를 cos(theta)로 남은 결과를 나누어야 한다.

그래서 어떻게 내적을 계산하는가? 내적은 컴포넌트 순서 곱이다. 거기에서 우리는 결과를 서로 합한다. 그것은 두 개의 단위 벡터들이 있는 이것 처럼 보인다. (너는 두 벡터들의 길이가 정확히 1이라는 것을 증명할 수 있다.)


두 개의 이러한 단위벡터들 사이의 각도를 계싼하기 위해 우리는 역코사인 함수 cos^-1을 사용한다. 그리고 이것은 143.1도를 결과를 만든다. 우리는 효과적으로 이러한 두 벡터 사이의 각을 계산했다. 내적은 조명 계산을 할 때 매유 유용하다는 것이 입증된다.

Cross product
외적은 오직 3D 공간상에서만 정의된다. 그리고 input으로서 두 개의 평행하지않은 벡터들을 받고 두 input 벡터들에 직교하는 세 번째 벡터를 만들어 낸다. 만약 그 input 벡터들이 또한 서로 직교한다면, 외적은 3 개의 직교하는 벡터들을 만들어 낸다. 이것은 다음의 튜토리얼들에서 유용하다고 증명될 것이다. 다음의 이미지는 이것이 3D 공간 상에서 어떻게 보이는지 보여준다.

다른 연산과 다르게, 외적은 선형 대수를 공부하지 않고서는 정말 직관적이지 않다. 그래서 공식을 그냥 외우는 것이 가장 좋다. 아래에서 너는 두 직교하는 벡터 A와 B 사이의 외적을 볼 것이다.

너도 보듯이, 정말 이해가 안되는 것처럼 보인다. 그러나 만약 너가 이 단계들을 따라온다면, 너는 너의 input 벡터들에 직교하는 또 다른 벡터를 어등ㄹ 것이다.

Matrices
 벡터에 대해서 거의 이야기 했으니, 이제 행렬로 들어갈 차례이다. 행렬은 기본적으로 정사각형의 숫자, 기호, 표현들의 배열이다. 각각의 행렬의 item들은 행렬의 원소라고 불려진다. 2x3 행렬의 아래에서 보여진다.


Addition and subtraction
Matrix-scalar products
Matrix-matrix multiplication
Matrix-vector multiplication
Identity matrix

행렬에 관한 기본적인 연산이여서 Pass

Scaling
우리가 벡터를 스케일링 할 때, 우리는 우리가 스케일하고 싶은 양으로 화살의 길이를 증가시킬 것이다. 그것의 방향을 같게 유지하면서. 우리가 2 또는 3차원에서 작업할 것이기 때문에, 우리는 2 또는 3 개의 스케일링 변수들의 벡터로 스케일링을 정의할 수 있다. 각 스케일링의 한 축으로 (x, y or z).

vector 를 스케일링 해보자. 우리는 그 벡터를 x축을 따라 0.5 스케일링할 것이다. 그것을 두 배정도 좁게 하면서. 그리고 우리는 y축으로 2로 그 벡터를 스케일링 할 것이다. 높이를 두 배를 만들면서. 우리가 (0.5, 2)로 스케일링을하면 어떨지 봐보자.

OpenGL은 보통 3D 공간에서 연산한다는 것을 기억해라. 2D의 경우에 우리는 z-축 scale을 1로 설정하여 그것이 변동이 없도록 할 수 있다. 우리가 수행했던 scaling operation은 non-uniform scale 이다. 왜냐하면 scaling 인자들이 각 축에 대해 같지 않기 때문이다. 만약 scalar가 모든 축에 대해 동일 하다면 그것은 uniform scale이라 불려진다.

우리를 위해 scaling을 하는 transformation 행렬을 만들어보자. 우리는 항등행렬로부터 대각선의 원소 각각이 그것에 대응하는 벡터의 원소와 곱해지는 것을 보았다. 우리가 그 항등행렬에 있는 1들을 3으로 바꾸면 어떨까? 그러한 경우에, 우리는 그 벡터의 원소들을 3의 값으로 곱할 것이고 효과적으로 벡터를 3으로 스케일링 할 것이다. 만약 그 스케일 변수를 (S1, S2, S3)로 나타낸다면 우리는 스케일링 행렬을 어떤 벡터 (x, y, z)에대해 다음과 같이 정의할 수 있다.


4번째 스케일 벡터가 1로 유지되는 것에 주목해라. 왜냐하면 3D 공간에서 w 컴포넌트를 스케일하는 것의 정의되지 않기 때문이다. w component는 우리가 나중에 볼 다른 목적을 위한 사용이다.

Translation
Translation(이동)은 다른 위치를 가진 새로운 벡터를 반환하기 위해 원래의 벡터 위에 다른 벡터를 더하는 과정이다. 따라서 이것은 translation vector를 기반으로 그 벡터를 움직이게 한다. 우리는 이미 벡터 덧셈에 대해 이야기 했다. 그래서 이것은 또한 새로운 것이 아닐 것이다.

스케일링 행렬처럼 어떤 연산을 수행하기 위해 우리가 사용할 수 있는 4x4 행렬에 몇 가지 위치들이 있다. 그리고 이동을 위해 그러한 것들은 4번째 열에서 위에서 3개의 값들이다. 만약 우리가 scaling vector를 (Tx, Ty, Tz)로 표현한다면 우리는 이동 행렬을 다음과 같이 정의한다.


이것은 모든 이동 값들이 벡터의 w로 곱해지고 벡터의 원래 값에 더해지기 때문에 작동한다. (행렬곱 규칙을 기억해라.) 이것은 3x3행렬에서는 가능하지 않다.

Green Box
Homogenous coordinates(동차 좌표)
벡터의 w 컴포넌트는 homogenous coordinate라고 또한 알려져 있다. 동차 벡터로부터 3D 벡터를 얻기 위해, 우리는 x,y,z 좌표를 그것의 w 좌표로 나눈다. 우리는 보통 이것을 알지못한다. 왜냐하면 w 요소가 대게 1이기 때문이다. 동차좌표를 사용하는 것은 몇 가지 이점을 가진다 : 그것은 우리가 3D 벡터들에 대해 이동(translation)을 할 수 있게 해준다. (w 요소가 없이 우리는 vector들을 translate 할 수 없다.) 다음 챕터에서 우리는 3D visuals를 위해서 w값을 사용할 것이다.

또한 동차 좌표가 0일 떄마다, 그 벡터는 특정하게 방향 벡터로 알려져 있다. w가 0인 벡터는 이동(translated)될수 없기 때문이다.

이동 행렬로, 우리는 객체를 우리가 원하는 3 가지 방향 (x,y,z) 어디든지 움직일 수 있다. 이것은 우리의 transformation toolkit에 대해 유용한 transformation matrix를 만든다.

Rotation
마지막 몇 안되는 변환들은 상대적으로 2D 또는 3D 공간에서 이해하고 시각화하기에 쉬웠다. 그러나 회전은 조금 까다롭다. 만약 너가 이러한 행렬들이 정확히 어떻게 구성되는지를 알고 싶다면, Khan Academy의 선형대수학 비디오의 회전 부분을 보기를 추천한다.

한 벡터의 회적이 실제로 무엇인지 정의하자. 2D 또는 3D에서의 회전은 각도로 표현된다. 각도는 degree 또는 라디안일 수 있다. 그리고 전체 원은 360도이거나 2PI 라디안을 가진다. 나는 개인적으로 '도'로 작업하는 것을 선호한다. 왜냐하면 그렇게 하는 게 나에게 더 이해가 잘 되기 때문이다.

Green Box
대부분의 회전 함수들은 라디안 각을 요구한다. 그러나 운 좋게도 각도들은 쉽게 라디안으로 변환되어진다.
angle in degrees = angle in radias * (180.0f / PI)
angle in radians = angle in degrees * (PI / 180.0f)
여기에서 PI는 대략 3.14159265359이다.

한 원을 절반 회전시키는 것은 우리게에 180도를 회전시키게 한다. 오른쪽으로 1/5 회전시키는 것은 360/5 = 72도로 오른쪽으로 회전시키는 것을 의미한다. 이것은 vector v가  k로부터 72도 회전되는 기본 2D 벡터로 보여줄 수 있다.

3D에서 회전들은 각도와 회전축으로 명시된다. 거기에서 각도는 theta symbol로 표현된다.

X축에 대한 회전

Y축에 대한 회전

Z 축에 대한 회전

회전 행렬을 사용하여, 우리는 세 개의 단위 축 중에 하나 주변에서 우리의 위치벡터를 변환할 수 있다. 그것들을 처음에 X축에 대해 회전하고 그런 다음에 Y축에대해 회전하여 합치는 것이 가능하다. 그러나, Gimbal lock이라 불리는 문제를 만들어낸다. 우리는 세부사항에 대해서 이야기 하지 않을 것이지만, 더 좋은 해결책은 임의의 단위 축에 대해 회전시키는 것이다. 예를들어 (0.662, 0.2, 0.722) (이것이 단위 벡터라는 것을 주의해라) 당장 그 회전 행렬을 합치는 대신에. 그러한 한 행렬은 존재하고 아래에서 (Rx, Ry, Rz)로 임의의 회전 축에 대해 주어진다.


이러한 행렬을 만들어내는 수학적인 토론은 이 튜토리얼의 범위를 벗어난다. 심지어 이 행렬은 완젛니 gimbal lock을 해결하지 못한다는 것을 명심해라. (더 어렵게 얻어졌을지라도.) 진짜로 Gimbal lock을 방지하기 위해서 우리는 quaternions을 사용하여 회전을 표현해야만 한다. 그것은 안전할 뿐만아니라 좀더 연산적으로 친화적이다. 그러나 사원수에 대한 논의는 나중의 튜토리얼로 미뤄둔다.

Combining matrices
 변환에 대해 행렬들을 사용하는 진정한 힘은 우리가 단일의 매트릭스에서 행렬-행렬 곱 덕분에 여러가지 변환을 합칠 수 있다는 것이다. 우리가 몇 가지 변환들을 합치는 변환 행렬을 만들어 낼 수 있는지 알아보자. (x,y,z) 벡터가 있고 우리가 그것을 2만큼 스케일하고, 그리고나서 (1,2,3)으로 그것을 이동시키자고 하자. 우리는 이동과 스케일링 행렬을 우리의 요구된 단계에서 필요로 한다. 그 결과 변환 행렬은 다음과 같을 것이다.


우리가 행렬을 곱할 때 처음에 이동을하고 그 후에 스케일 변환을 했다는 것에 주목해라. 행렬 곱은 교환법칙이 성립하지 않는다. 이것은 그것들의 순서가 중요하다는 것을 의미한다. 행렬을 곱할 때 가장 오른쪽에 있는 것이 그 벡터에 처음 곱해지는 것이다. 그래서 너는 오른쪽에서 왼쪽으로 곱들을 읽어야만 한다. 행렬을 합칠 때 처음에 스케일 연산을 하고 그 후에 회전 그리고 마지막으로 이동하는 것이 충고된다. 만약 그렇지 않는다면, 그것들을 (부정적으로) 서로에게 영향을 미칠 지도 모른다. 예를들어 만약 너가 변환을 먼저하고 스케일을 한다면, 그 이동 벡터 또한 스케일 될 것이다.

최종 변환 행렬을 우리의 벡터에 작동시키는 것은 다음의 벡터를 만들어 낸다.


훌륭해! 그 벡터는 처음에 2로 스케일링되고, 그 후에 (1,2,3)만큼 이동한다.

In practice
변환 뒤에 있는 모든 이론을 설명 했으니, 우리가 실제로 이 지식을 우리의 이익을 위해 어떻게 사용할지를 볼 시간이다. OpenGL은 내장된 행렬 또는 벡터 지식을 가지고 있지 않다. 그래서 우리는 우리 자신의 수학 클래스와 함수를 정의해야만 한다. 튜토리얼에서 우리는 모든 작은 수학적 세부사항으로부터 추상화 할 것이고 간단히 미리 만들어진 수학 라이브러리들을 사용할 것이다. 운좋게도 GLM이라고 불리는 사용하기 쉽고 OpenGL에 맞게 만들어진 수학 라이브러리가 있다.

GLM
GLM은 OpenGL Mathematics를 말한다. 그리고 이것은 오직 header만 있는 라이브러리 이다. 이것은 우리가 적절한 헤더파일만 include 한다면 끝난다는 것을 의미한다. 어떠한 linking과 컴파일이 필요하지 않다. GLM은 웹사이트에서 다운받아질 수 있다.

Red Box
GLM 버전 0.9.9 이후로, GLM은 기본적으로 행렬 타입을 항등행렬 대신에 0으로 초기환된 행렬로 초기화 해야한다. 그 버전부터, 행렬 타입을 glm::mat4 mat = glm::mat4(1.0f)로 초기화 할 것이 요구되어 진다. 튜토리얼의 코드와의 일관성을 위해 0.9.9보다 낮은 GLM 버전을 사용하는 것이 충고된다 또는 모든 행렬들을 위에 언급된 것처럼 초기화 해라. 

우리가 필요한 GLM의 대부분의 기능은 우리가 포함하는 다음의 오직 세 개의 헤더파일에서 발견될 수 있다.

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

우리가 우리의 변환 지식을 (1,0,0)벡터를 (1,1,0)으로 이동하여 사용하도록 하는지 봐보자. (우리가 glm::vec4의 동차좌표를 1.0으로 한다는 것을 주목해라.)

glm::vec4 vec(1.0f, 0.0f, 0.0f, 1.0f);
glm::mat4 trans = glm::mat4(1.0f);
trans = glm::translate(trans, glm::vec3(1.0f, 1.0f, 0.0f));
vec = trans * vec;
std::cout << vec.x << vec.y << vec.z << '\n';

우리는 처음에 GLM의 내장 벡터 클래스를 사용하여 vec이라는 벡터를 정의했다. 다음에 우리는 4x4 항등행렬인 mat4를 정의한다. 다음단계는 우리의 항등행렬을 glm:translate 함수로 넘겨서 변환 행렬을 만드는 것이다. translation vector도 함께 (주어진 행렬은 그러고나서 이동 행렬과 곱해지고 결과 행렬이 반환된다.)

그리고 나서 우리는 우리의 벡터에 변환 행렬을 곱하고, 결과를 만들어 낸다. 만약 우리가 행렬 이동이 어떻게 작동하는지를 기억한다면, 결과 벡터는 (1 + 1, 0 + 1, 0 + 0)이 되어 (2,1,0)이 되어야 한다. 이 코드의 정보는 210을 만들어내고, 그래서 그 이동 행렬은 그것의일을 한다.

좀 더 흥미로운 것을 해보자. 이전 튜토리얼의 컨테이너 오브젝트를 스케일하고 회전 시켜보자.

glm::mat4 trans(1.0f);
trans = glm::rotate(trans, glm::radians(90.f), glm::vec3(0.0, 0.0, 1.0));
trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5));

처음에 우리는 그 컨테이너를 각 축에 대해 0.5로 스케일하고, Z축에 대해 90도 회전시킨다. GLM은 그것의 각을 라디안으로 기대한다. 그래서 우리는 그 도를 glm::radians를 사용하여 도를 라디안으로 변환한다. 그 텍스쳐 처리된 정사각형이 XY면에 있다는 것에 주목해라. 그래서 우리는 Z축에 대해 회전 시키고자 하는 것이다. 그 행렬을 GLM 함수의 각각에 넘겨주기 때문에, GLM은 자동으로 그 행렬들을 함께 곱한다. 이것은 모든 변환을 합친 변환 행렬을 만들어 낸다.

다음의 큰 질문은 어떻게 우리가 변환 행렬을 쉐이더로 가게 하는 것이냐 이다. 우리는 이전에 짧게 GLSL가 mat4 타입을 가지고 있다고 언급했었다. 그래서 우리는 vertex shader가 mat4 uniform variable를 받고 그 uniform 행렬에 position vector를 곱하도록 하게 할 것이다.

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;

out vec2 TexCoord;
  
uniform mat4 transform;

void main()
{
    gl_Position = transform * vec4(aPos, 1.0f);
    TexCoord = vec2(aTexCoord.x, aTexCoord.y);

Green Box
GLSL는 또한 mat2와 mat3 타입을 가지고 있는데, 이것은 벡터처럼 swizzling 같은 연산을 할 수 있다. 모든 앞에서 언급된 수학 연산들 (스칼라-행렬 곱, 행렬-벡터 곱 그리고 행렬-행렬 곱)은 행렬의 형태로 되어질 수 있다. 특별한 행렬 연산이 사용되는 곳 어디서든, 우리는 무엇이 일어나고 있는지 확실히 설명할 것이다.

우리는 uniform 변수를 추가했고, gl_Position에 position 벡터를 넘기기전에 transformation matrix를 곱했다. 우리의 container는 지금 두 배 작아지고 90도 (왼쪽으로 굽어진)로 회전되어 있을 것이다. 우리는 여전히 쉐이더에게 변환 행렬을 넘길 필요가 있다.

unsigned int transformLoc = glGetUniformLocation(ourShader.ID, "transform");
glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

우리는 처음에 uniform 변수의 위치를 찾고, 그리고나서 그것의 후위연산자로 Matrix4fv와 함께  glUniform 함수를 통해 쉐이더에 행렬 데이터를 보낸다. 첫 번째 인자는 uniform의 위치여서 지금까지는 친숙할 것이다. 두 번째 인자는 OpenGL에게 얼마나 많은 행렬들을 우리가 보내고 싶은지 말해준다. 그리고 그것은 현재 1이다. 세 번째 인자는 우리가 우리의 행렬을 전치시키고 싶은지 묻는다. 즉 행과 열을 바꾸는 것이다. OpenGL 개발자들은 종종 column-major ordering(열 중심 순서)라고 불려지는 내부 행렬 레이아웃을 사용한다. 이것은 GLM에서 기본 행렬 layout이다. 그래서 행렬을 전치시킬 필요가 없다. 우리는 그것을 GL_FALSE로 유지한다. 마지막 인자는, 실제 행렬 데이터이지만, GLM은 그것들의 행렬을 OpenGL이 받고 싶어하는 정확한 방식으로 행렬들을 저장하지 않는다. 그래서 우리는 처음에 그것들을 GLM의 내장함수인 value_ptr 함수로 변형한다.

우리는 변환 행렬을 만들었고, vertex 쉐이더에 uniform을 선언했으며, 우리가 vertex 좌표를 변형하고자 하는 쉐이더에 행렬을 보냈다. 그 결과는 이것 처럼 보일 것이다.

완벽해! 우리의 container는 실제로 왼쪽으로 기울었고, 두 배 더 작다. 그래서 변환은 성공적이였다. 좀 더 멋진 걸 해보고 우리가 시간에 따라 container를회전 시킬 수있는니 봐보자. 재미로 우리는 또한 그 container를 창의 오른쪽 하단 면에 재위치 시킬 것이다. 시간에 따라서 container를회전시키기 위해, 우리는 game loop에서 변환 행렬을 업데이트 해야만 한다. 왜냐하면 그것은 각 렌더링 반복을 업데이트 시킬 필요가 있기 때문이다. 우리는 시간에 따른 각을 얻기위해 GLFW의 시간 함수를사용한다.

glm::mat4 trans(1.0f);
trans = glm::translate(trans, glm::vec3(0.5, -0.5, 0.0));
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.f, 1.f));

이전의 경우에 우리는 transformation 행렬을 어디에서든지 선언한 것을 명시해라. 그러나, 이제 우리는 그것을 매 반복마다 만들어야 한다. 그래서 우리는 그 회전을 계속해서 업데이트한다. 이것은 우리가 게임 loop의 매 반복마다 변환 행렬을 다시 만들어야 하는것을 의미한다. 보통 장면을 렌더링 할 때, 우리는 각 렌더링 반복마다 새로운 값으로 다시 만들어지는 몇 가지 변환 행렬을 가진다.

여기에서 우리는 처음에 그 컨테이너를 원점에 대해 회전시킨다. 일단 그것이 회전한다면, 우리는 그것의 회전된 버전을 스크린의 오른쪽 하단 모서리로 이동시킨다. 실제 변환 순서는 역으로 읽어져야 한다는 것을 기억해라. 비록 코드에서 우리는 처음에 이동시키고 나중에 회전할지라도. 실제 변환은 처음에 회전이 적용되고 그 후에 이동을 적용한다.  이러한 모든 변환을 이해하고, 그것들이 객체에 어떻게 적용되는지를이해하는 것은 이해하기에 얿다. 이것처럼 변환을 가지고 시도하고 실험해보아라. 그러면 ㅓㄴ는 빠르게 그것을 이해할 것이다.

만약 너가 옳게 했다면, 다음과 같은 결과를 얻을 것이다.

그리고 이제 너가 해냈다. 이동된 컨테이너는 시간에 따라서 회전한다. 단일의 변환 행렬에 의해. 이제 너는 왜 그래픽스 땅에서 행렬이 왜 강렬한 구조물인지 알 수 있다. 우리는 무한한 변환을 정의하고 그것들을 하나의 행렬에 합칠 수 있다. 그리고 우리는 우리가 하고 싶은대로 종종 그것을 재사용 할 수 있다.  vertex shader에서 이것처럼 변환을 사용하는 것은 우리가 vertex data를 재정의 하는 노력을 덜어주고, 우리에게 연산시간을 줄여준다 또한. 왜냐하면 우리의 데이터를 항상 다시 보낼 필요가 없기 때문이다. (그리고 이것은 꽤 느리다.)

만약 너가 옳은 결과를 받지 못하고, 어딘가에서 막혔다면, 소스코드를 참조하고, 업데이트된 쉐이더 클래스를 보아라.
다음 튜토리얼에서 우리는 어떻게 우리가 정점들에대해 다른 좌표 공간들을 정의하기 위해 행렬들을 사용하는지에 대해 이야기 할 것이다. 이것은 실시간 3D 그래픽스에서 첫 단계가 될 것이다.

Exercises
- 컨테이너에서 마지막 변환을 사용해서, 처음에 회전하고 그리고나서 이동하여 순서를 바꾸어보아라. 무엇이 일어나는지 보고, 왜 이것이 일어나는지를 추론해라.

-  glDrawElements에 대한 다른 호출로 두 번째 container를 그리려고 시험해라. 그러나 transformation만을 사용하여 다른 위치에 두려고 해라. 이 두 번째 컨테이너가 창의 왼쪽 상단에 있도록 하고, 회전하는 대신에 시간에 따라 스케일링 해라. (sin 함수를 여기에 사용하는 것이 유용하다. sin을 사용하는 것은 객체가 음수 스케일이 적용되자마자, 뒤집어지는 것을 발생시킬 것이다.)

glm::mat4 trans(1.0f);
trans = glm::translate(trans, glm::vec3(0.5, -0.5, 0.0));
trans = glm::rotate(trans, (float)glfwGetTime(), glm::vec3(0.0f, 0.f, 1.f));
trans = glm::scale(trans, glm::vec3(0.7, 0.7, 0.7));
ourShader.setMat4("transform", trans);
ourShader.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

glm::mat4 trans2(1.0f);
trans2 = glm::translate(trans2, glm::vec3(-0.5, 0.5, 0.0));
float sinvalue = sin(glfwGetTime());
sinvalue = sinvalue < 0 ? -sinvalue : sinvalue;
trans2 = glm::scale(trans2, glm::vec3(sinvalue));
ourShader.setMat4("transform", trans2);
ourShader.use();
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

 

댓글 없음:

댓글 쓰기