CSS 개발자를 위한 GPU 2D 에디터 만들기
Guide
CSS Matrix
Guide
CSS Matrix
  • Part 0. CSS에서 GPU로 사고 전환

    • CSS 박스가 사라지면 무엇이 남는가
    • Canvas는 DOM이 아니라 픽셀 버퍼다
    • CSS pixel, device pixel, backing store
    • scene model과 renderer 분리하기
    • retained mode vs immediate mode
    • DOM event target 없이 hit testing 하기
  • Part 1. Canvas editor의 기본 뼈대

    • render loop와 frame lifecycle
    • viewport와 camera
    • screen/world/local 좌표계 복습
    • cursor anchored zoom
    • grid와 ruler를 canvas에 그리기
    • overlay layer와 control layer 분리
  • Part 2. WebGL 2D renderer

    • WebGL context와 첫 triangle
    • clip space와 화면 좌표
    • shader를 CSS transform 관점으로 읽기
    • rectangle을 두 triangle로 그리기
    • matrix uniform으로 camera 적용하기
    • 색상, alpha, blending
    • 여러 rectangle을 buffer에 담기
    • batching과 draw call 줄이기
    • instanced rectangle renderer
    • line/outline renderer
  • Part 3. Editor tool math

    • pointer 좌표를 world 좌표로 바꾸기
    • CPU hit testing
    • selection bounds
    • hover outline
    • move tool
    • resize handles
    • rotation handle
    • marquee selection
    • snapping과 smart guides
    • group transform
  • Part 4. Figma-like scene graph

    • node tree 설계
    • transform inheritance
    • layer order와 z sorting
    • frame과 clipping
    • fill, stroke, effect 모델
    • command model과 undo/redo
    • JSON export/import
    • renderer-independent editor core
  • Part 5. Image, text, vector

    • texture로 image node 그리기
    • texture atlas 기본
    • text rendering 전략
    • SVG/text를 GPU editor에서 다루는 경계
    • vector path는 어디까지 직접 구현할 것인가
  • Part 6. WebGPU로 옮기기

    • WebGPU adapter/device/context
    • WGSL과 render pipeline
    • WebGL renderer를 WebGPU renderer로 바꾸기
    • uniform buffer와 bind group
    • WebGPU instancing
    • WebGL/WebGPU fallback 전략
  • Part 7. Capstone

    • editor shell 만들기
    • toolbar / layer panel / inspector 연결
    • mini Figma-like editor 완성
    • 성능 점검과 디버깅
    • 배포와 브라우저 호환성 체크
  • Part 8. Three.js로 WebGL 개발하기

    • Three.js를 WebGL renderer로 쓰는 기준
    • Scene, Camera, Renderer, render loop
    • OrthographicCamera로 2D editor 좌표계 만들기
    • BufferGeometry, Material, ShaderMaterial
    • Raycaster와 editor picking
    • Three.js renderer를 editor core 뒤에 붙이기
    • Three.js 프로젝트 세팅과 renderer lifecycle
    • Object3D transform과 editor scene graph 매핑
    • InstancedMesh로 많은 rectangle 그리기
    • Texture, CanvasTexture, Sprite로 이미지/텍스트 다루기
    • Three.js에서 outline, selection, overlay 만들기
    • dispose, cache, renderer.info로 성능 관리하기
    • WebGPURenderer와 TSL로 넘어가는 길
    • RenderTarget을 이용한 picking buffer
    • Three.js를 쓰면 안 좋은 경우
  • Appendix A. GPU editor debugging

    • WebGL/WebGPU 디버그 overlay 만들기
    • 좌표계, matrix, bounds readout 설계
    • frame time, draw call, buffer upload 측정하기
    • Spector.js / Chrome DevTools로 WebGL 프레임 보기
  • Appendix B. Browser and GPU compatibility

    • WebGL/WebGPU feature detection 체크리스트
    • DPR, resize, context lost 처리
    • Safari/Chrome/Firefox 차이와 fallback 정책
    • GPU memory와 texture size 제한
  • Appendix C. Asset pipeline

    • 이미지 로딩, ImageBitmap, texture upload
    • SVG를 texture로 쓸지 vector로 유지할지
    • 폰트 로딩과 text metrics
    • export용 PNG/SVG/JSON 생성 전략
  • Appendix D. Interaction polish and motion

    • inertial pan과 smooth zoom
    • snapping feedback animation
    • selection/hover transition
    • timeline 없이 필요한 최소 모션 수학
  • Appendix E. Production architecture

    • renderer worker / OffscreenCanvas를 고려하는 기준
    • document model versioning과 migration
    • plugin architecture와 command API
    • test 가능한 renderer abstraction 만들기
  • Appendix F. 2D renderer engine patterns

    • renderable type 선택: shape, sprite, mesh
    • static subtree를 texture cache로 굽기
    • render layer와 render group 설계
    • viewport culling과 spatial index
    • clipping 구현: scissor, stencil, mask texture
    • filters와 blend modes가 batch를 깨는 이유
    • interactivity budget: pickable, hitArea, skip children
    • texture GC와 idle resource eviction
    • dynamic text update 비용과 bitmap/glyph 전략
    • Canvas/WebGL editor의 accessibility layer
  • Appendix G. Rendering editor production gaps

    • render invalidation과 dirty flag
    • color space, premultiplied alpha, export 색상
    • stroke join/cap/dash/fill rule
    • editable text: DOM overlay, IME, caret, metrics
    • tool state machine과 pointer capture
    • pixel test와 renderer regression test

WebGL context와 첫 triangle

Canvas 2D에서는 fillRect 같은 명령으로 바로 픽셀을 그렸습니다. WebGL에서는 조금 더 돌아갑니다. 먼저 canvas에서 WebGL context를 얻고, GPU가 실행할 shader를 준비하고, vertex 데이터를 보내고, draw call을 호출합니다.

처음에는 번거로워 보입니다. 하지만 이 번거로움이 나중에 수천 개의 도형을 빠르게 그릴 수 있는 길이 됩니다.

실제로 돌아가는 최소 코드

아래 코드는 위 demo의 핵심과 같은 구조입니다. WebGL은 “shader 준비 -> buffer 준비 -> attribute 연결 -> draw” 순서로 첫 픽셀을 만듭니다.

const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl2");

const vertexSource = `#version 300 es
in vec2 a_position;

void main() {
  gl_Position = vec4(a_position, 0.0, 1.0);
}
`;

const fragmentSource = `#version 300 es
precision highp float;
out vec4 outColor;

void main() {
  outColor = vec4(0.22, 0.55, 1.0, 1.0);
}
`;

function compileShader(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    throw new Error(gl.getShaderInfoLog(shader));
  }

  return shader;
}

const program = gl.createProgram();
gl.attachShader(program, compileShader(gl.VERTEX_SHADER, vertexSource));
gl.attachShader(program, compileShader(gl.FRAGMENT_SHADER, fragmentSource));
gl.linkProgram(program);

const positions = new Float32Array([
  -0.72, -0.58,
   0.72, -0.58,
   0.00,  0.72
]);

const vao = gl.createVertexArray();
const buffer = gl.createBuffer();
const location = gl.getAttribLocation(program, "a_position");

gl.bindVertexArray(vao);
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);
gl.enableVertexAttribArray(location);
gl.vertexAttribPointer(location, 2, gl.FLOAT, false, 0, 0);

gl.viewport(0, 0, canvas.width, canvas.height);
gl.clearColor(0.08, 0.09, 0.1, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.drawArrays(gl.TRIANGLES, 0, 3);

이 코드에서 positions는 CPU 쪽 숫자 배열이고, gl.bufferData 이후에는 GPU가 읽을 수 있는 buffer가 됩니다. gl.vertexAttribPointer는 “buffer의 숫자 두 개를 a_position 하나로 읽어라”라는 연결 규칙입니다.

context는 GPU 렌더러의 입구다

WebGL 렌더러는 canvas에서 시작합니다.

const canvas = document.querySelector("canvas");
const gl = canvas.getContext("webgl2");

gl은 DOM 요소가 아닙니다. GPU에 명령을 보내기 위한 상태ful API입니다. shader program, buffer, texture, viewport, blending 같은 렌더링 상태를 이 context를 통해 설정합니다.

첫 목표는 triangle이다

WebGL에서 모든 2D 도형은 결국 triangle로 내려갑니다. 사각형도 triangle 두 개고, 복잡한 path도 잘게 쪼개면 triangle 목록입니다.

그래서 첫 렌더러의 목표는 거창하지 않습니다.

vertex 3개를 보낸다.
GPU가 triangle 하나를 그린다.

이 작은 성공이 WebGL renderer의 최소 회로입니다.

지금은 편집기와 분리해서 본다

첫 triangle을 그릴 때는 scene graph, selection, camera를 잠시 내려놓습니다. 먼저 GPU에 데이터를 보내고 화면에 색이 나오는지 확인합니다.

하지만 목적지는 잊지 않습니다.

triangle
-> rectangle
-> many rectangles
-> scene nodes
-> editor renderer

오늘의 triangle은 API 튜토리얼이 아니라 Figma-like editor renderer의 첫 픽셀입니다.

오늘의 핵심

WebGL context는 Canvas를 GPU 렌더링 표면으로 바꾸는 입구입니다. 첫 triangle은 renderer가 살아 있는지 확인하는 가장 작은 단위입니다.

canvas -> WebGL context -> shader -> buffer -> draw

이 흐름을 잡으면 다음 레슨에서 WebGL의 낯선 좌표계인 clip space를 다룰 준비가 됩니다.

최근 수정: 26. 5. 16. PM 12:53
Contributors: jinho.park.s3
Next
clip space와 화면 좌표