렌더링파이프라인 이해
렌더링이라는 것은 무엇인가?
렌더링 : 컴퓨터 그래픽스에서 3D 모델, 2D 이미지를 화면에 보여지는 과정 전체를 의미한다.
- 2D 렌더링 : 2D 이미지를 기반으로 색상, 위치 등의 속성을 통하여 화면에 표현하는 과정.
- 3D 렌더링 : 3D 장면을 기반하여 카메라를 통해 장면을 어떻게 볼 것인지 계산하고 광원, 재질 등의 속성을 통하여, 화면에 표현하는 과정.
게임은 2D 렌더링부터 시작한다. (스페이스워, 슈퍼마리오 브로스)
게임은 3D 렌더링에서 고도화된다. (울펜슈타인 3D, 버추어 파이터)
GPU의 가속을 받으면서 렌더링도 발달했다.
렌더링 파이프라인
그래픽데이터가 Modeling, Lighting. Shading 등의 핵심 과정 등을 거쳐 화면에 2차원 픽셀로 표시되는 과정
모델링 > 라이팅 > 셰이딩 > 최종 화면
모델링 : Max, Maya를 통해서 Export 된 결과물.
라이팅 : 빛에 해당되는 부분을 어떻게 할 것인지
셰이딩 : 빛에 대한 부분을 받아서 음영 처리를 어떻게 할 것인지, 컬러, 텍스쳐 모핑 과정 포함.
최종 화면 : 3D 공간으로 구성되고, 작업했다고 해도 최종적인 화면은 2D 화면으로 표기된다.
렌더링 파이프라인은 자동차 공정처럼 순차적인 진행이 필수적이다.
자동차를 만들기 위해서는 외관이 필요하다. 엔진을 만들어 붙이고, 완성되는 과정.
각 과정은 선행 과정이 완료되지 않으면 더 이상 진행될 수 없다.
렌더링 파이프라인 또한 그렇다. 데이터가 없는데 라이팅이나 쉐이딩을 할 수는 없다.
렌더링 파이프라인 기본 구조
- 초기 : Fixed Pipeline (DX9, OpenGL3 이전 구간) - 미리 정의된 함수 사용.
- 최근 : Programmable Pipeline - 셰이더 프로그램 이용.
Application > Geometry Processing > Rasterization > Pixel Processing
Application
어떠한 데이터를 쓸 것인지. 해당되는 데이터들이 Collision Detection을 통해 충돌이 되는지. (Culling이 무엇인지는 추후 추가 강의 예정.) 어느 것이 화면에 보이게 될 것인지. 삼각형을 그릴 것인지, 사각형을 그릴 것인지.
- Input Data
- Collision Detection
- Particular Culling Algorithms
- Rendering Primitive
Geometry Processing
배치를 하는 과정. 이동변환, 크기변환, 회전변환을 사용한다.
위치를 어느 곳에 그릴 것인지의 처리를 진행한다.
- Model & View Transform
- Vertex Shading
- Projection
- Clipping
3차원 공간을 화면에 어떻게 보이게 할 것인지의 모든 과정.
최종적으로 3D 화면을 2D화 시킨다.
Rasterization
알고리즘을 통해 어느 곳의 픽셀을 찍을지 결정.
- Scan Conversion
Pixel Processing
색을 입히는 과정
- Pixel Shading
- Merging
현대의 렌더링 파이프라인
기본 구조를 보면, 최근에 나오고 있는 렌더링 파이프라인 또한 과정 자체는 같다.
Metal Rendering Pipeline
Vertices > Vertex function > Rasterization > Fragment function > Pixels
Vulkan : Vertex/index buffer > Input assember > Vertex shader > Tessellation > Geometry shader > Rasterization > Fragment shader > Color blending > Framebuffer
면접에서 질문을 받게 되었다면, Vulkan을 기준으로 대답하는 것이 좋다.
렌더링 파이프라인과 공간
로컬 공간 (Local Space)
특정 객체가 지니고 있는 로컬 좌표계를 기준으로 생성된 공간이다.
- 3D 저작도구에서 작업하는 모델 데이터들은 로컬 좌표계를 기준으로 생성된다. (왼손 좌표계인지 오른손 좌표계인지 확인 필요) : 인간을 만들 때, 맥스나 마야의 좌표계는 월드좌표계이다. 유니티나 엔진의 경우 로컬 좌표계로 표기된다.
- 로컬공간의 원점은 객체의 중심점이 될 수도 있으며 (0, 0, 0)이 될 수도 있음.
- 배치와 변환의 용이성.
- 게임에서 로컬공간의 원점은 (0, 0, 0) 사용.
- 모델 데이터는 원점을 기준으로 작업 후 FBX로 내보냄
월드 공간 (World Space)
세계를 표현하기 위해 구성된 유일한 공간. 하나만 존재해야한다.
- 월드 좌표계를 통해 배치된 객체들은 유일한 좌표가 하나 존재한다.
- 월드 공간안에 배치된 모든 객체의 위치와 회전, 크기 변환 기준은 월드 좌표계 기반으로 동일하다.
- 게임상의 객체들은 월드 공간 상에서 움직이거나 상호작용이 이루어진다.
- 게임 프로그래밍에서 월드 공간에 배치되는 객체들의 범위 고려가 필요하다.
뷰 공간 (View Space, Camera Space)
월드 공간의 개체를 카메라의 관점으로 변환한 공간이다.
- 사용자의 시선을 기반으로 카메라의 위치와 방향이 결정된다.
- 카메라가 중심이 되는 공간이기 때문에 카메라 위치가 중요하다.
- 뷰포트를 통해서 화면에 보여질 크기를 결정한다.
카메라 없이는 무언가를 볼 수 없다. 카메라를 통해서 어디를 바라보게 될 지 결정된다.
뷰포트 (View Port)
카메라를 통해 출력할 화면의 크기와 위치 정보 : x, y, viewWidth, viewHeight.
- 뷰포트의 크기와 위치는 출력 화면에 어떻게 배치할지 결정한다.
- 일반적으로 뷰포트의 크기는 화면의 크기와 기준점에 맞춘다.
좌상단에 붙일지, 화면 끝에 붙일지, 중앙에 붙일지의 결정을 뷰포트에서 진행한다.
스크린 공간 (Screen Space)
- 사용자가 보게 되는 최종 2차원 화면을 결정하는 공간
- 2D일 경우에는 좌상단이 0, 0인 화면 좌표계를 대부분 사용한다.
- 3D일 경우에는 좌하단 혹은 좌상단 0, 0인 화면 좌표계를 사용한다.
- 화면 (x, y) 좌표는 화면의 픽셀의 최종 위치를 나타낸다.
- 일반 2D, DX는 좌상단, OpenGL 좌하단을 기준으로 구현하게 된다.
2D 렌더링 파이프라인과 공간
Local Space > (월드 변환) > World Space > (뷰 변환) > View Space > (화면 변환) > Screen Space
2D에서는 카메라의 z축이 없다.
- 모델 행렬 : 로컬 공간에서 월드 공간으로 변환하는 행렬 (M)
- 뷰 행렬 : 월드 공간에서 뷰 공간으로 변환하는 행렬 (V)
- 모델뷰 행렬 : 모델 행렬과 뷰 행렬을 결합한 행렬
화면 좌표계의 변환
스크린 공간으로 변환을 위해 좌상단이 (0, 0)인 화면 좌표계를 사용할 경우 뷰 공간의 좌표를 화면 좌표계로 변환이 필요하다.
뷰 공간의 좌표의 화면 공간으로의 변환 방법
- screen_x = viewCenterX + view_x
- screen_y = viewCenterY - view_y
2D 렌더링 파이프라인 실습
- 화면의 크기는 1280 * 720
- 월드 공간은 화면 정중앙인 0, 0인 직교 좌표계를 사용
- 카메라의 위치는 20, 20 뷰포트 (0, 0, 1280, 720)
- 사각형의 위치 값은 사각형의 중앙의 위치 값 의미
- 사각형의 width와 height : 100, 빨간색
- 사각형은 총 3개 : 각 위치 값 - (0, 0), (300, 350), (300, -300)
실습 진행
- 화면의 크기는 1280 * 720
- 월드공간은 화면 정중앙인 0,0인 직교 좌표계 사용
- 카메라의 위치는 20,20, 뷰포트 (0,0,1280,720)
- 사각형의 위치 값은 사각형의 중앙의 위치 값 의미
- 사각형의 width 와 height : 100, 색깔 빨간색
- 사각형은 총 3개 각 위치 값 : (0,0) , (300,350), (-300,-300)
import pygame
import numpy as np
#- input : surface, x1, y1, x2, y2, color
screenWidth = 1280
screenHeight = 720
cameraX = 20
cameraY = 20
# 좌표계 변경
def view_coordinates(x, y):
vertex_world = np.array([
(x),
(y),
(1)
])
view_matrix = np.array([
(1, 0, -cameraX),
(0, 1, -cameraY),
(0, 0, 1),
])
vertex_world = np.matmul(view_matrix, vertex_world)
return (vertex_world[0], vertex_world[1])
# 좌표계 변경
def screen_coordinates(x, y):
screen_x = round(screenWidth / 2 + x)
screen_y = round(screenHeight / 2 - y)
return (screen_x, screen_y)
def bresenham(surface, x1, y1, x2, y2, color):
x = round(x1)
y = round(y1)
dx = abs(x2 - x1)
dy = abs(y2 - y1)
sx = 1 if x1 < x2 else -1
sy = 1 if y1 < y2 else -1
if(dx >= dy):
P = (-2 * dy) + dx
for x in range(round(x1), round(x2 + 1)):
vX, vY = view_coordinates(x, y)
wX, wY = screen_coordinates(vX, vY)
surface.set_at((wX, wY), color)
if (P >= 0):
P += (-2 * dy)
else:
y += sy
P += (-2 * dy) + (2 * dx)
else:
P = (-2 * dx) + dy
for y in range(round(y1), round(y2 + 1)):
vX, vY = view_coordinates(x, y)
wX, wY = screen_coordinates(vX, vY)
surface.set_at((wX, wY), color)
if (P >= 0):
P += (-2 * dx)
else:
x += sx
P += (-2 * dx) + (2 * dy)
return
def square(surface, vertices, color1, color2):
squarePoints = [(vertices[0][0], vertices[0][1]), (vertices[1][0], vertices[1][1]), (vertices[2][0], vertices[2][1]), (vertices[3][0], vertices[3][1])]
scanline(surface, squarePoints, color1)
bresenham(surface, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1], color2)
bresenham(surface, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1], color2)
bresenham(surface, vertices[2][0], vertices[2][1], vertices[3][0], vertices[3][1], color2)
bresenham(surface, vertices[3][0], vertices[3][1], vertices[0][0], vertices[0][1], color2)
return
def scanline(surface, vertices, color):
vertices.sort(key = lambda x : x[1])
start = vertices[0]
end = vertices[3]
for y in range(start[1], end[1] + 1):
rightX = end[0]
leftX = start[0]
bresenham(surface, leftX, y, rightX, y, color)
return
pygame.init()
BACKGROUND_COLOR = (217, 217, 217)
LINE_COLOR = (200, 200, 200)
LINE_COLOR2 = (100, 100, 100)
COLOR_RED = (255, 0, 0)
screen = pygame.display.set_mode((screenWidth, screenHeight))
pygame.display.set_caption("Graphics Programming")
running = True;
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
key_input = pygame.key.get_pressed()
if key_input[pygame.K_UP]:
cameraY = cameraY + 10
# print("up")
if key_input[pygame.K_LEFT]:
cameraX = cameraX + 10
# print("left")
if key_input[pygame.K_DOWN]:
cameraY = cameraY - 10
# print("down")
if key_input[pygame.K_RIGHT]:
cameraX = cameraX - 10
# print("right")
screen.fill(BACKGROUND_COLOR)
for x in range (0, 720, 10):
pygame.draw.line(screen, LINE_COLOR , view_coordinates(0, x), view_coordinates(1280, x), 1)
for y in range (0, 1280, 10):
pygame.draw.line(screen, LINE_COLOR , view_coordinates(y, 0), view_coordinates(y, 720), 1)
pygame.draw.line(screen, LINE_COLOR2 , (0, 360), (1280, 360), 2)
pygame.draw.line(screen, LINE_COLOR2 , (640, 0), (640, 720), 2)
rectanglePoints1 = ((-50, -50),(-50, 50), (50, 50), (50, -50))
rectanglePoints2 = ((250, 300), (250, 400), (350, 400), (350, 300))
rectanglePoints3 = ((-350, 250), (-350, 350), (-250, 350), (-250, 250))
square(screen, rectanglePoints1, COLOR_RED, BACKGROUND_COLOR)
square(screen, rectanglePoints2, COLOR_RED, BACKGROUND_COLOR)
square(screen, rectanglePoints3, COLOR_RED, BACKGROUND_COLOR)d
pygame.display.flip()
pygame.quit()
3D 렌더링 파이프라인과 공간
Local Space > Model Matrix > World Space > View Matrix > View Space > Projection Matrix > Clip Space > Screen Space
원근감을 주는 형태로 갈 것인지.
3차원 공간에서의 월드 변환을 위한 이동, 회전, 크기 변환
- 3차원 공간의 변환 처리를 위해서 2차원 보다 1차원 증가
- 2차원 x, y 좌표계에서 z축을 추가해서 3차원 좌표계 추가
- 오른손 좌표계 이용
3차원 공간에서의 이동 변환
- 동차좌표계를 이용해서 2차원 공간에서는 3x3 행렬로 표현
- 2D : 동차좌표계를 이용해서 점 P(x, y, 1)를 이동 변환한 점 T(x+e, y+f, 1)
- 3D : 동차좌표계를 이용해서 3차원 공간에서는 4x4 행렬로 표현
3차원 공간에서의 크기 변환
- 동차좌표계를 이용해서 2차원 공간에서는 3x3 행렬로 표현
- 2D : 동차좌표계를 이용해서 점 P(x, y, 1)를 이동 변환한 점 T(kxx, kyy, 1)
- 3D : 동차좌표계를 이용해서 3차원 공간에서는 4x4 행렬로 표현
3차원 공간에서의 회전 변환
- 회전변환은 형태와 크기를 변경하지 않은 위치와 방향만 변경
- x, y, z 축은 직교하므로 회전변환 후 크기 변경 없이 직교 유지
- 회전변환 후의 새로운 기저벡터를 구한 후 기저벡터를 열벡터로 회전 변환 행렬을 구현
- 3개의 기저 벡터 값을 매번 계산하기 어렵다.
- 회전 중심축과 회전 량인 각도로 표현하는 방식을 고려
-> x, y, z축을 회전 중심축으로 각 축의 회전 각도로 표현
-> 오일러각 : 3차원 공간의 회전을 세 각도로 표현한 방식
-> 게임 엔진 혹은 3D 소프트웨어마다 x, y, z축 방향이 달라 호환 문제가 발생한다.
오일러각을 통한 회전 변환 표현 : (Yaw, Pitch, Roll)
- Yaw : 좌우의 회전 움직임
- Pitch : 위아래로의 회전 움직임
- Roll : 앞 뒤축을 기준으로 시계방향, 반시계방향으로 회전 움직임
오일러각을 이용한 회전 행렬 : 3차원 공간의 변환을 표현하기 위해서는 행렬 사용이 필수적이다.
x축 회전
yz 평면에서의 회전
- yz평면의 y축과 z축의 기저벡터를 표현하면 x성분은 0
- x, y, z 좌표계로 기저벡터 변환
- x축 기저벡터 : (1, 0, 0))
- y축 기저벡터 : (0, cos, sin))
- z축 기저벡터 : (0, -sin, cos))
y축 회전
zx 평면에서의 회전
- zx 평면의 z축과 x축의 기저벡터를 표현하면 y 성분은 0
- x, y, z 좌표계로 기저벡터 변환
- x축 기저벡터 : (cos, 0, -sin))
- y축 기저벡터 : (0, 1, 0))
- z축 기저벡터 : (sin, 0, cos))
z축 회전
xy 평면에서의 회전
- xy 평면의 x축과 y축의 기저벡터를 표현하면 z 성분은 0
- x, y, z 좌표계로 기저벡터 변환
- x축 기저벡터 : (cos, sin, 0))
- y축 기저벡터 : (-sin, 0, cos))
- z축 기저벡터 : (0, 0, 1))
xyz축의 오일러각의 회전 변환
- 일반적으로 많이 사용되는 회전 순서 : Roll > Pitch > Yaw
- 현재 3D 좌표계에서 Roll : z축, Pitch : x축, Yaw : y축
- z축 회전 > x축 회전 > y축 회전
𝑿𝒍𝒐𝒄𝒂𝒍𝑿 𝒀𝒍𝒐𝒄𝒂𝒍𝑿 𝒁𝒍𝒐𝒄𝒂𝒍𝑿 0
𝑿𝒍𝒐𝒄𝒂𝒍𝒀 𝒀𝒍𝒐𝒄𝒂𝒍𝒀 𝒁𝒍𝒐𝒄𝒂𝒍𝒀 0
𝑿𝒍𝒐𝒄𝒂𝒍𝒁 𝒀𝒍𝒐𝒄𝒂𝒍𝒁 𝒁𝒍𝒐𝒄𝒂𝒍𝒁 0
0 0 0 1
- 동차좌표계를 이용해서 3차원 공간에서는 4x4 행렬로 표현
- 3D 모델행렬 : Model Matrix = T * R * S
𝑋𝑙𝑜𝑐𝑎𝑙𝑋𝑘𝑥 𝑌𝑙𝑜𝑐𝑎𝑙𝑋𝑘𝒚 𝑍𝑙𝑜𝑐𝑎𝑙𝑋𝑘𝒚 tx
𝑋𝑙𝑜𝑐𝑎𝑙𝑌𝑘𝑥 𝑌𝑙𝑜𝑐𝑎𝑙𝑌𝑘𝒚 𝑍𝑙𝑜𝑐𝑎𝑙𝑌𝑘𝒚 ty
𝑋𝑙𝑜𝑐𝑎𝑙𝑍𝑘𝑥 𝑌𝑙𝑜𝑐𝑎𝑙𝑍𝑘𝒚 𝑍𝑙𝑜𝑐𝑎𝑙𝑍𝑘𝒚 tz
0 0 0 1
3D 카메라와 뷰공간
3차원 공간에서의 월드 공간에서 뷰 공간 이동을 위한 뷰 변환
- 뷰 변환을 위해서는 카메라를 기반한 뷰 행렬 필수
- 카메라가 물체를 바라보도록 하면 뷰 공간 x축이 왼쪽 방향
- 뷰 공간 x축 방향을 오른쪽으로 향하도록 y축으로 π회전 필요
- 뷰 변환은 카메라의 회전, 이동으로만 구성되며 크기 변환 의미 없음
- 카메라는 -z축을 바라보게 되며, 객체들의 z값은 -값을 가짐
카메라의 이동 행렬 T
1 0 0 tx
0 1 0 ty
0 0 1 tz
0 0 0 1
카메라의 회전 행렬 R
𝑋𝑙𝑜𝑐𝑎𝑙𝑋 𝑌𝑙𝑜𝑐𝑎𝑙𝑋 𝑍𝑙𝑜𝑐𝑎𝑙𝑋 0
𝑋𝑙𝑜𝑐𝑎𝑙𝑌 𝑌𝑙𝑜𝑐𝑎𝑙𝑌 𝑍𝑙𝑜𝑐𝑎𝑙𝑌 0
𝑋𝑙𝑜𝑐𝑎𝑙𝑍 𝑌𝑙𝑜𝑐𝑎𝑙𝑍 𝑍𝑙𝑜𝑐𝑎𝑙𝑍 0
0 0 0 1
뷰 행렬을 구하기 위해서는 카메라 행렬들의 역행렬 계산이 필요하다.
카메라가 y축을 기준으로 θ 회전하면 객체가 y축을 기준으로 -θ만큼 회전한 것으로 보인다.
이동 행렬 T의 역행렬 T-1은 (-tx, -ty, -tz)을 행렬화 시킨 것과 동일하다.
1 0 0 −tx
0 1 0 −ty
0 0 1 −tz
0 0 0 1
회전 행렬 R의 역행렬 R-1은 전치행렬 RT와 동일하다.
𝑋𝑙𝑜𝑐𝑎𝑙𝑋 𝑋𝑙𝑜𝑐𝑎𝑙𝑌 𝑋𝑙𝑜𝑐𝑎𝑙𝑍 0
𝑌𝑙𝑜𝑐𝑎𝑙𝑋 𝑌𝑙𝑜𝑐𝑎𝑙𝑌 𝑌𝑙𝑜𝑐𝑎𝑙𝑧 0
𝑍𝑙𝑜𝑐𝑎𝑙𝑋 𝑍𝑙𝑜𝑐𝑎𝑙𝑌 𝑍𝑙𝑜𝑐𝑎𝑙𝑍 0
0 0 0 1
카메라 모델 행렬을 Mcam이라 하면 (이동, 회전 변환 순서)
Mcam = T⋅R
Mcam-1 = (T⋅R)-1 = R-1⋅T-1
𝑋𝑙𝑜𝑐𝑎𝑙𝑋 𝑋𝑙𝑜𝑐𝑎𝑙𝑌 𝑋𝑙𝑜𝑐𝑎𝑙𝑍 −(𝑋𝑙𝑜𝑐𝑎𝑙⋅𝑡)
𝑌𝑙𝑜𝑐𝑎𝑙𝑋 𝑌𝑙𝑜𝑐𝑎𝑙𝑌 𝑌𝑙𝑜𝑐𝑎𝑙𝑧 −(𝑌𝑙𝑜𝑐𝑎𝑙⋅𝑡)
𝑍𝑙𝑜𝑐𝑎𝑙𝑋 𝑍𝑙𝑜𝑐𝑎𝑙𝑌 𝑍𝑙𝑜𝑐𝑎𝑙𝑍 −(𝑍𝑙𝑜𝑐𝑎𝑙⋅𝑡)
0 0 0 1
최종 뷰 공간은 y축 기준으로 π회전해야 하므로 y축 회전행렬 계산
𝒄𝒐𝒔𝜽 0 𝒔𝒊𝒏𝜽 0
0 1 0 0
−𝒔𝒊𝒏𝜽 𝟎 𝒄𝒐𝒔𝜽 0
0 0 0 1
−𝟏 0 𝟎 0
0 1 0 0
𝟎 𝟎 −𝟏 0
0 0 0 1
Y축 회전행렬과 R-1⋅T-1곱셈
−𝑋𝑙𝑜𝑐𝑎𝑙𝑋 −𝑋𝑙𝑜𝑐𝑎𝑙𝑌 −𝑋𝑙𝑜𝑐𝑎𝑙𝑍 𝑋𝑙𝑜𝑐𝑎𝑙⋅ 𝑡
𝑌𝑙𝑜𝑐𝑎𝑙𝑋 𝑌𝑙𝑜𝑐𝑎𝑙𝑌 𝑌𝑙𝑜𝑐𝑎𝑙𝑧 −(𝑌𝑙𝑜𝑐𝑎𝑙⋅ 𝑡)
−𝑍𝑙𝑜𝑐𝑎𝑙𝑋 −𝑍𝑙𝑜𝑐𝑎𝑙𝑌 −𝑍𝑙𝑜𝑐𝑎𝑙𝑍 𝑍𝑙𝑜𝑐𝑎𝑙⋅ 𝑡
0 0 0 1
'대학생활 > 수업' 카테고리의 다른 글
게임프로그래밍고급 12주차 - Geometry Shader (0) | 2023.05.23 |
---|---|
게임기획크리틱 12주차 - 게임 스토리와 게임 시나리오 (0) | 2023.05.23 |
게임네트워크프로그래밍 11주차 - 키 입력 동기화 게임 실습 (0) | 2023.05.22 |
게임레벨디자인 10주차 - 엑셀 사용 기초2 (0) | 2023.05.18 |
게임음악작곡법 10주차 - Minor Key (0) | 2023.05.18 |