최초의 삼각형

132025년 08월 09일4

몇 달의 시간이 쏜살같이 흘렀다. 구글의 Dawn 프로젝트 연구실은 초기의 활기 대신 끈질긴 인내의 공기로 채워져 있었다. 화이트보드 위의 완벽한 이상은, 코드라는 현실의 벽 앞에서 수없이 깨지고 다시 조립되기를 반복했다. 수만 줄의 C++ 코드가 쌓여갔고, 셀 수 없이 많은 유닛 테스트가 통과되었다. 하지만 팀원들의 마음 한구석에는 채워지지 않는 갈증이 있었다.

그들은 아직 자신들의 창조물이 살아 움직이는 것을 보지 못했다.

마침내, 그날이 왔다. 드미트리는 아침 회의에서 단호하게 선언했다.
“오늘, 우리는 첫 번째 삼각형을 그립니다.”

그의 말에 팀원들의 눈빛이 달라졌다. 이것은 또 하나의 단위 테스트가 아니었다. 지금까지 설계하고 구현한 모든 구성 요소—Adapter, Device, Shader, Pipeline, Command Queue—를 하나의 흐름으로 엮어, 실제로 화면에 무언가를 그려내는 최초의 완전한 통합 테스트였다. 만약 성공한다면, 그것은 Dawn이 비로소 생명을 얻었음을 의미하는 신호탄이었다.

벤이 메인 테스트용 컴퓨터 앞에 앉았다. 그의 등 뒤로 팀원들이 숨을 죽인 채 모여들었다.

첫 번째 단계는 심장, 즉 셰이더를 작성하는 것이었다. 벤은 WGSL로 가장 단순한 형태의 버텍스 셰이더와 프래그먼트 셰이더를 작성했다.

// Vertex Shader
@vertex
fn main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
    var pos = array<vec2<f32>, 3>(
        vec2<f32>(0.0, 0.5),
        vec2<f32>(-0.5, -0.5),
        vec2<f32>(0.5, -0.5)
    );
    return vec4<f32>(pos[in_vertex_index], 0.0, 1.0);
}

// Fragment Shader
@fragment
fn main() -> @location(0) vec4<f32> {
    return vec4<f32>(1.0, 0.0, 0.0, 1.0); // 붉은색
}

정점 3개의 위치를 하드코딩하고, 모든 픽셀을 붉은색으로 칠하는, 그래픽스 프로그래밍의 ‘Hello, World’와도 같은 코드였다.

다음은 이 모든 것을 지휘할 자바스크립트 코드였다. 드미트리가 옆에서 차분하게 절차를 읊었다.

“우선, Adapter를 요청하고, 비동기적으로 Device를 얻어옵니다.”
벤의 손가락이 키보드 위를 빠르게 움직였다. navigator.gpu.requestAdapter()adapter.requestDevice(). 모든 것의 시작이었다.

“다음은 파이프라인. 셰이더 모듈을 만들고, 버텍스 버퍼의 레이아웃을 정의하고, 최종적으로 렌더 파이프라인을 생성합니다.”
createShaderModule, createRenderPipelineAsync. 수많은 논쟁 끝에 탄생한 함수들이 하나씩 호출되었다. 이 과정이야말로 WebGPU의 핵심 철학이 담긴, 가장 까다로우면서도 중요한 관문이었다.

마침내 모든 준비가 끝났다. 렌더링 명령을 기록할 시간이 왔다.
“Command Encoder를 생성하고, Render Pass를 시작합니다.”
“파이프라인을 설정하고, draw를 호출합니다. 정점은 3개.”
“Encoder를 finish해서 Command Buffer를 만들고, 마지막으로… Queue에 submit합니다.”

queue.submit([commandBuffer]);

마지막 세미콜론이 찍히는 순간, 연구실에는 정적이 흘렀다. CPU가 GPU에게 보내는 첫 번째 공식적인 작업 지시서가 발송된 것이다.

벤은 심호흡 한번 하고, 실행(Run) 버튼을 눌렀다.
팀원 모두가 모니터에 시선을 고정했다. 1초, 2초…

화면은 텅 비어 있었다. 칠흑 같은 검은색뿐.
아무것도 뜨지 않았다.

“젠장…”
누군가의 입에서 나직한 탄식이 흘러나왔다. 실패인가.

하지만 드미트리는 침착했다.
“크래시가 나지 않은 게 어딥니까. 이건 에러가 아니라, 논리적인 문제일 겁니다.”

팀은 즉시 디버깅에 돌입했다. 셰이더 코드를 다시 보고, 파이프라인 설정을 점검했다. 좌표계가 잘못되었나? 뎁스 테스트가 켜져 있나? 수십 개의 가설이 오갔다.

그때, 카이가 조용히 화면을 가리켰다.
“잠깐만. 버텍스 셰이더의 정점 순서를 봐. (0, 0.5), (-0.5, -0.5), (0.5, -0.5). 이거… 시계 방향(Clockwise) 아닌가?”

순간 모두의 머릿속에 섬광이 스쳤다. 그래픽스에서는 기본적으로 반시계 방향(Counter-clockwise)으로 정의된 삼각형의 앞면만 그리는 ‘후면 컬링(Back-face culling)’이 기본값으로 켜져 있는 경우가 많았다. 그들이 정의한 삼각형은 뒷면으로 판정되어 그려지지 않았을 가능성이 높았다.

벤은 즉시 두 번째와 세 번째 정점의 순서를 바꿨다. 반시계 방향으로.
다시 실행 버튼을 눌렀다.

이번에는 달랐다.
모니터 중앙에, 약속이라도 한 듯 선명한 붉은색 삼각형 하나가 나타났다.

“……됐다!”

가장 먼저 터져 나온 것은 환호성이 아니었다. 안도의 한숨이었다. 이내 연구실은 박수와 나지막한 환호로 가득 찼다. 누군가는 옆 동료와 하이파이브를 했고, 누군가는 안경을 고쳐 쓰며 미소를 지었다.

드미트리는 그저 모니터 위의 작은 삼각형을 묵묵히 바라보고 있었다.
저것은 단순한 도형이 아니었다.
W3C의 수많은 논쟁, 화이트보드 위의 복잡한 다이어그램, 그리고 지난 몇 달간 팀원들이 쏟아부은 수만 줄의 코드. 그 모든 것이 저 작은 삼각형 안에 하나의 교향곡처럼 완벽하게 응축되어 있었다.

Dawn은 마침내 첫 울음을 터뜨렸다. 비록 작은 삼각형에 불과했지만, 그것은 웹 그래픽의 새로운 새벽이 실제로 밝았음을 알리는, 세상에서 가장 아름다운 신호였다.