[COORDINATE SYSTEM]
1. Before
- OpenGL에서는 각 vertex shader가 실행된 후 그리고자하는 모든 vertex들이 정규화된 디바이스 좌표로 표시되어야한다
: 각 vertex의 x, y, z 좌표가 -1.0 ~ 1.0 범위 안에 있어야한다 (그 밖의 좌표는 보이지 않음)
- 일반적으로 사용자가 직접 구성한 범위의 좌표를 지정하고 vertex shader에서 이 좌표들을 NDC로 변환한다
: 변환된 NDC 좌표들은 rasterizer에 보내지고, 화면 위의 2D coordinate/pixel로 변환된다
- 좌표를 NDC로 변환하는 것은 객체의 vertex을 NDC로 변환하기 전에 여러 좌표계로 변환하는 단계별 방식으로 수행된다
: 중간 좌표계로 변환하는 것은 일부 연산/계산들이 특정 좌표 시스템에서 수행하기 쉬워진다는 장점을 가진다
: Coordinate System 5가지 (이것들은 모두 vertex가 fragment로 변환되기 전에는 다른 상태이다)
Local Space (or Object Space)
World Space
View Space (or Eye Space)
Clip Space
Screen Space
2. The Global Picture
- 하나의 공간에서 다음 좌표 공간으로 좌표를 변환하기 위해 여러가지 변환 행렬을 사용한다
: 이 중 가장 중요한 것은 model, view, projection martix이다
- vertex 좌표는 local 공간에서 local 좌표를 사용하여 시작하고, world - view - clip 좌표 순서로 처리되고 최종적으로 screen 좌표로 변환된다
: 위 이미지는 이 과정을 시각화 한 그림이다
- 과정의 진행 순서
Local Space : local 좌표는 local 원점에 상대적인 object의 좌표로, object기 시작되는 좌표이다
World Space : 전체적인 world의 원점을 기준으로 하는 좌표로, 많은 양의 object를 배치할 수 있다
View Space : world 좌표를 각 좌표들이 카메라/보는 사람의 시점에서 보이는 view-space좌표로 변환한다
Clip Space : view-space로 변환된 좌표들을 clip 좌표로 투영한다 (좌표는 -1.0 ~ 1.0 범위로 처리되고, 화면에 표시될 vertex를 결정한다)
+) 원근 투영을 사용하는 경우 clip space 좌표에 투영하여 원근 투영을 추가할 수 있다
Screen Space : Viewport Transform 작업을 통해 clip 좌표를 screen 좌표로 변환한다
+)결과로 나온 좌표들을 rasterizer로 보내져 fragment로 변환된다
3. Local Space
- 사용자의 object에 대한 좌표 공간으로, object가 시작하는 공간
: 사용자가 생성한 모든 모델의 초기 위치는 (0, 0, 0)을 가진다
: 모델의 모든 vertex들은 local space에 있다 (모든 vertex는 object에 대해 local이다)
- 이전 장에서 사용했던 컨테이너의 vertex들은 0.0을 원점으로, -0.5 ~ 0.5 범위의 좌표로 지정했다
: 이 좌표들이 local 좌표이다
4. World Space
- 각 object들에 대한 위치인 world-space의 좌표를 정의하여 각 object들을 더 큰 world에 배치할 수 있도록 한다
: 모든 vertex들의 좌표는 world를 기준으로 한다
- model matirx를 사용하여 object의 coordinate를 local space에서 world space로 변환한다
- model matrix : translation, scale, rotation 등을 world 기준으로 object의 상대적 위치, 크기, 회전 등을 정의한 행렬
5. View Space
- 일반적으로 OpenGL에서 카메라를 나타낸다 (Camera Space나 Eye Space라고도 불린다)
- world space coordinate를 사용자의 시점 앞에 있는 coordinate로 변환했을 때의 결과이다
: 카메라의 관점에서 바라보는 공간
- scene을 이동/회전시키기 위해 이동/회전의 조합을 사용하여 수행되어 특정 item이 카메라 앞으로 변환된다
: 조합된 transformation은 view matrix에 저장되고 view coordinate은 world coordinate를 view space로 변환한다
6. Clip Space
- 각 vertex shader 실행의 마지막에서, OpenGL은 coordinate가 특정 범위 내에 있을 것으로 예상하고 이 범위 밖에 있는 coordinate는 모두 clipping된다
: clip된 좌표들은 삭제되고 남은 좌표들은 최종적으로 fragment가 되어 화면에 보이게 된다 (clip space이 name을 가져오는 위치)
- 모든 가시 좌표를 -1.0 ~ 1.0 범위 내에 지정하는 것은 직관적이지 않으므로, 작동할 자체 coordinate set를 지정하고 NDC로 다시 변환한다
- vertex coordinate를 ivew에서 clip space로 변환하기 위해 사용자는 좌표의 범위를 지정하는 project matrix를 정의해야한다
: projection matrix는 지정된 범위에 있는 coordinate를 NDC로 변환한다 (범위 밖의 좌표는 clip된다)
+) Ex) 삼각형의 일부만 clipping volume 밖에 있는 경우 OpenGL은 clipping 범위 내에 맞도록 하나 이상의 삼각형으로 재구성한다
- projection matrix가 생성하는 viewing box는 frustum(절두체)라고 불리고 이 frustum 내부에 있는 각 coordinate들은 사용자의 화면에 나타나게 된다
- 지정된 범위에서 NDC로 변환하는 전체적인 과정은 projection(투영)이라고도 불린다
- 모든 vertex들이 clip space로 변환되었다면 perspective division(투시 분할)이라고 불리는 마지막 작업이 수행된다
: 이 과정에서 위치 벡터의 x, y, z 요소들을 벡터의 w요소로 나눈다
: 4D clip space 좌표를 3D NDC로 변환하는 것이다 (vertex shader의 실행 마지막에 자동으로 수행된다)
- perspective division 단계 이후에는 결과 coordinate가 화면 coordinate에 mapping 되고 glViewport 설정을 사용하여 fragment로 변환된다
- View coordinate를 clip coordinate로 변환하는 projection matrix는 일반적으로 서로 다른 두 개의 형태를 취하고, 각 형태는 고유한 틀을 정의한다
: orthographic projection matrix(정사영 투영 행렬)이나 perspective projection matrix(원근 투영 행렬)을 만들 수 있다
7. Orthographic projection (정사영 투영)
- 정육면체와 같은 절두체 상자를 정의한다
: 상자 밖에 있는 vertex를 clip하는 clipping space를 정의한다
- orthographic projection matrix를 생성할 때 가시적인 절두체의 너비, 높이, 길이를 정의한다
: 변환이 완료된 후에 이 절두체 안에 있는 모든 coordinate들은 clip 되지 않는다
: 컨테이너와 유사하게 생겼다
- 절두체는 가시좌표를 정의하고, 폭/높이/근거리평면/원거리평면에 의해 구성된다
: 근거리 평면 앞의 좌표와 원거리 평면 뒤의 좌표는 clip된다
- 절두체 내부에 있는 모든 coordinate를 NDC로 직접 mapping한다
: 각 vector의 w요소를 건드리지 않는다
: w요소가 1.0 perspective 분할과 동일하게 유지되면 coordinate는 변경되지 않는다
glm::ortho(0.0f, 800.0f, 0.0f, 600.0f, 0.1f, 100.0f);
- orthographic projection matrix를 생성하기 위해 GLM의 glm::ortho function을 사용한다
- 1~2번째 parameter : 각각 절두체의 왼쪽, 오른쪽 좌표를 지정한다
- 3~4번째 parameter : 각각 절두체의 하단과 상단 좌표를 지정한다
- 5~6번째 parameter : 위의 4개 지점을 사용하여 근거리 평면과 원거리 평면의 크기를 지정한다
: 근거리 평면과 원거리 평면 사이의 거리를 정의한다 (지정된 projection matrix는 x, y, z 범위 값을 가진 모든 좌표를 NDC로 변환한다)
- orthographic projection matrix는 coordinate들을 화면의 2D 평면에 직접 mapping하지만 실제로 직접 투영은 원근법을 고려하지 않기 때문에 비현실적인 결과를 생성한다
: 이 문제는 perspective projection matrix가 해결한다
8. Perspective projection (원근 투영)
- projection matrix는 주어진 절두체를 clip된 공간에 mapping하고 각 vertex coordinate의 w값을 조정한다
: 시점으로부터 vertex coordinate가 멀어질수록 w요소가 증가한다
: coordinate가 clip space로 변환된 후에는, -w ~ w 범위 내에 존재한다
: -w ~ w 범위 외의 모든 것은 clip된다
- 가시 좌표가 최종 vertex shader 출력으로 NDC내에 있어야 하므로, coordinate가 clip space에 있으면 perspective division (원근 분할)이 clip space coordinate에 적용된다
- vertex coordinate의 각 요소들은 w요소로 나누어진다
: 시점에서 멀리 떨어진 vertex에는 작은 vertex coordinate가 할당된다
: result coordinate는 NDC범위 내에 있다
glm::mat4 proj = glm::perspective(glm::radians(45.0f), (float)width/(float)height, 0.1f, 100.0f);
- perspective projection matrix은 GLM에서 위 코드를 통해 생성할 수 있다
glm::perspective
- 눈에 보이는 공간을 정의하는 큰 절두체를 생성하는 역할
: 균일하지 않은 상자모양으로 시각화 될 수 있다
- 첫 번째 parameter
: fov (vield of view) 값을 지정한다 (view space의 크기 설정, 일반적으로 45도로 설정)
- 두 번째 parameter
: viewport의 너비를 높이로 나누어 계산되는 가로 세로 비율을 설정한다
- 세 번째 parameter
: 절두체에서 가까운 면 설정 (일반적으로 0.1로 설정)
- 네 번째 parameter
: 절두체에서 먼 면 설정 (일반적으로 100.0으로 설정)
=> 근 평면과 원 평면 사이에 있고 절두체 내부에 있는 모든 vertex는 render 된다
+) perspective matrix의 근 평면의 값이 10.0f와 같은 조금 큰 값으로 설정될 때마다 OpenGL은 카메라와 가까운 모든 좌표들(0.0f ~ 10.0f)을 모두 clip한다. 그러므로 만약 object에 가까이 다가가면 object를 뚫고 볼 수 있게 된다
9. Orthographic projection VS Perspective projection
- orthographic projection을 사용할 때 각 vertex coordinate는 원근 분할 없이 직접 clip space에 mapping된다
: 원근 분할은 여전히 수행되지만 w요소는 조작되지 않고 1로 유지된다
- orthographic projection은 perspective projection을 사용하지 않으므로 원근법이 적용되지 않는다
: 현실적이지 않은 시각적 출력을 생성한다
: 이런 특성으로 인해 orthographic projection은 주로 2D rendering과 원근법이 필요없는 일부 건축/엔지니어링 애플리케이션에 사용된다
: 3D modeling에 사용되는 blender와 같은 응용 프로그램은 각 객체의 치수를 더 정확히 묘사하기 때문에 modeling에 orthographic projection을 사용하기도 한다
- 위 그림을 통해 perspective projection(거리에 따라 vertex 크기 조정)과 orthographic projection(같은 거리에 있는 vertex)의 차이를 확인할 수 있다
10. Putting It All Together
- 앞서 언급한 각 단계에 필요한 transform matrix(model, view, projection)를 생성한다
- 이후 vertex coordinate를 위의 식을 사용하여 clip coordinate로 변환한다
: matrix의 곱셈 순서는 오른쪽에서 왼쪽으로 실행된다
- result vertex는 vertex shader의 gl_Position에 할당되고, 그 후 OpenGL은 perspective division과 clipping을 자동으로 수행한다
+) 이후 실행되는 Viewport transform : vertex shader의 출력에서 coordinate들은 clip-space 내부에 있어야한다. 또한 OpenGL은 NDC로의 변환을 위해 clip-space coordinate에서 perspective division을 수행한다. 이후 OpenGL은 glViewPort function을 이용하여 화면 coordinate에 NDC를 mapping한다(화면 coordinate에서는 각 coordinate가 800x600 화면의 지점에 mapping한다)
[GOING 3D]
1. Make Transform Matrix
glm::mat4 model = glm::mat4(1.0f);
model = glm::rotate(model, glm::radians(-55.0f), glm::vec3(1.0f, 0.0f, 0.0f));
- model matrix는 모든 객체의 vertex를 global space로 변환하기 위한 이동, 스케일, 회전으로 구성된다
- vertex coordinate를 model matrix와 곱하면 vertex coordinate를 world coordinate로 변환할 수 있다
: 평면은 global world에서 나타나진다
glm::rotate
- 첫 번째 parameter : 단위 행렬
- 두 번째 parameter : Rotate하고자 하는 크기
- 세 번째 parameter : 기준이 되는 축
glm::mat4 view = glm::mat4(1.0f);
// note that we're translating the scene in the reverse direction of where we want to move
view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f));
- view matrix는 scene 전체를 사용자가 카메라를 이동시키고자 하는 방향과 반대로 움직인다
: 카메라를 뒤로 이동하면 모든 scene은 앞으로 이동한다
glm::translate
- 첫 번째 parameter : 단위행렬
- 두 번째 parameter : translate 하고자하는 크기
- OpenGL은 오른손 좌표계이므로 z축의 +방향으로 이동하여야한다
+) 오른손 좌표계
- 각 축의 +방향이 x축에서는 오른쪽, y축에서는 위쪽, z축에서는 뒤쪽을 향하는 것이다
: 좌측의 그림은 오른손 좌표계를 시각화 한 것이다
- 왼손좌표계는 z축이 반대로 표시되며, 흔히 DirectX에서 사용된다
: NDC에서 OpenGL은 실제로 왼손 좌표계를 사용하지만, projection matrix가 이를 오른손 좌표계로 변경한다
glm::mat4 projection;
projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);
- projection matrix는 perspective projection을 사용하므로 위와 같이 선언한다
#version 330 core
layout (location = 0) in vec3 aPos;
...
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// note that we read the multiplication from right to left
gl_Position = projection * view * model * vec4(aPos, 1.0);
...
}
- 6.1.coordinate_systems.vs 생성
: vertex shader에 transform matrix를 uniform으로 선언하고 vertex coordinate에 곱한다
int modelLoc = glGetUniformLocation(ourShader.ID, "model");
glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
... // same for View Matrix and Projection Matrix
- transform matrix는 자주 변하기 때문에, 각 render loop가 돌 때마다 shader에 matrix를 전달한다
- 위의 과정이 문제 없이 실행 되었다면 사진과 같은 결과가 출력된다
2. More 3D
- 3D 정육면체를 render하기 위해서는 총 36개의 vertex가 필요하다
: 6개의 면 x 2개의 삼각형 x 3개의 vertex = 36 vertex
model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));
- 위 code를 사용하여 시간이 지남에 따라 정육면체를 회전시킬 수 있다
glDrawArrays(GL_TRIANGLES, 0, 36);
- 위 함수를 사용하여 36개의 vertex를 사용한다
glDrawArrays
- 첫 번째 parameter : render할 기본 형식의 종류를 지정 (GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP 등)
- 두 번째 parameter : 활성화된 array의 시작 index
- 세 번째 parameter : render할 index의 개수
- 정육면체가 조립되었지만 일부 면들이 다른 면의 위로 그려지고 있다
: 이는 OpenGL이 삼각형 단위로 그리기 때문에 발생하는 현상이다
- 위 문제를 해결하기 위해 z-buffer를 사용하여 OpenGL이 depth-testing을 할 수 있게 구성한다
3. Z - Buffer (Depth Buffer)
- OpenGL은 z-buffer에 모든 depth 정보를 저장한다
: GLFW는 출력이미지의 컬러를 저장하는 color buffer처럼 depth buffer를 자동으로 생성한다
: depth는 각 fragment의 z값으로 저장된다
- fragment가 색을 출력하려고 할 때마다 z-buffer와 각 depth 값을 비교한다
:현재 fragment가 다른 fragment의 뒤에 있다면 현재 fragment는 삭제 되고, 앞에 있다면 이를 덮어씌운다
: 이 프로세스를 Depth-testing이라고 칭하며, 이는 OpenGL에 의해 자동으로 수행된다 (하지만 기본적으로 depth testing은 사용하지 않도록 되어있어서, 이를 수행하려면 OpenGL에게 depth testing을 사용할 것이라고 알려주어야한다)
glEnable(GL_DEPTH_TEST);
- glEnable을 사용하여 depth testing을 사용할 수 있도록 한다
glEnable / glDisable
: OpenGL의 특정 기능에 대해서 사용 가능/불가능을 설정할 수 있다
: 한 번 설정된 가능 여부는 설정을 업데이트 하기 전까지 유지된다
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
- depth buffer를 사용하고 있으므로 loop가 돌 때마다 depth buffer를 비워주어야한다
: 이를 수행하지 않으면 이전 frame의 depth 정보가 그대로 buffer에 남아있게 된다
- color buffer를 지웠던 것과 마찬가지로 glClear를 사용하여 depth buffer를 비울 수 있다
- code가 문제 없이 실행되었다면 depth testing이 적용되어 정육면체가 시간이 지남에 따라 회전한다
4. More Cubes
- 화면에 10개의 정육면체를 출력한다고 가정했을때, 각 정육면체는 똑같이 생겼지만 world에서 다른 회전으로 다른 위치에 존재한다
- 정육면체의 graphic layout은 이미 정의되어 있으므로 더 많은 object를 render할 때 buffer나 attribute array를 수정할 필요가 없다
: 사용자는 각 object에 대해 정육면체를 world로 변환할 model matrix만 수정하면 된다
glm::vec3 cubePositions[] = {
glm::vec3( 0.0f, 0.0f, 0.0f),
glm::vec3( 2.0f, 5.0f, -15.0f),
glm::vec3(-1.5f, -2.2f, -2.5f),
glm::vec3(-3.8f, -2.0f, -12.3f),
glm::vec3( 2.4f, -0.4f, -3.5f),
glm::vec3(-1.7f, 3.0f, -7.5f),
glm::vec3( 1.3f, -2.0f, -2.5f),
glm::vec3( 1.5f, 2.0f, -2.5f),
glm::vec3( 1.5f, 0.2f, -1.5f),
glm::vec3(-1.3f, 1.0f, -1.5f)
};
- 각 정육면체에 대한 translation vector(이동 벡터)를 정의하여 world space에서 각 정육면체의 위치를 지정한다
: glm::vec3 array에 10개의 정육면체 위치를 정의한다
glBindVertexArray(VAO);
for(unsigned int i = 0; i < 10; i++)
{
glm::mat4 model = glm::mat4(1.0f);
model = glm::translate(model, cubePositions[i]);
float angle = 20.0f * i;
model = glm::rotate(model, glm::radians(angle), glm::vec3(1.0f, 0.3f, 0.5f));
ourShader.setMat4("model", model);
glDrawArrays(GL_TRIANGLES, 0, 36);
}
- glDrawArrays 함수를 10번 호출해야하므로 for문을 사용한다
- render할 때마다 다른 model 행렬을 vertex shader에게 보내야한다
: loop 안에서 이 작업을 수행할 작은 for loop를 만든다
: 각 정육면체에 약간의 회전도 추가하였다
=> 따라서 새로운 정육면체가 render 될 때마다 model matrix를 수정하고 이것을 10번 반복한다
- 모든 과정이 문제 없이 수행되었다면 위와 같은 결과가 출력된다
'OpenGL Study' 카테고리의 다른 글
OpenGL "Textures" (0) | 2023.01.06 |
---|---|
OpenGL "Camera" (0) | 2022.12.06 |
OpenGL "Transformations" (1) | 2022.11.22 |
OpenGL "Shaders" (1) | 2022.11.17 |
OpenGL "Hello Triangle" (0) | 2022.11.16 |