본문으로 건너뛰기

버그 제보

이상한 점을 알려 주시면 고칠게요. 지금 보고 있는 페이지와 브라우저 정보가 함께 담겨요.

페이지:

Computer Science

컴퓨터 그래픽스 렌더링 기초: 프레임버퍼에서 3D 래스터화까지

작성 2026. 10. 23.추가
목차14개 항목
출처와 참고

CrashCourse의 「Crash Course Computer Science」 강의 「Screens & 2D Graphics」, 「3D Graphics」 편을 바탕으로 재구성한 노트다.

렌더링이란

렌더링(rendering)은 그릴 대상의 기술(도형, 장면)을 화면의 픽셀 값으로 바꾸는 일이다. 2D에서는 선·사각형·글자를 픽셀로 칠하고, 3D에서는 공간의 물체를 카메라에서 본 2D 그림으로 만든다. 어느 쪽이든 마지막 결과는 메모리에 있는 픽셀 배열이고, 디스플레이는 그 배열을 비춘다.

화면에 그리는 두 방식

방식그리는 법메모리에 두는 것맞는 그림
벡터 스캔(vector scan)전자빔·펜을 도형의 윤곽을 따라 직접 움직인다「(50, 50)으로 이동, 밝기 100%, (100, 50)까지 선」 같은 그리기 명령 목록선으로 된 도면, 초기 게임
래스터 스캔(raster scan)왼쪽 위에서 오른쪽 아래로 한 줄씩 정해진 경로를 반복해 훑고, 점마다 밝기를 정한다화면 전체의 점(픽셀) 값사진, 채워진 면, 오늘날의 모든 화면

벡터 명령은 삼각형 하나를 수십 바이트로 담지만, 래스터는 점의 개수만큼 메모리가 든다. 메모리가 귀하던 시절에는 이 차이가 결정적이었다. 지금의 LCD·OLED는 모두 래스터 방식으로, 픽셀마다 빨강·초록·파랑 부분 픽셀의 밝기를 1초에 수십~수백 번 갱신한다. 같은 대비는 파일 형식에서도 벡터(SVG)와 래스터(PNG·JPEG)로 이어진다.

텍스트 모드: 글자로 화면을 채우던 방식

픽셀 대신 글자 격자를 메모리에 두는 방법도 있다. 80열 × 25행 화면이라면 글자 코드 2,000개만 저장하면 된다. PC의 텍스트 모드는 칸마다 글자 코드 1바이트와 전경·배경색 속성 1바이트를 두어 화면 전체가 4,000바이트다.

flowchart TB
  n1["화면 버퍼(RAM의 정해진 영역)에 글자 코드를 쓴다"] --> n2["문자 생성기(character generator)가 코드를 읽는다"]
  n2 --> n3["ROM에 든 글자별 점 패턴(dot matrix)을 꺼내"]
  n3 --> n4["해당 칸 위치에 래스터로 그린다"]

프로그램은 화면 버퍼의 값만 바꾸면 화면이 바뀐다. 그릴 수 있는 것은 글자 집합에 있는 모양뿐이라, 선·모서리 같은 반그래픽 문자를 더한 확장 문자 집합으로 상자와 표를 그렸다. 지금의 터미널 에뮬레이터도 화면을 같은 글자 격자 모델로 다룬다(셸이 명령을 실행하는 방식).

프레임버퍼

비트맵 디스플레이(bitmapped display) 는 메모리의 값 하나하나가 화면의 픽셀 하나에 대응한다. 화면용으로 떼어 둔 이 메모리 영역을 프레임버퍼(frame buffer) 라 하고, 그래픽 카드는 그 내용을 계속 디스플레이로 내보낸다. 별도 그래픽 카드는 자기 전용 고속 메모리(VRAM)에 프레임버퍼와 텍스처를 두고, 내장 GPU는 시스템 메모리를 나눠 쓴다.

화면픽셀당프레임버퍼 한 장
200 × 200 흑백1비트40,000비트 = 5KB
1920 × 10804바이트(RGBA)약 8.3MB
3840 × 21604바이트(RGBA)약 33MB

프레임버퍼가 생기면 어떤 그림이든 그릴 수 있다. 2D 그리기 함수는 결국 픽셀 값을 쓰는 반복문이다.

/* (x, y)에서 시작하는 w×h 사각형을 color로 채운다 */
void fill_rect(int x, int y, int w, int h, uint32_t color) {
    for (int row = y; row < y + h; row++)        /* 바깥 반복: 줄 */
        for (int col = x; col < x + w; col++)    /* 안쪽 반복: 줄 안의 픽셀 */
            framebuffer[row * WIDTH + col] = color;
}

실제로는 이런 함수를 직접 쓰지 않고 선·곡선·도형·글자 함수를 갖춘 그래픽 라이브러리를 쓴다. C에서 버퍼에 그리는 법과 직선 알고리즘은 C로 하는 2D 그래픽 기초에, 브라우저의 비트맵 그리기는 Canvas 2D API에 있다.

3D 렌더링: 래스터화 파이프라인

3D 장면을 픽셀로 바꾸는 표준 방법은 래스터화(rasterization) 다. 물체를 삼각형으로 쪼개고, 삼각형을 화면에 투영한 뒤, 삼각형이 덮는 픽셀을 칠한다.

flowchart TB
  n1["삼각형 메시 (x, y, z 정점들)"] --> n2["변환·투영<br/>3D 좌표를 2D 화면 좌표로"]
  n2 --> n3["후면 컬링<br/>뒤를 향한 삼각형을 버린다"]
  n3 --> n4["래스터화<br/>삼각형이 덮는 픽셀을 찾는다 (스캔라인)"]
  n4 --> n5["가림 처리<br/>더 가까운 것만 남긴다 (Z 버퍼)"]
  n5 --> n6["셰이딩·텍스처<br/>픽셀 색을 정한다"]
  n6 --> n7["프레임버퍼"]

투영과 와이어프레임

화면에는 z가 없으므로 3D 좌표 (x, y, z)를 2D 평면으로 눌러 펴야 한다. 이것이 3D 투영(3D projection) 이다. 평행선이 그대로 평행한 직교 투영과, 멀수록 작아져 평행선이 소실점으로 모이는 원근 투영이 있다. 행렬로 쓰는 법은 변환 행렬의 「투영」에 있다.

모든 꼭짓점을 2D로 옮긴 뒤 2D 선 그리기로 꼭짓점을 이으면 와이어프레임 렌더링(wireframe rendering) 이 된다. 정육면체 골조에 손전등을 비추면 벽에 생기는 그림자가 곧 투영이다.

왜 삼각형인가

복잡한 모양은 다각형(polygon)을 이어 붙인 메시(mesh) 로 표현하고, 그 다각형으로는 거의 항상 삼각형을 쓴다.

  • 세 점은 평면 하나를 유일하게 정한다. 점 넷 이상은 한 평면 위에 있다는 보장이 없고, 점 둘은 선만 정한다. 셋이 평면을 정하는 최소 개수다.
  • 삼각형은 항상 볼록하므로, 한 줄을 가로지르면 반드시 두 변과 만난다. 아래 스캔라인 채우기가 단순해진다.
  • 메시가 촘촘할수록 곡면이 매끄럽지만 폴리곤 수가 늘어 계산이 많아진다. 폴리곤이 너무 많으면 프레임레이트가 부드럽게 느껴지는 선 아래로 떨어지므로, 게임은 충실도와 폴리곤 수를 맞추고 멀리 있는 물체에는 단순화한 메시(LOD)를 쓴다.

스캔라인 채우기와 안티앨리어싱

스캔라인 렌더링(scanline rendering) 은 삼각형 하나를 픽셀로 채우는 고전 알고리즘이다.

  1. 세 꼭짓점의 y 최솟값과 최댓값을 찾고, 그 사이의 픽셀 줄만 본다.
  2. 줄마다 그 줄의 중심선이 삼각형의 두 변과 만나는 두 점을 구한다.
  3. 두 교점 사이의 픽셀을 칠한다.

GPU가 초당 채울 수 있는 픽셀 수를 필레이트(fill rate) 라 한다.

픽셀은 정사각형이라 비스듬한 변이 계단처럼 깨진다(계단 현상, jaggies). 안티앨리어싱(antialiasing) 은 변이 픽셀을 얼마나 덮는지에 따라 색의 농도를 달리해 경계를 부드럽게 한다. 완전히 안에 있는 픽셀은 온전한 색, 살짝 걸친 픽셀은 옅은 색이 된다. 화면의 글꼴과 아이콘도 모두 이렇게 그린다. GPU는 픽셀 안의 여러 표본점으로 덮인 비율을 재는 MSAA, 그린 결과에서 경계를 찾아 흐리는 FXAA 같은 후처리 방식을 쓴다.

가림 처리: 화가 알고리즘과 Z 버퍼

장면의 어떤 다각형은 다른 다각형 뒤에 가려진다(가림, occlusion). 앞에 있는 것만 보이게 하는 방법은 둘이다.

방법동작장점한계
화가 알고리즘(painter's algorithm)다각형을 먼 것부터 가까운 것 순으로 정렬한 뒤 그 순서로 칠한다. 화가가 배경부터 그리는 것과 같다단순하다. 반투명 물체를 섞어 그릴 때 지금도 쓴다정렬 비용이 들고, 서로 꿰뚫거나 순환해서 겹치는 다각형은 어떤 순서로도 맞게 그릴 수 없다
Z 버퍼(Z-buffering, 깊이 버퍼)픽셀마다 「지금까지 본 가장 가까운 거리」를 담는 행렬을 무한대로 초기화한다. 다각형을 채울 때 픽셀의 깊이가 기록값보다 가까울 때만 색과 깊이를 갱신한다정렬이 필요 없다. 픽셀 단위라 꿰뚫는 다각형도 맞게 그린다픽셀 수만큼 메모리가 든다. 깊이가 거의 같으면 Z 파이팅이 생긴다

GPU는 Z 버퍼를 하드웨어로 처리하고, 불투명한 물체는 이것으로 가린다.

Z 파이팅(Z-fighting) 은 두 면의 깊이가 같거나 거의 같을 때 생긴다. 부동소수점 반올림 오차와 그리는 순서에 따라 픽셀마다 이기는 면이 바뀌어, 두 면의 무늬가 지글거리며 번갈아 보인다. 겹친 면을 조금 띄우거나, 카메라의 near 평면을 너무 가깝게 잡지 않아 깊이 정밀도를 확보한다.

후면 컬링

삼각형에는 앞면과 뒷면이 있다. 캐릭터의 머리나 지형처럼 닫힌 물체는 바깥을 향한 면만 보인다. 그래서 카메라에서 볼 때 뒤를 향한 삼각형을 그리기 전에 버리는 후면 컬링(back-face culling) 으로 처리할 면을 대략 절반으로 줄인다. 앞뒤는 화면에 투영된 꼭짓점의 감기 순서(시계 방향·반시계 방향)로 판단한다.

버그로 카메라가 물체 안으로 들어가면 안쪽에서는 모든 면이 뒷면이라, 물체가 통째로 사라져 보인다.

셰이딩: 빛에 따른 밝기

모든 삼각형을 한 색으로 채우면 입체감이 없다. 3D 장면에서는 면이 빛을 받는 각도에 따라 밝기가 달라야 한다. 면이 향하는 방향을 면에 수직인 화살표로 나타낸 것이 법선(surface normal) 이다. 법선이 광원 쪽을 향할수록 밝고, 등지면 어둡다(밝기 ∝ 법선과 빛 방향 사이 각의 코사인).

셰이딩빛을 계산하는 곳결과
플랫 셰이딩(flat shading)삼각형마다 한 번면마다 한 색. 다각형 경계가 그대로 보인다
고러드 셰이딩(Gouraud shading)꼭짓점마다 계산하고 색을 면 안에서 보간한다매끄럽지만 면 가운데 맺히는 하이라이트를 놓친다
퐁 셰이딩(Phong shading)법선을 면 안에서 보간하고 픽셀마다 빛을 계산한다가장 매끄럽고 하이라이트가 정확하다. 계산이 가장 많다

오늘날 GPU에서는 픽셀마다 빛을 계산하는 방식이 기본이고, 그 계산을 프래그먼트 셰이더로 직접 쓴다(WebGL과 셰이더).

텍스처 매핑

그래픽스에서 텍스처(texture) 는 촉감이 아니라 표면의 겉모습이다. 텍스처 매핑(texture mapping) 은 다각형을 채우면서 픽셀마다 메모리에 있는 이미지(텍스처)에서 색을 찾아 쓴다. 그러려면 다각형의 각 꼭짓점이 텍스처의 어느 좌표(UV)에 대응하는지 정해 두고, 면 안의 픽셀은 그 좌표를 보간해 텍스처를 찾는다. 화면의 픽셀 하나가 텍스처의 여러 텍셀(texel)에 걸치면 해당 영역의 평균 색을 써야 하므로, 미리 줄여 둔 텍스처 묶음(밉맵, mipmap)과 필터링으로 처리한다.

왜 GPU인가

장면 하나는 수백만 개의 다각형이고, 다각형마다 같은 종류의 계산(투영, 스캔라인 채우기, 깊이 비교, 셰이딩, 텍스처 조회)을 되풀이한다. 다각형과 픽셀은 서로 거의 독립적이라 나눠서 동시에 처리할 수 있다.

  • CPU는 적은 수의 강력한 코어로 복잡한 순차 작업을 빨리 하도록 설계되어 이런 일에 비효율적이다.
  • GPU(Graphics Processing Unit) 는 단순한 코어 수천 개로 같은 계산을 대량으로 병렬 실행하고, 래스터화·깊이 검사·텍스처 필터링은 전용 고정 기능 하드웨어로 한다.
  • 메시와 텍스처는 그래픽 카드의 VRAM에 두어 많은 코어가 동시에 빠르게 읽는다.
  • 같은 대량 병렬 구조가 그래픽 밖의 계산(머신러닝 학습·추론, 과학 계산)에도 쓰인다.

빛의 경로를 픽셀에서 거꾸로 추적하는 레이 트레이싱(ray tracing) 은 래스터화와 다른 렌더링 방식이다. 그림자·반사·굴절이 정확하지만 계산이 훨씬 많다. 실시간 게임은 래스터화를 기본으로 하고 반사·그림자 같은 일부 효과에만 GPU의 레이 트레이싱 가속을 섞어 쓴다.

정리

  • 화면은 메모리를 비춘다. 래스터 디스플레이는 프레임버퍼의 픽셀 배열을, 텍스트 모드는 글자 코드 격자를 비춘다.
  • 3D 래스터화는 투영 → 컬링 → 스캔라인 채우기 → Z 버퍼 가림 처리 → 셰이딩·텍스처 순으로 삼각형을 픽셀로 만든다.
  • 삼각형을 쓰는 이유는 세 점이 평면 하나를 유일하게 정하고 항상 볼록하기 때문이다.
  • Z 버퍼는 정렬 없이 픽셀마다 가장 가까운 깊이만 남기고, 깊이가 같으면 Z 파이팅이 생긴다.
  • 같은 계산을 수백만 번 독립적으로 되풀이하는 일이라 GPU의 대량 병렬 처리가 맞는다.

관련 노트: 변환 행렬: 이동·크기·회전과 동차 좌표, WebGL과 셰이더: 브라우저가 GPU로 그리는 방식, Three.js로 웹에서 3D 그리기, C로 하는 2D 그래픽 기초, Canvas 2D API, 베지어 곡선, 그래픽 사용자 인터페이스

원본 기록2

댓글

GitHub 계정으로 댓글을 남길 수 있어요.