WebGL 파이프라인: 데이터가 그래픽이 되기까지.

382025년 08월 21일4

‘최초의 삼각형’을 그려본 개발자들은 곧 공통적인 질문에 도달했다. "왜 이렇게 복잡한 단계를 거쳐야만 하는가?" 그 질문에 대한 답은 WebGL의 핵심 작동 방식, 바로 ‘렌더링 파이프라인(Rendering Pipeline)’에 있었다.

파이프라인은 데이터가 입력되어 그래픽으로 출력되기까지 거치는 일련의 고정된 처리 단계를 의미한다. 마치 자동차 공장의 조립 라인처럼, 각 단계는 자신의 역할에만 집중하고 결과를 다음 단계로 넘긴다. 개발자는 이 파이프라인의 특정 지점(셰이더)에 직접 개입할 수는 있지만, 파이프라인의 전체적인 흐름 자체를 바꿀 수는 없다.

WebGL 워킹 그룹의 엔지니어들은 개발자들이 이 파이프라인의 구조를 이해하는 것이 매우 중요하다고 생각했다. 그들은 블로그 포스트와 개발자 문서를 통해 이 복잡한 과정을 설명하기 시작했다.

1단계: 정점 데이터 준비 (자바스크립트 -> GPU)
모든 것은 개발자의 자바스크립트 코드에서 시작된다. 개발자는 3D 모델을 구성하는 정점들의 위치, 색상, 텍스처 좌표 등의 데이터를 TypedArray로 준비한다. 그리고 gl.bufferData API를 통해 이 데이터 덩어리를 CPU의 메모리에서 GPU의 메모리에 있는 버퍼(Buffer) 로 전송한다. 이것이 파이프라인에 원재료를 투입하는 첫 번째 과정이다.

2단계: 정점 셰이더 (Vertex Shader)
gl.drawArraysgl.drawElements 함수가 호출되면, GPU는 버퍼에 있는 정점 데이터를 하나씩 꺼내 정점 셰이더로 보낸다. 정점 셰이더는 이 각각의 정점에 대해 실행되며, 모델-뷰-투영 행렬 변환을 통해 3D 공간상의 점을 2D 화면상의 좌표(Clip Space)로 변환하는 핵심적인 역할을 수행한다. 이 단계에서 모델의 형태와 움직임이 결정된다.

3단계: 기본 도형 조립 (Primitive Assembly)
정점 셰이더가 계산을 마친 정점들은 이제 ‘기본 도형(Primitive)’으로 조립된다. gl.drawArrays의 첫 번째 인자(gl.POINTS, gl.LINES, gl.TRIANGLES 등)에 따라, 이 점들은 독립적인 점으로 남거나, 선으로 연결되거나, 대부분의 경우 삼각형으로 묶인다. 3D 그래픽의 모든 복잡한 표면은 사실 이 작은 삼각형들의 집합이다.

4단계: 래스터라이제이션 (Rasterization)
이제 3D 공간의 개념은 사라진다. 래스터라이저는 3단계에서 조립된 삼각형이 화면의 어떤 픽셀들을 덮고 있는지를 계산하는 과정이다. 이 단계의 결과물은 ‘프래그먼트(Fragment)’라고 불리는 픽셀 조각들의 집합이다. 프래그먼트는 최종 픽셀이 되기 전의 후보로, 색상뿐만 아니라 깊이 값과 같은 추가 정보를 담고 있다.

5단계: 프래그먼트 셰이더 (Fragment Shader)
래스터라이저가 생성한 각각의 프래그먼트는 프래그먼트 셰이더로 보내진다. 프래그먼트 셰이더는 해당 픽셀의 최종 색상을 결정하는 역할을 한다. 텍스처에서 색상을 가져오거나, 조명 계산을 통해 음영을 만들거나, 안개 효과를 추가하는 등의 모든 시각적 마법이 이 단계에서 일어난다.

6단계: 최종 테스트 및 프레임버퍼에 쓰기 (Per-Fragment Operations)
프래그먼트 셰이더가 색상 계산을 마쳤다고 해서 바로 화면에 그려지는 것은 아니다. 마지막으로 몇 가지 테스트를 통과해야 한다. 대표적인 것이 ‘깊이 테스트(Depth Test)’다. 만약 현재 픽셀에 이미 더 가까운 물체가 그려져 있다면, 새로 계산된 색상은 버려진다. 이 과정을 통해 물체들이 서로를 제대로 가릴 수 있게 된다. 이 모든 테스트를 통과한 최종 픽셀만이 비로소 화면을 나타내는 메모리 공간인 프레임버퍼(Framebuffer) 에 기록된다.

이 여섯 단계의 파이프라인이 초당 60번씩 반복되면서, 우리는 부드럽게 움직이는 3D 애니메이션을 볼 수 있게 되는 것이다.

이 파이프라인의 구조를 이해하자, 개발자들은 비로소 WebGL API의 수많은 함수들이 왜 필요했는지를 깨닫기 시작했다. gl.bufferData는 1단계를, gl.compileShader는 2단계와 5단계를, gl.vertexAttribPointer는 1단계와 2단계를 연결하는 역할을 했던 것이다.

복잡하고 불친절해 보였던 API는 사실 이 렌더링 파이프라인의 각 단계를 제어하기 위한, 지극히 논리적이고 체계적인 도구의 집합이었다. 이 파이프라인에 대한 이해는 초보 개발자와 중급 개발자를 가르는 중요한 분수령이 되었다.