WebGL(Web Graphics Library)은 브라우저에서 GPU를 직접 활용하는 저수준 JavaScript 그래픽 API다. HTML <canvas> 위에서 동작하며 OpenGL ES 2.0(WebGL1) / ES 3.0(WebGL2) 스펙을 따른다. 플러그인 없이 하드웨어 가속 2D·3D 렌더링을 제공한다.
렌더링 파이프라인
CPU (JavaScript)
↓ 정점 데이터 / 유니폼 / 텍스처 업로드
GPU 파이프라인
├─ Vertex Shader — 정점 좌표 변환 (클립 공간)
├─ Rasterization — 삼각형 → 픽셀 분해
├─ Fragment Shader — 픽셀 색상 결정
└─ Framebuffer — 최종 출력 (화면 or RenderTarget)
모든 렌더링은 삼각형 단위로 이루어진다. 복잡한 형태도 삼각형의 집합으로 표현한다.
컨텍스트 취득
javascript
const canvas = document.querySelector('canvas');
// WebGL 1 (OpenGL ES 2.0 기반)
const gl = canvas.getContext('webgl', {
antialias: true,
alpha: false,
depth: true,
stencil: false,
preserveDrawingBuffer: false, // true면 스크린샷 가능하나 성능 저하
});
// WebGL 2 (OpenGL ES 3.0 기반, 현재 모든 주요 브라우저 지원)
const gl = canvas.getContext('webgl2');
if (!gl) throw new Error('WebGL 미지원 환경');
셰이더 (GLSL)
GPU에서 실행되는 프로그램. 반드시 Vertex + Fragment 쌍으로 링크해야 한다.
glsl
// Vertex Shader — 정점마다 실행
attribute vec3 a_position; // 정점 데이터 (버퍼에서 공급)
attribute vec2 a_uv;
uniform mat4 u_mvp; // CPU에서 전달하는 변환 행렬
varying vec2 v_uv; // Fragment로 보간하여 전달
void main() {
v_uv = a_uv;
gl_Position = u_mvp * vec4(a_position, 1.0);
}
glsl
// Fragment Shader — 픽셀(프래그먼트)마다 실행
precision mediump float;
varying vec2 v_uv;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_uv);
}
셰이더 컴파일 & 링크
javascript
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
function createProgram(gl, vertSrc, fragSrc) {
const vert = createShader(gl, gl.VERTEX_SHADER, vertSrc);
const frag = createShader(gl, gl.FRAGMENT_SHADER, fragSrc);
const program = gl.createProgram();
gl.attachShader(program, vert);
gl.attachShader(program, frag);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program));
return null;
}
return program;
}
버퍼 (Buffer)
정점 데이터(위치, 법선, UV 등)를 GPU 메모리에 올린다.
javascript
// Vertex Buffer Object (VBO)
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// x, y, z
0.0, 0.5, 0.0,
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
]), gl.STATIC_DRAW);
// attribute 연결 (셰이더의 a_position)
const loc = gl.getAttribLocation(program, 'a_position');
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(loc, 3, gl.FLOAT, false, 0, 0);
// Index Buffer Object (IBO) — 정점 재사용
const ibo = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ibo);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array([0, 1, 2]), gl.STATIC_DRAW);
CPU → GPU로 전달하는 상수 값(행렬, 색상, 시간 등).
javascript
gl.useProgram(program);
const uLoc = gl.getUniformLocation(program, 'u_time');
gl.uniform1f(uLoc, performance.now() / 1000);
gl.uniform3fv(gl.getUniformLocation(program, 'u_color'), [1, 0, 0]);
gl.uniformMatrix4fv(gl.getUniformLocation(program, 'u_mvp'), false, matrixArray);
텍스처
javascript
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, imageElement);
gl.generateMipmap(gl.TEXTURE_2D);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(gl.getUniformLocation(program, 'u_texture'), 0);
드로우콜
javascript
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLES, 0, 3);
gl.drawElements(gl.TRIANGLES, 6, gl.UNSIGNED_SHORT, 0);
WebGL 1 vs WebGL 2
| 기능 | WebGL1 | WebGL2 |
|---|
| 기반 | OpenGL ES 2.0 | OpenGL ES 3.0 |
| Instancing | 확장(ext) | 내장 (drawArraysInstanced) |
| VAO | 확장 | 내장 (createVertexArray) |
| Uniform Buffer Object | ✗ | ✓ |
| 3D 텍스처 | ✗ | ✓ |
| MRT (Multi Render Target) | 확장 | ✓ |
| Transform Feedback | ✗ | ✓ |
| GLSL 버전 | 100 | 300 es |
Vertex Array Object (VAO) — WebGL2
attribute 설정 묶음을 객체로 저장해 드로우 시 한 번에 복원한다.
javascript
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.enableVertexAttribArray(posLoc);
gl.vertexAttribPointer(posLoc, 3, gl.FLOAT, false, 20, 0);
gl.enableVertexAttribArray(uvLoc);
gl.vertexAttribPointer(uvLoc, 2, gl.FLOAT, false, 20, 12);
gl.bindVertexArray(null);
// 드로우 시
gl.bindVertexArray(vao);
gl.drawArrays(gl.TRIANGLES, 0, count);
Framebuffer (오프스크린 렌더링)
화면 대신 텍스처에 렌더링. 포스트프로세싱, 그림자맵, 반사에 사용한다.
javascript
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
const colorTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, colorTex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, colorTex, 0);
const depthRbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, depthRbo);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, width, height);
gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, depthRbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, null); // 기본 프레임버퍼로 복귀
상태 관리
WebGL은 전역 상태 머신. 설정 후 명시적으로 변경하기 전까지 유지된다.
javascript
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LEQUAL);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.CULL_FACE);
gl.cullFace(gl.BACK);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0, 0, 0, 1);
WebGL vs 추상화 라이브러리
| WebGL Raw | Three.js | Babylon.js |
|---|
| 학습 난이도 | 높음 | 낮음 | 낮음 |
| 제어 수준 | 최대 | 중간 | 중간 |
| 번들 크기 | 0 KB | ~600 KB | ~2 MB |
| 커스텀 셰이더 | 자유 | ShaderMaterial | ShaderMaterial |
| 용도 | 엔진 제작, 특수 효과 | 일반 3D 앱 | 게임 엔진 |
관련 문서