Skip to content

게임 개발자를 위한 WebGL 기초

WebGL은 브라우저에서 GPU에 직접 접근하게 해줍니다. 3D(그리고 성능이 중요한 2D) 웹 게임의 토대죠. 이 튜토리얼은 핵심만 다룹니다.

1) WebGL 컨텍스트 가져오기

js
const canvas = document.getElementById('game')
const gl = canvas.getContext('webgl2') || canvas.getContext('webgl')

if (!gl) {
  // 폴백 또는 오류 메시지
  console.error('WebGL not supported')
}

가능하면 webgl2를 우선 쓰세요. 폭넓게 지원되고 더 나은 기능을 제공합니다.

2) 렌더 파이프라인 (단순화 버전)

  1. 버텍스 셰이더는 정점마다 한 번씩 실행됩니다. 지오메트리의 위치를 정합니다.
  2. 프래그먼트 셰이더는 픽셀마다 한 번씩 실행됩니다. 출력 색을 칠합니다.
  3. 버퍼는 데이터(위치, UV, 색)를 담습니다.
  4. 텍스처는 셰이더가 샘플링하는 이미지입니다.

3) 최소한의 셰이더 한 쌍

버텍스 셰이더:

glsl
#version 300 es
in vec2 a_position;
void main() {
  gl_Position = vec4(a_position, 0.0, 1.0);
}

프래그먼트 셰이더:

glsl
#version 300 es
precision mediump float;
out vec4 fragColor;
void main() {
  fragColor = vec4(1.0, 0.5, 0.2, 1.0); // orange
}

4) 셰이더 컴파일과 링크

js
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, vs, fs) {
  const program = gl.createProgram()
  gl.attachShader(program, vs)
  gl.attachShader(program, fs)
  gl.linkProgram(program)
  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error(gl.getProgramInfoLog(program))
    return null
  }
  return program
}

5) 버퍼 만들기

js
const positions = new Float32Array([
  -0.5, -0.5,
   0.5, -0.5,
   0.0,  0.5,
])

const buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW)

6) 버퍼를 셰이더에 연결하기

js
const positionLoc = gl.getAttribLocation(program, 'a_position')
gl.enableVertexAttribArray(positionLoc)
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)

7) 게임 루프

js
function render() {
  gl.viewport(0, 0, canvas.width, canvas.height)
  gl.clearColor(0, 0, 0, 1)
  gl.clear(gl.COLOR_BUFFER_BIT)

  gl.useProgram(program)
  gl.drawArrays(gl.TRIANGLES, 0, 3)

  requestAnimationFrame(render)
}
render()

8) 텍스처 로딩

js
function loadTexture(gl, url) {
  const texture = gl.createTexture()
  gl.bindTexture(gl.TEXTURE_2D, texture)

  // 이미지가 로드되기 전까지 쓸 자리표시용 픽셀
  gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
    new Uint8Array([255, 0, 255, 255]))

  const img = new Image()
  img.onload = () => {
    gl.bindTexture(gl.TEXTURE_2D, texture)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img)
    gl.generateMipmap(gl.TEXTURE_2D)
  }
  img.src = url
  return texture
}

9) 자주 빠지는 함정

  • 2의 거듭제곱 텍스처는 WebGL 2에서 꼭 필요한 건 아니지만, 밉맵과는 더 잘 맞습니다.
  • 컨텍스트 손실: webglcontextlost 이벤트를 처리하세요. 모바일 브라우저는 메모리가 부족하면 컨텍스트를 회수해 버립니다.
  • 부동소수점 텍스처는 확장 기능 확인이 필요합니다(EXT_color_buffer_float).

10) 언제 라이브러리를 쓸까

순수 WebGL은 코드가 장황합니다. 대부분의 게임이라면 다음을 고려해 보세요.

  • Three.js — 기능이 완비된 3D
  • PixiJS — 빠른 2D 렌더링
  • PlayCanvas — 에디터가 딸린 게임 엔진

순수 WebGL은 이럴 때 쓰세요.

  • 최대한의 제어가 필요할 때
  • 커스텀 렌더러를 만들 때
  • 내부가 실제로 어떻게 돌아가는지 배우고 싶을 때

2026년 기준으로 한 가지 짚자면, 이제 WebGPU가 모든 주요 브라우저에서 기본으로 제공됩니다(Chrome과 Edge는 113부터, Firefox는 Windows에서 141, macOS에서 145, Safari는 iOS 26, iPadOS 26, macOS Tahoe 26부터). 여기서 다룬 기초는 그대로 유효합니다. Three.js 같은 라이브러리는 WebGPU 렌더러를 돌리면서 WebGL 2로 자동 폴백하기 때문에, WebGL 2는 여전히 믿을 만한 기준선으로 남아 있습니다. 오늘 새 렌더러를 처음부터 만든다면, API를 확정하기 전에 이 글과 함께 WebGPU 튜토리얼도 읽어 보세요.

관련 글

외부 자료