사전 렌더 패스
개념

렌더 패스

gabury1고친 사람 github-actions[bot]

렌더 패스는 그리기 명령을 한 묶음으로 그래픽 카드에 넘깁니다. 묶음을 열 때 어느 이미지에 그릴지 먼저 알립니다. 닫을 때는 그 이미지를 남길지 버릴지도 정합니다. 그래픽 카드는 이 정보를 보고 메모리를 덜 오가도록 일을 짭니다. 게임 엔진에서는 그림자 그리기처럼 장면을 한 차례 그리는 단계를 부를 때도 이 이름을 씁니다.

쉽고 빠른 이해

그리기 명령을 「이 이미지에 여기부터 여기까지 그린다」는 묶음으로 넘기는 일입니다. 바닥 · 나무 · 사람을 그리는 명령 셋을 화면 이미지 하나에 그린다고 묶어 보내는 식입니다.

많은 그래픽 카드는 이미지를 작은 조각으로 나눠 칩 안의 가까운 메모리에서 그립니다. 묶음이 없으면 다음 명령이 어느 이미지를 쓸지 몰라 조각을 자주 내보내고 다시 불러오게 됩니다.

  1. 그릴 이미지와, 처음에 지울지 불러올지를 적어 묶음을 엽니다
  2. 그리기 명령을 넣습니다. 이 동안은 그릴 이미지를 못 바꿉니다
  3. 묶음을 닫으며 결과를 남길지 버릴지 정합니다

그 대신 다른 이미지에 그리려면 묶음을 닫고 새로 열어야 합니다. 여닫을 때마다 결과를 내보내고 다시 불러오는 비용이 붙습니다.

상세

그리기 명령을 여닫는 묶음

그래픽 카드에서 그리기를 맡는 칩이 GPU(Graphics Processing Unit, 그래픽 처리 장치)입니다. 프로그램은 GPU 에 직접 그리지 않습니다.

대신 그래픽스 API(Application Programming Interface)에 그리기 명령을 적어 넘깁니다. 그래픽스 API 는 프로그램이 GPU 에 일을 시키는 창구입니다.

렌더 패스는 이 명령들 사이에 여는 명령과 닫는 명령을 하나씩 둡니다. 여는 명령에는 어느 이미지에 그릴지 적습니다. 닫는 명령이 올 때까지 들어온 그리기 명령은 전부 그 이미지에 그려집니다.

여는 명령에 적는 이미지는 대개 둘입니다. 하나는 렌더 타깃입니다. 화면의 점 하나(픽셀)마다 색을 받는 이미지입니다. 아래에서 말하는 칸은 이 점 하나입니다. 화면에 보일 그림이 여기에 남습니다.

다른 하나는 깊이 버퍼입니다. 칸마다 그 점이 카메라에서 얼마나 먼지를 담습니다. 앞에 있는 물체가 뒤의 물체를 가리게 하려고 씁니다.

렌더 패스에 걸어 둔 이런 이미지가 어태치먼트입니다. 패스에 붙여 둔 이미지라는 뜻입니다. 패스가 열려 있는 동안 어태치먼트는 바뀌지 않습니다. 다른 이미지에 그리려면 지금 패스를 닫고 새 패스를 엽니다.

Vulkan · Metal · WebGPU 같은 그래픽스 API 는 모두 렌더 패스를 여는 명령과 닫는 명령을 따로 둡니다. 이름과 적는 법은 API 마다 다릅니다. 열 때 이미지를 걸고 닫을 때 결과를 정한다는 뼈대는 같습니다.

시작과 끝에 고르는 두 연산

렌더 패스는 열 때와 닫을 때 어태치먼트마다 연산을 하나씩 정합니다.

열 때 정하는 연산이 로드 연산(load operation)입니다. 패스를 열 때 어태치먼트에 이미 있던 내용을 어떻게 할지 정합니다. 고를 수 있는 값은 셋입니다.

값 뜻 고르는 때
clear 정해 둔 값으로 지우고 시작한다 새 장면을 처음부터 그릴 때
load 있던 내용을 불러와 그 위에 이어 그린다 앞 패스가 그린 그림 위에 덧그릴 때
don't care 있던 내용을 신경 쓰지 않는다 패스가 모든 칸을 새로 칠할 때

마지막 값을 고르면 패스를 연 직후 칸마다 무엇이 들어 있을지 알 수 없습니다. 그래서 모든 칸을 빠짐없이 새로 칠할 때만 고릅니다.

닫을 때 정하는 연산은 스토어 연산(store operation)입니다. 패스를 닫을 때 그린 결과를 어떻게 할지 정합니다. store 는 결과를 남깁니다. don't care 는 결과를 버려도 된다고 알립니다.

깊이 버퍼가 버리는 쪽의 흔한 예입니다. 깊이 값은 그리는 동안 앞뒤를 가리는 데만 씁니다. 장면을 다 그리고 나면 다시 읽을 일이 없을 때가 많습니다. 그럴 때 스토어 연산을 don't care 로 두면 깊이 값을 내보내지 않습니다.

두 연산을 한 패스에 적으면 아래처럼 됩니다. 어느 API 의 문법도 아니고 흐름만 보인 코드입니다.

begin_render_pass(
    color = 화면 이미지, load = clear, store = store,
    depth = 깊이 이미지, load = clear, store = dont_care
)
draw(바닥)
draw(나무)
draw(사람)
end_render_pass()

화면 이미지는 지우고 시작해 결과를 남깁니다. 깊이 이미지는 지우고 시작해 끝나면 버립니다. 그사이 세 번의 그리기는 전부 이 두 이미지에 그려집니다.

연산을 미리 정하는 까닭

로드와 스토어를 미리 정해 두면 왜 빨라지는지는 GPU 의 메모리 구조에서 나옵니다. 화면을 조각내 그리는 GPU 에서 이 차이가 크게 드러납니다.

GPU 는 그린 결과를 주 메모리에 둡니다. 주 메모리는 크지만 칩 바깥에 있어 오가는 데 시간이 듭니다. 칩 안에도 메모리가 있습니다. 작지만 칩 안에 있어 금방 닿습니다.

타일 기반 렌더링을 쓰는 GPU 는 화면을 작은 사각형 조각으로 나눠 그립니다. 이 조각이 타일입니다. 타일 하나를 칩 안 메모리에 올려 둡니다. 그 타일에 걸리는 그리기를 다 마치면 다음 타일로 넘어갑니다. 휴대폰의 GPU 가 대개 이 방식입니다.

이런 GPU 에서 로드 연산과 스토어 연산은 곧 주 메모리를 오가는 일입니다. load 는 타일을 그리기 전에 주 메모리에서 내용을 읽어 옵니다. store 는 타일을 다 그린 뒤 주 메모리에 써 냅니다.

flowchart TD
    subgraph MAIN["주 메모리 · 크고 멀다"]
        IMG["어태치먼트 이미지"]
    end
    subgraph CHIP["칩 안 메모리 · 작고 가깝다"]
        TILE["타일 하나"]
    end
    IMG -->|"load · 열 때 읽어 온다"| TILE
    TILE -->|"store · 닫을 때 써 낸다"| IMG

로드 쪽 clear 와 don't care 는 읽기 화살표를 없앱니다. clear 는 읽어 오지 않고 칩 안에서 바로 칠합니다. don't care 는 읽지도 칠하지도 않고 시작합니다.

스토어 쪽 don't care 는 쓰기 화살표를 없앱니다. 타일을 써 내지 않고 버립니다. 타일마다 아낀 왕복이 모이면 이미지 한 장을 읽고 쓰는 양이 줄어듭니다.

주방에서 요리를 시작하기 전에 조리대에 꺼낼 재료를 정합니다. 요리가 끝나면 남길 것만 냉장고에 넣습니다. 나머지는 버립니다. 이걸 미리 정해 두면 냉장고 문을 여닫는 횟수가 줄어듭니다.

냉장고는 주 메모리이고 조리대는 칩 안 메모리입니다. 재료를 꺼내는 일이 load 입니다. 냉장고에 넣는 일이 store 이고 버리는 일이 스토어 쪽 don't care 입니다.

드라이버는 API 의 명령을 GPU 가 알아듣는 일로 바꿔 주는 소프트웨어입니다. GPU 마다 알아듣는 명령이 달라서 이 번역이 필요합니다.

OpenGL 이나 Direct3D 11 처럼 렌더 패스가 없는 그래픽스 API 는 그리기 명령만 받습니다. 열 때와 닫을 때 연산을 정하는 틀이 없습니다. 이 연산을 적을 곳이 없으면 드라이버가 앞뒤 명령을 보고 짐작합니다. 짐작이 빗나가면 안 해도 될 읽기와 쓰기가 생깁니다.

화면 전체를 한 번에 다루는 GPU 에서는 이 차이가 작은 편입니다. 이런 GPU 는 처음부터 주 메모리의 이미지에 그립니다. 타일을 칩 안에 올렸다 내리는 단계가 따로 없어서 여닫을 때 드는 왕복이 원래 적습니다. 데스크톱용 GPU 가 대개 그렇습니다.

패스 경계에서 기다리는 일

한 장면은 대개 패스 여럿으로 그립니다. 앞 패스의 결과가 뒤 패스의 재료가 되면 지켜야 할 순서가 생깁니다. 그림자 그리기가 흔한 예입니다.

텍스처는 그리는 도중에 읽어 쓸 수 있는 이미지입니다. 물체 겉면에 입힐 무늬를 담기도 하고, 앞 패스가 그려 둔 결과를 뒤 패스에 넘기는 데도 씁니다.

그림자를 그리는 첫 패스는 빛이 있는 쪽에서 장면을 그려 깊이 버퍼만 남깁니다. 카메라 대신 빛에서 본 깊이 버퍼라서 칸마다 빛에서 얼마나 먼지가 담깁니다. 뒤 패스가 이 값을 읽어야 하니 스토어 연산을 store 로 두고 닫습니다. 앞에서 버리던 깊이 값을 이번에는 남기는 것입니다.

다음 패스는 눈에 보일 장면을 그리며 이 깊이 버퍼를 텍스처로 읽습니다. 빛에서 떨어진 거리가 텍스처에 적힌 값보다 먼 칸이 그림자 진 칸입니다.

GPU 는 받은 명령을 겹쳐서 처리합니다. 뒤 패스가 앞 패스의 결과를 읽으려면 앞 패스의 쓰기가 끝날 때까지 기다려야 합니다. 안 기다리면 반쯤 그려진 텍스처를 읽습니다.

이 기다림은 패스 경계에 겁니다. 패스가 닫히면 그 패스의 쓰기가 어디서 끝나는지 분명해집니다. 「앞의 쓰기가 끝난 뒤에 읽어라」는 표시를 배리어라고 부릅니다. Vulkan 에서는 프로그램이 배리어를 직접 적습니다. Metal 과 WebGPU 에서는 대개 API 가 알아서 넣습니다.

한 패스 안에서 지금 그리는 어태치먼트를 텍스처로 읽으면 어느 값이 나올지 정해지지 않습니다. 쓰는 중인 칸을 같은 때 읽기 때문입니다. 그래서 읽을 이미지는 앞 패스에서 다 그려 둡니다.

엔진이 부르는 패스

게임 엔진 코드에서는 같은 낱말을 한 단계 큰 뜻으로 씁니다. API 의 렌더 패스는 그 큰 단계를 이루는 부품이 됩니다.

엔진이 그림자 패스 · 조명 패스 · 후처리 패스라고 부르는 것은 장면을 한 차례 그리는 단계입니다. 후처리는 다 그린 그림 전체에 흐림이나 색 보정을 거는 단계입니다. 이런 단계 하나가 API 의 렌더 패스 하나로 끝나기도 하고 여럿으로 나뉘기도 합니다.

단계가 많아지면 어느 단계가 어느 이미지를 쓰고 읽는지 따지기가 번거로워집니다. 그래서 엔진은 단계와 이미지 사이의 관계를 그래프로 적어 둡니다. 그 그래프를 보고 순서와 기다림을 자동으로 정하기도 합니다. 이런 구조가 렌더 그래프입니다.

패스를 나누고 합치는 기준

그리기를 몇 개의 패스로 나눌지는 프로그램이 정합니다. 기준은 하나입니다. 결과를 주 메모리에 내보내야 다음 일을 할 수 있느냐입니다.

패스를 닫고 다음 패스에서 같은 이미지를 load 로 열면 그 이미지는 주 메모리를 한 번 왕복합니다. 타일 기반 GPU 에서는 이 왕복이 패스 수만큼 쌓입니다. 같은 이미지에 그릴 것은 한 패스에 모으는 편이 쌉니다.

반대로 꼭 나눠야 하는 때가 있습니다. 뒤 단계가 이웃 칸의 값까지 읽을 때입니다. 흐림 효과는 한 칸을 칠하려고 주변 칸 여럿을 읽습니다. 이웃 칸은 다른 타일에 있을 수 있어서 앞 단계의 결과가 주 메모리에 다 나가 있어야 합니다.

뒤 단계가 같은 칸만 읽으면 사정이 다릅니다. Vulkan 은 한 렌더 패스 안을 다시 여러 단계로 나눌 수 있게 합니다. 이렇게 나눈 단계가 서브패스입니다. 타일 기반 GPU 에서 뒤 서브패스는 앞 서브패스의 결과를 주 메모리를 거치지 않고 칩 안에서 바로 넘겨받습니다.

흔한 낭비도 연산을 고르는 데서 나옵니다. 처음부터 다시 그릴 이미지를 load 로 열면 쓰지도 않을 내용을 읽습니다. 다시 안 읽을 깊이 버퍼를 store 로 닫으면 버릴 값을 써 냅니다.

렌더 패스 안에는 그리기 명령과 그에 딸린 설정 명령을 둡니다. 버퍼를 복사하는 명령이나 그리기가 아닌 계산은 패스 밖에 두거나 계산용 패스에 따로 담습니다. WebGPU 와 Metal 은 계산용 패스를 컴퓨트 패스라고 부릅니다.

관련 항목

렌더 패스가 걸어 두는 이미지

어태치먼트 · 렌더 타깃 · 프레임버퍼 · 색 버퍼 · 깊이 버퍼 · 스텐실 버퍼 · 텍스처 · 이미지 뷰

렌더 패스가 열고 닫을 때 고르는 연산

로드 연산 · 스토어 연산 · 멀티샘플 리졸브 · 멀티샘플 안티에일리어싱

렌더 패스를 쪼개거나 대신하는 구조

서브패스 · 입력 어태치먼트 · 서브패스 의존성 · 타일 셰이딩 렌더 패스 · 다이내믹 렌더링

렌더 패스를 담아 GPU 로 보내는 부품

커맨드 버퍼 · 커맨드 인코더 · 커맨드 큐 · 드로우 콜 · 그래픽스 파이프라인 · 파이프라인 상태 객체 · 컴퓨트 패스

렌더 패스가 비용을 줄이려는 GPU 구조

GPU · 타일 기반 렌더링 · 타일 메모리 · 즉시 모드 렌더링 · 메모리 대역폭 · 디바이스 드라이버

렌더 패스 경계에서 맞추는 동기화

파이프라인 배리어 · 이미지 레이아웃 · 렌더 패스 데이터 레이스 · 피드백 루프

엔진이 패스 단위로 나눠 그리는 단계

그림자 매핑 · 지연 셰이딩 · 후처리 · G-버퍼 · 멀티패스 렌더링 · 렌더 그래프

렌더 패스를 여닫는 명령을 둔 그래픽스 API

Vulkan · Metal · WebGPU · Direct3D 12

렌더 패스가 속하는 상위 분류

그래픽스 · 렌더링 · 그래픽스 API · 실시간 렌더링

다른 이름: render pass · 렌더패스