티스토리 뷰

Book

OPENGL

BaeMinCheon 2018. 7. 6. 15:10

OPENGL

본 포스팅은 Sanjay Madhav 가 집필한 Game Programming in C++ (2018) 의 내용을 정리한 글입니다
그 중에서도 5장 OPENGL 의 내용을 다룹니다


Overview

본 장에서는 아래와 같은 내용을 배울 수 있습니다

  • Initializing OpenGL
    • OpenGL을 사용하기 위해 초기화하는 방법
  • Triangle Basics
    • 3D 그래픽스 구현을 위한 폴리곤과 좌표계
  • Shaders
    • 화면의 각 픽셀에 별도의 연산을 수행하는 방법
  • Transformation Basics
    • 물체의 좌표계에서 확대 축소 회전 이동을 적용하는 방법
  • Matrices and Transformations
    • 물체의 좌표계에서 게임의 좌표계로 변경하는 방법
  • Texture Mapping
    • OpenGL에서 텍스처를 사용하는 방법

Initializing OpenGL

기존에 사용하던 SDL 라이브러리는 2D까지만 지원하기 때문에 3D 그래픽을 구현할 수 없다
따라서 구축해둔 프레임워크에서 렌더링 부분은 OpenGL로 대체하기로 한다

  • Profile Type
    • Core
      • 데스크탑 어플리케이션을 개발할 때 사용한다
    • Compatibility
      • 더 이상 지원하지않는 기능들을 이용해야할 때 사용한다
    • ES
      • 모바일 어플리케이션을 개발할 때 사용한다
  • openGL Extension Wrangler library GLEW
    • OpenGL을 사용함에 있어 작성해야하는 복잡한 코드들을 캡슐화한 라이브러리이다
    • GLEW 를 사용해 간접적으로 OpenGL을 초기화시킨다
  • Initialization Order
    • SDL_GL_SetAttribute() 의 매개변수로 원하는 설정값들을 넘긴다
      • 생성하기 전에 OpenGL Context가 어떤 속성을 가질지 결정한다
    • SDL_CreateWindow() 의 매개변수로 SDL_WINDOW_OPENGL 을 넘긴다
      • OpenGL을 사용하는 윈도우를 생성하도록 한다
    • SDL_GL_CreateContext() 의 매개변수로 윈도우를 넘긴다
      • 해당 윈도우에 대한 OpenGL Context를 생성한다
    • glewInit() 으로 OpenGL을 초기화한다
      • 현재 OpenGL Context가 사용할 수 있는 모든 기능들을 초기화한다

Triangle Basics

오늘날의 컴퓨터는 과거와 달리 2D 그래픽스뿐만 아니라 3D 그래픽스도 지원해야한다
3D 그래픽스를 어떤 방식으로 지원하는 지에 대해 알아본다

  • Blitting
    • 일정한 크기의 메모리를 Color Buffer 에 복사하는 기법
    • NES 시절에는 2D 게임들이 대부분이었기 때문에 이 기법으로도 충분했다
    • 하지만 이 기법으로는 3D 게임을 개발할 수 없다 → Polygon Rendering 채택
      ※ 오늘날의 그래픽스 하드웨어들은 Polygon Rendering 에 특화된 구조를 갖는다
      ※ 따라서 2D 그래픽스에 대해서도 Polygon Rendering 을 사용한다
  • Polygon Rendering
    • 3D 그래픽스를 구현하는 알고리듬들 중 하나이다
      • 각 꼭지점 정점 에 벡터를 갖는 다각형 폴리곤 을 이어붙여 물체를 구성
      • 다각형의 표면은 색 또는 텍스처 등으로 채워질 수 있다
        ※ 제일 단순한 다각형인 삼각형이 주로 사용된다
    • 필요 연산량이 비교적 적은 편이며 그래픽스 하드웨어 성능에 따라 유연하게 대처가능
      • 게임 등의 실시간 렌더링을 요구하는 분야에서 자주 사용된다
  • Normalized Device Coordinates NDC
    • OpenGL에서 제공하는 기본 좌표계이다
      • 오른손 좌표계로 각 축을 계산한다
      • 엄지 → X 축
      • 검지 → Y 축
      • 중지 → Z 축
    • 화면 정중앙을 원점으로 한다 → (0, 0, 0)
      • 오른쪽으로 이동 → +X
      • 위로 이동 → +Y
      • 뒤로 이동 → +Z
    • 위치지정에 정규화된 벡터값을 사용한다
      • 화면 좌상단 끝 → (-1, 1, ?)
      • 화면 우하단 끝 → (1, -1, ?)
        ※ ? 는 임의의 값이란 뜻이다
        ※ 정규화되었기 때문에 각 축마다 올 수 있는 값은 -1 ∽ 1
  • Vertex Buffer & Index Buffer
    • 대부분의 폴리곤들은 혼자서만 사용되지않고 여러 개가 묶여 사용된다
      • 따라서 각 물체마다 사용되는 폴리곤들을 배열로 묶어 저장한다
      • 이 경우 중복되는 정점들이 존재하기 때문에 메모리가 낭비된다
    • Vertex Buffer
      • 중복되지 않도록 정점들을 저장하는 배열
      • 이 정점들만으로는 폴리곤을 어떻게 생성해야할지는 알 수 없음
    • Index Buffer
      • Vertex Buffer 와 함께 사용되는 배열
      • 각 폴리곤이 Vertex Buffer 내 몇 번째 정점으로 생성되어야하는지 정의
    • Vertex Array Object
      • OpenGL은 Vertex Buffer + Index Buffer + Vertex Layout 를 객체단위로 관리
        ※ Vertex Layout 은 각 정점마다 어떤 자료형이 저장되는지 등을 정의
      • 그 객체가 바로 Vertex Array Object 이다

Shaders

오늘날의 그래픽스 하드웨어들은 쉐이더라는 GPU 제어 프로그램을 지원한다
쉐이더 언어인 GLSL로 쉐이더 프로그램을 작성해 화면의 각 픽셀에 대한 별도의 연산을 수행한다

  • Shader Programs
    • GPU는 CPU에 비해 매우 많은 연산유닛 ALU 들을 가진다
      • GPU의 각 ALU 마다 거의 하나의 픽셀을 담당할 수 있다
    • 병렬처리에 특화된 GPU를 그래픽스 연산에 활용하는 프로그램이다
      • 각 픽셀마다 출력해야할 RGB값에 대한 계산을 GPU가 담당
    • 각 픽셀마다 서로 다른 쉐이더 프로그램을 계산한 결과의 합이 할당된다
      • 원한다면 각 픽셀마다 특별한 효과를 넣을 수 있다
    • (기본적으로) 쉐이더 프로그램 총 개수는 모든 정점의 개수와 같다
    • 하나의 쉐이더 프로그램은 여러 개의 쉐이더들로 이루어진다
      • 그 종류로는 Vertex Shaders 또는 Fragment Shaders 등이 있다
  • Vertex Shaders
    • [filename].vert 로 정의되는 쉐이더 프로그램 구성요소
    • 정점 속성을 입력받고 출력 픽셀의 위치를 결정하는 역할을 수행
      • gl_Position 에 픽셀 위치를 대입하여 결정
    • 주로 여기에서 Object Space → World Space 변환을 수행
  • Fragment Shaders
    • [filename].frag 로 정의되는 쉐이더 프로그램 구성요소
    • 텍스처 등을 입력받고 픽셀의 RGB값을 결정하는 역할을 수행
      • Color Buffer 에 부합하는 출력 변수에 대입하여 결정
        ※ Color Buffer 의 원소가 RGBA라면 vec4 를 출력 변수로 설정
    • 주로 여기에서 특별한 시각적 효과를 가미한다
      • 별도의 라이팅 계산 등을 수행
  • Applying Shader Programs
    • 어떤 종류의 쉐이더를 생성할지 결정
      • [shader_id] = glCreateShader([shader_type]);
    • GLSL로 작성된 쉐이더 소스코드를 할당
      • glShaderSource([shader_id], 1, &[shader_file], nullptr);
    • 쉐이더 소스코드를 컴파일
      • glCompileShader([shader_id]);
    • 쉐이더 프로그램에 부착
      • glAttachShader([program_id], [shader_id]);
    • 쉐이더 프로그램에 부착된 쉐이더들을 링크
      • glLinkProgram([program_id]);
        ※ 쉐이더 프로그램에 사용할 쉐이더들을 모두 부착한 후에 수행할 것
    • 쉐이더 프로그램 활성화
      • glUseProgram([program_id]);
        ※ 매 프레임마다 쉐이더 프로그램을 활성화시켜줘야 한다
    • 현재 활성화된 쉐이더 프로그램을 기반으로 출력
      • glDrawElements([polygon_type], [index_buffer_element_size], [index_type], nullptr);

Transformation Basics

게임상의 개체들은 확대축소회전이동을 편리하게 하기 위해 각자만의 좌표계를 가진다
그리고 확대 축소 회전 이동 각 연산들로 개체들을 변형시킬 수 있다

  • Object Space or Model Space
    • 각 개체가 가지는 독립적인 좌표계를 말한다
    • NDC 와 유사한 구조를 갖는다
      • 개체의 중심을 원점 (0, 0, 0) 으로 결정한다
      • 오른손 좌표계에 따라 각 축의 값을 증감한다
      • 각 정점은 비정규화된 벡터값으로 표현된다
    • 화면의 원하는 위치에 출력하기 위해서는 World Space 로 변환되어야 한다
  • World Space
    • 화면에 출력되는 게임세계의 고유한 좌표계를 말한다
      • 게임세계 → 게임의 맵 또는 UI 등에 해당한다
    • NDC 와 유사한 구조를 갖는다
      • 세계에서 임의의 위치를 원점으로 결정한다
      • 오른손 좌표계에 따라 각 축의 값을 증감한다
      • 각 정점은 정규화된 벡터값으로 표현된다
    • 고유한 Vertex Array Object 를 사용해 Object Space 를 변환한다
  • Transformation
    • Translation
      • 개체가 이동되는 연산을 말한다
      • X 축으로 a 만큼, Y 축으로 b 만큼 이동할 경우
        (X', Y') = (X + a, Y + b)
      • 개체의 모든 정점에 대해 이 연산을 수행하여 이동시킬 수 있다
    • Scale
      • 개체가 확대 또는 축소되는 연산을 말한다
      • X 축으로 a 만큼, Y 축으로 b 만큼 확대할 경우
        (X', Y') = (a \cdot X, b \cdot Y)
      • 개체의 모든 정점에 대해 이 연산을 수행하여 확대시킬 수 있다
        ※ 축소의 경우 a 또는 b 가 0 ∽ 1 사이의 값이다
        ※ a 와 b 가 동일한 경우 Uniform Scale 이라 한다
    • Rotation
      • 개체가 회전되는 연산을 말한다
      • 반시계방향으로 \theta 만큼 회전할 경우
        • (X', Y') = (X \cdot cos(\theta) - Y \cdot sin(\theta), X \cdot sin(\theta) + Y \cdot cos(\theta))
      • 개체의 모든 정점에 대해 이 연산을 수행하여 회전시킬 수 있다
  • Combination
    • 위의 확대축소회전이동 연산들을 조합하여 한 번에 수행할 수 있다
    • 다만 연산 순서에 따라 다른 결과값이 나올 수 있음을 주의해야한다
      • 특히 이동 연산과 회전 연산은 교환 법칙 을 만족하지않는다
    • 의도한 대로 변형시키려면 Scale → Rotation → Translation 순서로 수행한다

Matrices and Transformations

개체들을 화면에 표현하기 위해서 화면의 좌표계로 변환할 필요가 있다
이 과정또한 일련의 행렬곱으로 표현될 수 있다

  • Matrix Multiplication
    • 위의 Combination 에서 언급되었던 순서를 행렬곱으로 표현할 수 있다
      • 변형 연산들의 행렬곱 결과를 변형 행렬 C 라 하자
    • 정점의 위치벡터 P 가 변형 행렬 C 의 앞에 오는지 뒤에 오는지에 따라
      • P 가 1 행 N 열 벡터가 될지, N 행 1 열 벡터가 될지 결정된다
  • Combination
    • Translation 연산은 P 보다 더 큰 차원을 필요로 한다
      • 행렬곱은 전자 행렬의 열 개수와 후자 행렬의 행 개수가 동일해야하므로
      • P 를 비롯한 다른 행렬들의 차원을 한 단계 늘린다 → W Component
    • 위에서 언급한 변형 행렬을 World Transform 행렬이라고 한다
      • Scale → Rotation → Translation 순서의 행렬곱으로 구해진다
  • Clip Space
    • World Transform 행렬과 P 의 곱으로 개체가 World Space 상에서
      • 어떤 위치에 존재하는지를 알 수 있게 되었다
    • 개체의 World Space 상 위치에서 화면의 픽셀 위치로 변경할 필요가 있다
      • 화면상의 위치들을 포함하는 좌표계를 Clip Space 라고 한다
      • W Component 를 가진다는 것이 NDC 와 Clip Space 의 차이점이다
    • World Space → Clip Space 변환을 수행하는 행렬을 View Projection 행렬이라 한다
      • 3D 그래픽스에서의 View Projection 행렬은 복잡한 구조를 가지지만
      • 이 장에서는 2D 그래픽스를 사용하므로 간단한 구조의 View Projection 을 사용한다
        ※ World Space 상의 하나의 단위가 Clip Space 상의 하나의 단위와 동일하기 때문
  • Applying to Shaders
    • 이전에 언급했던 것처럼 쉐이더가 정점에 대한 연산을 수행할 수 있다
      • 특히 Object Space → World Space → Clip Space 변환을 수행할 수 있다
    • 각 쉐이더 프로그램에 World Transform 및 View Projection 행렬을 복사하기보다
      • 모든 쉐이더 프로그램들이 공유할 수 있는 uniform 변수로 전달한다
    • World Transform 행렬은 게임 내 개체들이 움직임에 따라 변경될 수 있다
      • 따라서 화면 출력을 할 때마다 World Transform 행렬을 다시 구한다
    • 반대로 View Projection 행렬은 게임 초기화 이후 변경하지않는다

Texture Mapping

OpenGL에서는 텍스처를 사용하기 위해 별도의 좌표계를 사용한다
또한 텍스처를 사용함에 따라 쉐이더 프로그램 등을 변경하게 된다

  • Texture Coordinates or UV Coordinates
    • 텍스처를 사용하기 위해서는 각 정점에 추가적인 값을 저장해야한다
      • 해당 정점이 텍스처의 어느 부분에 해당하는지를 명시해야하기 때문
      • 따라서 Vertex Buffer 에 저장되는 정점의 크기가 증가한다
    • 각 정점이 텍스처의 어떤 위치에 해당하는지를 UV Coordinates 로 표현한다
      • 2차원 좌표계로서 텍스처의 좌하단 끝을 원점 (0, 0) 으로 한다
      • 오른쪽 방향은 +X 이고 위 방향은 +Y 에 해당한다
      • 정규화되었기 때문에 각 축은 0 ∽ 1 의 값을 가질 수 있다
    • 각 정점마다 추가로 가지게 된 UV Coordinates 상의 좌표를 Texel 이라 한다
      • 폴리곤마다 각 정점의 Texel 이 존재할 것이고
      • 텍스처상에서 그 Texel 들을 기반으로 가상의 폴리곤을 만들 수 있을 것이다
      • 텍스처의 폴리곤에 해당하는 텍스처의 일부분을 원본 폴리곤의 면에 삽입한다
  • Nearest Neighbor Filtering
    • 폴리곤의 면적이 Texel 로 구해진 텍스처의 면적보다 클 경우
      • 이로 인해 빈공간이 발생하고 이를 메꿔야할 것이다
    • Nearest Neighbor Filtering 기법은 다음과 같은 절차로 빈공간을 메꾼다
      • 삽입하려는 텍스처의 Texel 들을 확대한다
      • Texel 들의 좌표값이 확대됨에 따라 Texel 간 간격이 벌어진다
      • 벌어진 간극을 메꿀 Texel 로 가장 가까운 Texel 의 복사본을 할당한다
    • 가장 간단한 방식이지만 텍스처가 확대될수록 동일한 색이 반복되는 구간이 많아진다
      • 블럭 단위로 색이 나뉘어지는 현상을 볼 수 있게 된다 → 계단 현상
  • Bilinear Filtering
    • Nearest Neighbor Filtering 처럼 빈공간을 메꾸기 위해 사용한다
    • Bilinear Filtering 기법은 다음과 같은 절차로 빈공간을 메꾼다
      • 삽입하려는 텍스처의 Texel 들을 확대한다
      • Texel 들의 좌표값이 확대됨에 따라 Texel 간 간격이 벌어진다
      • 벌어진 간극을 메꿀 Texel 로 인접한 Texel 들의 평균값을 할당한다
    • 연산량이 비교적 많지만 텍스처가 확대되어도 자연스러운 색변화를 보여준다
      • 대신 텍스처가 뿌옇게 보이는 단점이 존재한다 → 블러 효과
  • Alpha Blending
    • Color Buffer 의 원소는 주로 RGBA 총 4차원의 벡터가 사용된다
      • 이 중 A는 Alpha Channel 로 주로 투명도를 저장하는 데에 사용된다
    • Alpha Blending 은 A값을 기반으로 투명도를 임의로 결정하는 기법이다
      • 이 기법은 일반적으로 다음과 같은 식을 사용한다
      • DstColor = SrcAlpha \cdot SrcColor + (1 - SrcAlpha) \cdot DstColor
    • 위의 식을 적용하면 임의의 픽셀에 대해
      • A값이 1.0 인 RGBA 가 기존 픽셀의 RGB 값을 무시하고 덮어쓴다
      • A값이 0.0 인 RGBA 가 기존 픽셀의 RGB 값을 변경하지 못한다

Exercise

예제 풀이는 https://github.com/BaeMinCheon/game-programming-in-cpp 의 commits 참고

'Book' 카테고리의 다른 글

ARTIFICIAL INTELLIGENCE  (0) 2018.07.04
VECTORS AND BASIC PHYSICS  (0) 2018.07.04
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함