๐Ÿ—๏ธ WebGL2์—์„œ ์ด๋ฏธ์ง€ ํ•œ ์žฅ์ด ๋งŒ๋“ค์–ด์ง€๊ธฐ๊นŒ์ง€

BamgasiJMยท2026๋…„ 7์›” 3์ผ

WebGL

๋ชฉ๋ก ๋ณด๊ธฐ
7/14
post-thumbnail

WebGL2๋กœ ์…ฐ์ด๋” ์•„ํŠธ๋ฅผ ๋งŒ๋“ค ๋•Œ ํ”ํžˆ "์…ฐ์ด๋”๋งŒ ์ž˜ ์งœ๋ฉด ๋œ๋‹ค"๊ณ  ์ƒ๊ฐํ•˜๊ธฐ ์‰ฝ์ง€๋งŒ, ์‹ค์ œ๋กœ ํ™”๋ฉด์— ํ”ฝ์…€ ํ•˜๋‚˜๊ฐ€ ์ฐํžˆ๊ธฐ๊นŒ์ง€๋Š” CPU์™€ GPU ์‚ฌ์ด๋ฅผ ์˜ค๊ฐ€๋Š” ์—ฌ๋Ÿฌ ๋‹จ๊ณ„์˜ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜์ด ์žˆ์–ด์š”. ์ด ๊ธ€์€ ์ฝ”๋“œ์˜ ์‹คํ–‰ ์ˆœ์„œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผ๊ฐ€๋ฉด์„œ, ๊ฐ ๋‹จ๊ณ„์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์–ด๋–ค ํ˜•ํƒœ๋กœ ์กด์žฌํ•˜๊ณ  ๋‹ค์Œ ๋‹จ๊ณ„๋กœ ์–ด๋–ป๊ฒŒ ์ „๋‹ฌ๋˜๋Š”์ง€๋ฅผ ์ •๋ฆฌํ•œ ๊ธ€์ด์—์š”.

main.js ์ฝ”๋“œ

// ========================================
// ์บ”๋ฒ„์Šค ์—˜๋ฆฌ๋จผํŠธ ์ฐธ์กฐ
// ========================================
// canvas๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ํ”ฝ์…€ ๋ฒ„ํผ๋ฅผ ๊ฐ€์ง„ DOM ์—˜๋ฆฌ๋จผํŠธ.
// ์ด ์‹œ์ ์—๋Š” ์•„์ง WebGL๊ณผ ์•„๋ฌด ๊ด€๊ณ„๋„ ์—†๋Š” ์ˆœ์ˆ˜ํ•œ HTML ์š”์†Œ์ผ ๋ฟ์ž„.
const canvas = document.getElementById("webgl_canvas");

// ========================================
// WebGL2 ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ
// ========================================
// getContext("webgl2")๋ฅผ ํ˜ธ์ถœํ•ด์•ผ ๋น„๋กœ์†Œ ์ด canvas๊ฐ€ GPU ๋“œ๋ผ์ด๋ฒ„์™€
// ํ†ต์‹ ํ•  ์ˆ˜ ์žˆ๋Š” ์ฐฝ๊ตฌ๋ฅผ ์–ป์Œ. ์ดํ›„ gl.xxx() ํ˜•ํƒœ์˜ ๋ชจ๋“  ํ˜ธ์ถœ์€
// ์ด ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฑฐ์ณ GPU ๋“œ๋ผ์ด๋ฒ„์— ๋ช…๋ น์„ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ.
const gl = canvas.getContext("webgl2");

if (!gl) {
  alert("WebGL2๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €์ž…๋‹ˆ๋‹ค.");
  throw new Error("WebGL2 not supported");
}

// ========================================
// ์บ”๋ฒ„์Šค ํฌ๊ธฐ | ๋ทฐํฌํŠธ ์„ค์ •
// ========================================
// canvas.width/height : ์‹ค์ œ ํ”ฝ์…€์ด ์ €์žฅ๋˜๋Š” ๋“œ๋กœ์ž‰ ๋ฒ„ํผ์˜ ํฌ๊ธฐ(๊ฐœ์ˆ˜)๋ฅผ ๊ฒฐ์ •.
// ์•„์ง ์ด ๋‹จ๊ณ„์—์„œ๋Š” ๋ฒ„ํผ ์•ˆ์— ์–ด๋–ค ์ƒ‰์ƒ ๋ฐ์ดํ„ฐ๋„ ์ฑ„์›Œ์ง€์ง€ ์•Š์€ ๋นˆ ์ƒํƒœ์ž„.
canvas.width = 1000;
canvas.height = 1000;

// gl.viewport(0, 0, width, height) : ์ •์  ์…ฐ์ด๋”๊ฐ€ ์ถœ๋ ฅํ•˜๋Š” -1~1 ๋ฒ”์œ„์˜
// ํด๋ฆฝ ๊ณต๊ฐ„ ์ขŒํ‘œ๋ฅผ, ์œ„์—์„œ ์ •ํ•œ ๋“œ๋กœ์ž‰ ๋ฒ„ํผ์˜ ๋ช‡ ๋ฒˆ์งธ ํ”ฝ์…€์— ๋Œ€์‘์‹œํ‚ฌ์ง€
// ์ •ํ•˜๋Š” "์ขŒํ‘œ ๋ณ€ํ™˜ ๊ทœ์น™"์„ ๋“ฑ๋กํ•˜๋Š” ํ˜ธ์ถœ. ์•„์ง ์‹ค์ œ ์ •์  ๋ฐ์ดํ„ฐ๋Š”
// ํ•˜๋‚˜๋„ ์—†์œผ๋ฏ€๋กœ, ์ด ์‹œ์ ์—๋„ ํ™”๋ฉด์—๋Š” ์•„๋ฌด๊ฒƒ๋„ ๊ทธ๋ ค์ง€์ง€ ์•Š์Œ.
gl.viewport(0, 0, canvas.width, canvas.height);

// ========================================
// ๋งˆ์šฐ์Šค ์ขŒํ‘œ ์ƒํƒœ
// ========================================
// ์ด ๋ณ€์ˆ˜๋“ค์€ ์ˆœ์ˆ˜ํ•˜๊ฒŒ JavaScript(CPU) ๋ฉ”๋ชจ๋ฆฌ์—๋งŒ ์กด์žฌํ•จ.
// GPU๋Š” ์ด ๊ฐ’์˜ ์กด์žฌ ์ž์ฒด๋ฅผ ์ „ํ˜€ ๋ชจ๋ฅด๋Š” ์ƒํƒœ์ด๊ณ , ๋ Œ๋” ๋ฃจํ”„์—์„œ
// uniform2f๋กœ ์—…๋กœ๋“œ๋˜๋Š” ์ˆœ๊ฐ„์—๋งŒ GPU ์ชฝ์œผ๋กœ ๊ฐ’์ด ๋ณต์‚ฌ๋จ.
let mouseX = 0;
let mouseY = 0;

// ========================================
// ๋งˆ์šฐ์Šค ์ด๋™ ์ด๋ฒคํŠธ ๋“ฑ๋ก
// ========================================
canvas.addEventListener("mousemove", (e) => {
  // getBoundingClientRect() : ์บ”๋ฒ„์Šค๊ฐ€ CSS๋กœ ์‹ค์ œ ํ™”๋ฉด์— ํ‘œ์‹œ๋˜๋Š”
  // ํฌ๊ธฐ์™€ ์œ„์น˜(CSS ํ”ฝ์…€ ๊ธฐ์ค€)๋ฅผ ๊ฐ€์ ธ์˜ด. ๋“œ๋กœ์ž‰ ๋ฒ„ํผ ํ”ฝ์…€ ํฌ๊ธฐ(canvas.width)์™€
  // ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ์•„๋ž˜์—์„œ ์Šค์ผ€์ผ ๋น„์œจ์„ ๊ตฌํ•ด ๋ณด์ •ํ•จ.
  const rect = canvas.getBoundingClientRect();
  const scaleX = canvas.width / rect.width;
  const scaleY = canvas.height / rect.height;

  // e.clientX/Y(๋ธŒ๋ผ์šฐ์ € ์ด๋ฒคํŠธ ์ขŒํ‘œ, CSS ํ”ฝ์…€)๋ฅผ ๋“œ๋กœ์ž‰ ๋ฒ„ํผ ํ”ฝ์…€ ์ขŒํ‘œ๋กœ ํ™˜์‚ฐ.
  mouseX = (e.clientX - rect.left) * scaleX;

  // ๋ธŒ๋ผ์šฐ์ € ์ขŒํ‘œ๊ณ„๋Š” ์™ผ์ชฝ ์œ„๊ฐ€ ์›์ ์ด์ง€๋งŒ, ์…ฐ์ด๋” ์ขŒํ‘œ๊ณ„๋Š” ๋ณดํ†ต
  // ์™ผ์ชฝ ์•„๋ž˜๊ฐ€ ์›์ ์ด๋ฏ€๋กœ canvas.height์—์„œ ๋นผ์„œ Y์ถ•์„ ๋ฐ˜์ „์‹œํ‚ด.
  // ์ด ๋ณ€ํ™˜์„ ํ•˜์ง€ ์•Š์œผ๋ฉด ์…ฐ์ด๋” ์•ˆ์—์„œ ๋งˆ์šฐ์Šค๋ฅผ ๋”ฐ๋ผ๊ฐ€๋Š” ํšจ๊ณผ๋ฅผ
  // ๋งŒ๋“ค ๋•Œ ์œ„์•„๋ž˜๊ฐ€ ๋’ค์ง‘ํ˜€ ๋ณด์ž„.
  mouseY = canvas.height - (e.clientY - rect.top) * scaleY;
});

// ========================================
// ์…ฐ์ด๋” ์†Œ์Šค ํŒŒ์ผ ๋กœ๋“œ
// ========================================
// .glsl ํŒŒ์ผ์€ GPU๊ฐ€ ๋ฐ”๋กœ ์ดํ•ดํ•˜๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌ๋žŒ์ด ์ฝ๋Š”
// GLSL ํ…์ŠคํŠธ ์ฝ”๋“œ. fetch๋กœ ๊ฐ€์ ธ์˜จ ์‹œ์ ์—๋Š” ๊ทธ๋ƒฅ ๋ฌธ์ž์—ด์ด๊ณ ,
// GPU๋Š” ์ด ์‹œ์ ๊นŒ์ง€ ์•„๋ฌด๊ฒƒ๋„ ์•Œ์ง€ ๋ชปํ•จ(์ˆœ์ˆ˜ CPU ๋‹จ๊ณ„).
async function loadShader(url) {
  const response = await fetch(url);

  // response.ok๋ฅผ ํ™•์ธํ•˜์ง€ ์•Š์œผ๋ฉด, ๊ฒฝ๋กœ๊ฐ€ ์ž˜๋ชป๋์„ ๋•Œ ์„œ๋ฒ„๊ฐ€ ๋ฐ˜ํ™˜ํ•˜๋Š”
  // 404 ์‘๋‹ต ๋ณธ๋ฌธ(HTML ๋ฌธ์ž์—ด)์ด ๊ทธ๋Œ€๋กœ ์…ฐ์ด๋” ํ…์ŠคํŠธ๋กœ ์ทจ๊ธ‰๋˜์–ด
  // ๋‹ค์Œ ๋‹จ๊ณ„์ธ ์ปดํŒŒ์ผ์—์„œ "์›์ธ ๋ถˆ๋ช…์˜ GLSL ๋ฌธ๋ฒ• ์—๋Ÿฌ"๋กœ๋งŒ ๋ณด์ด๊ฒŒ ๋จ.
  if (!response.ok) {
    throw new Error(`์…ฐ์ด๋” ํŒŒ์ผ ๋กœ๋“œ ์‹คํŒจ (${response.status}): ${url}`);
  }
  return await response.text();
}

// ========================================
// ์…ฐ์ด๋” ์ปดํŒŒ์ผ
// ========================================
// ์—ฌ๊ธฐ์„œ ์ฒ˜์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ CPU์—์„œ GPU ์ชฝ์œผ๋กœ ๋„˜์–ด๊ฐ.
function compileShader(type, source) {
  // createShader : GPU ๋“œ๋ผ์ด๋ฒ„ ์•ˆ์— ๋‚ด์šฉ์ด ๋น„์–ด์žˆ๋Š” ์…ฐ์ด๋” ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ.
  // ์•„์ง ์ด ๊ฐ์ฒด๋Š” ์–ด๋–ค GLSL ์ฝ”๋“œ๋„ ๋‹ด๊ณ  ์žˆ์ง€ ์•Š์Œ.
  const shader = gl.createShader(type);

  // shaderSource : CPU์— ์žˆ๋˜ GLSL ํ…์ŠคํŠธ(source)๋ฅผ ์ด ์…ฐ์ด๋” ๊ฐ์ฒด ์•ˆ์— ์ฑ„์›Œ ๋„ฃ์Œ.
  // ์ด ์‹œ์ ์—๋„ GPU ์ž…์žฅ์—์„œ๋Š” ์—ฌ์ „ํžˆ "ํ•ด์„๋˜์ง€ ์•Š์€ ํ…์ŠคํŠธ"์ผ ๋ฟ.
  gl.shaderSource(shader, source);

  // compileShader : GPU ๋“œ๋ผ์ด๋ฒ„ ๋‚ด๋ถ€ ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ํ…์ŠคํŠธ๋ฅผ ์‹ค์ œ๋กœ ์‹คํ–‰
  // ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋‹จ๊ณ„. ์ด ํ˜ธ์ถœ์ด ๋๋‚˜์•ผ ๋น„๋กœ์†Œ ์ด ๊ฐ์ฒด๊ฐ€
  // GPU์—์„œ ์˜๋ฏธ๋ฅผ ๊ฐ–๋Š” "์ปดํŒŒ์ผ๋œ ์…ฐ์ด๋”"๊ฐ€ ๋จ.
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    const log = gl.getShaderInfoLog(shader);
    gl.deleteShader(shader);
    const typeName = type === gl.VERTEX_SHADER ? "Vertex" : "Fragment";

    // ์‹คํŒจ ์‹œ null์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋Œ€์‹  ์˜ˆ์™ธ๋ฅผ ๋˜์ง. null์„ ๋ฐ˜ํ™˜ํ•˜๋ฉด
    // ํ˜ธ์ถœ๋ถ€์—์„œ ๋งค๋ฒˆ ๊ฒ€์ฆํ•˜์ง€ ์•Š๋Š” ํ•œ ๋ฌดํšจํ•œ ์…ฐ์ด๋” ๊ฐ์ฒด๋กœ ๋‹ค์Œ ๋‹จ๊ณ„
    // (attachShader ๋“ฑ)๊ฐ€ ๊ทธ๋Œ€๋กœ ์ง„ํ–‰๋˜์–ด, ์‹ค์ œ ๋ฌธ์ œ ๋ฐœ์ƒ ์ง€์ ๊ณผ
    // ์ฆ์ƒ์ด ๋‚˜ํƒ€๋‚˜๋Š” ์ง€์ ์ด ๋ฉ€์–ด์ ธ ๋””๋ฒ„๊น…์ด ์–ด๋ ค์›Œ์ง.
    throw new Error(`${typeName} ์…ฐ์ด๋” ์ปดํŒŒ์ผ ์—๋Ÿฌ:\n${log}`);
  }
  return shader;
}

// ========================================
// ์…ฐ์ด๋” ํ”„๋กœ๊ทธ๋žจ ์ƒ์„ฑ ๋ฐ ๋งํฌ
// ========================================
function createProgram(vertexShader, fragmentShader) {
  // createProgram : ๋‘ ์…ฐ์ด๋”๋ฅผ ๋‹ด์„ ๋นˆ ํ”„๋กœ๊ทธ๋žจ ์ปจํ…Œ์ด๋„ˆ๋ฅผ GPU์— ์ƒ์„ฑ.
  const program = gl.createProgram();

  // attachShader : ์ปดํŒŒ์ผ๋œ ์ •์ /ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๋ฅผ ์ด ํ”„๋กœ๊ทธ๋žจ์— ์—ฐ๊ฒฐ.
  // ์•„์ง ๋‘ ์…ฐ์ด๋”๋Š” ์„œ๋กœ ๋…๋ฆฝ๋œ ๊ฐ์ฒด์ด๋ฉฐ, ์—ฐ๊ฒฐ๋งŒ ๋œ ์ƒํƒœ.
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);

  // linkProgram : ์ •์  ์…ฐ์ด๋”๊ฐ€ out์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๊ฐ’๊ณผ ํ”„๋ž˜๊ทธ๋จผํŠธ
  // ์…ฐ์ด๋”๊ฐ€ in์œผ๋กœ ๋ฐ›๋Š” ๊ฐ’์˜ ์ด๋ฆ„ยทํƒ€์ž…์ด ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•˜๊ณ ,
  // ๋‘ ์…ฐ์ด๋”๋ฅผ ํ•˜๋‚˜์˜ ์‹คํ–‰ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๊ฒฐํ•ฉํ•จ. ๊ฐœ๋ณ„ ์ปดํŒŒ์ผ์ด
  // ๊ฐ๊ฐ ์„ฑ๊ณตํ•ด๋„ ์ด ๋‹จ๊ณ„์—์„œ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๋Š” ๋ณ„๋„์˜ ์‹คํŒจ ์ง€์ .
  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    const log = gl.getProgramInfoLog(program);
    gl.deleteProgram(program);
    throw new Error(`ํ”„๋กœ๊ทธ๋žจ ๋งํฌ ์—๋Ÿฌ:\n${log}`);
  }

  // ๋งํฌ๊ฐ€ ๋๋‚œ ์ด program ๊ฐ์ฒด๊ฐ€ ์ดํ›„ ๋ชจ๋“  ๋ Œ๋”๋ง์˜ ์‹คํ–‰ ๋‹จ์œ„๊ฐ€ ๋จ.
  return program;
}

// ========================================
// ์ดˆ๊ธฐํ™” ๋ฐ ๋ Œ๋” ๋ฃจํ”„ ์‹œ์ž‘
// ========================================
async function init() {
  // ์ด ์‹œ์ ์˜ vertexSource/fragmentSource๋Š” ์•„์ง CPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์˜
  // ๋ฌธ์ž์—ด์ผ ๋ฟ, GPU์™€๋Š” ๋ฌด๊ด€ํ•œ ๋ฐ์ดํ„ฐ.
  const vertexSource = await loadShader("shaders/vert.glsl");
  const fragmentSource = await loadShader("shaders/frag.glsl");

  // ์ปดํŒŒ์ผ : ํ…์ŠคํŠธ โ†’ GPU๊ฐ€ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ์…ฐ์ด๋” ๊ฐ์ฒด.
  const vertexShader = compileShader(gl.VERTEX_SHADER, vertexSource);
  const fragmentShader = compileShader(gl.FRAGMENT_SHADER, fragmentSource);

  // ๋งํฌ : ๋‘ ์…ฐ์ด๋” ๊ฐ์ฒด โ†’ ํ•˜๋‚˜์˜ ์‹คํ–‰ ํŒŒ์ดํ”„๋ผ์ธ(program).
  const program = createProgram(vertexShader, fragmentShader);

  // ----------------------------------------
  // VAO(Vertex Array Object) ์ƒ์„ฑ ๋ฐ ๋ฐ”์ธ๋”ฉ
  // ----------------------------------------
  // VAO๋Š” ๊ทธ ์ž์ฒด๋กœ ์ •์  ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ด๋Š” ๊ทธ๋ฆ‡์ด ์•„๋‹ˆ๋ผ, ์•„๋ž˜์—์„œ ์„ค์ •ํ• 
  // "๋ฒ„ํผ ๋ฐ”์ธ๋”ฉ + attribute ํ•ด์„ ๊ทœ์น™"์˜ ์กฐํ•ฉ์„ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ๊ธฐ๋กํ•ด๋‘๋Š”
  // ์ปจํ…Œ์ด๋„ˆ. ๋ฐ˜๋“œ์‹œ ๋ฒ„ํผ ๊ด€๋ จ ์„ค์ •๋ณด๋‹ค ๋จผ์ € ๋ฐ”์ธ๋”ฉํ•ด์•ผ, ์ดํ›„์˜
  // bindBuffer/vertexAttribPointer ํ˜ธ์ถœ ๊ฒฐ๊ณผ๊ฐ€ ์ด VAO์— ๊ท€์†๋จ.
  const vao = gl.createVertexArray();
  gl.bindVertexArray(vao);

  // ----------------------------------------
  // ์ •์  ์ขŒํ‘œ ์›๋ณธ ๋ฐ์ดํ„ฐ ์ •์˜ (์•„์ง CPU, ์•„์ง "์ •์ " ์•„๋‹˜)
  // ----------------------------------------
  // ์ด ๋ฐฐ์—ด์€ ์ด ์‹œ์ ์— ๊ทธ๋ƒฅ 8๊ฐœ์˜ ์ˆซ์ž๊ฐ€ ๋‚˜์—ด๋œ ๊ฒƒ์ผ ๋ฟ์ž„.
  // -1, -1 / 1, -1 / -1, 1 / 1, 1 ๋กœ 2๊ฐœ์”ฉ ๋ฌถ์–ด๋ณด๋ฉด ์‚ฌ๋žŒ ๋ˆˆ์—๋Š”
  // ํด๋ฆฝ ๊ณต๊ฐ„์˜ ๋„ค ๊ผญ์ง“์ ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ, JS๋„ GPU๋„ ์•„์ง
  // "2๊ฐœ์”ฉ ๋ฌถ์–ด์„œ ์ •์  ํ•˜๋‚˜๋กœ ์ทจ๊ธ‰ํ•˜๋ผ"๋Š” ๊ทœ์น™์„ ์•Œ์ง€ ๋ชปํ•จ.
  // ์ด ๊ทœ์น™์€ ํ›จ์”ฌ ์•„๋ž˜์˜ vertexAttribPointer์—์„œ ๋น„๋กœ์†Œ ์ •ํ•ด์ง.
  const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);

  // ----------------------------------------
  // ์ •์  ๋ฒ„ํผ(VBO) ์ƒ์„ฑ ๋ฐ GPU ์—…๋กœ๋“œ
  // ----------------------------------------
  // createBuffer : GPU ๋ฉ”๋ชจ๋ฆฌ ์ƒ์— ๋นˆ ๋ฒ„ํผ ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ.
  const positionBuffer = gl.createBuffer();

  // bindBuffer(ARRAY_BUFFER, ...) : ์ดํ›„์˜ ๋ฒ„ํผ ๊ด€๋ จ ํ˜ธ์ถœ์ด ์ด
  // positionBuffer๋ฅผ ๋Œ€์ƒ์œผ๋กœ ํ•˜๋„๋ก ์ง€์ •. VAO๊ฐ€ ๋ฐ”์ธ๋”ฉ๋œ ์ƒํƒœ์—์„œ
  // ํ˜ธ์ถœํ•ด์•ผ, ์•„๋ž˜ vertexAttribPointer ์„ค์ •์ด ์ด VAO์— ๊ธฐ๋ก๋จ.
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

  // bufferData : CPU์— ์žˆ๋˜ positions ๋ฐฐ์—ด(์ˆซ์ž ๋‚˜์—ด)์„ GPU ๋ฉ”๋ชจ๋ฆฌ๋กœ
  // ์‹ค์ œ ๋ณต์‚ฌํ•˜๋Š” ๋‹จ๊ณ„. ๋ฐ์ดํ„ฐ๊ฐ€ CPU โ†’ GPU๋กœ ๋„˜์–ด๊ฐ€์ง€๋งŒ, GPU ์ž…์žฅ์—์„œ๋Š”
  // ์—ฌ์ „ํžˆ "8๊ฐœ์˜ float๊ฐ€ ๋‚˜์—ด๋œ ๋ฐ”์ดํŠธ ๋ฉ์–ด๋ฆฌ"์ผ ๋ฟ, ์•„์ง ์ •์ ์ด๋ผ๋Š”
  // ์˜๋ฏธ๋Š” ๋ถ€์—ฌ๋˜์ง€ ์•Š์€ ์ƒํƒœ.
  gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

  // ----------------------------------------
  // Attribute ์—ฐ๊ฒฐ : ๋ฐ”์ดํŠธ ๋ฉ์–ด๋ฆฌ๋ฅผ "์ •์ "์œผ๋กœ ํ•ด์„ํ•˜๋Š” ๊ทœ์น™
  // ----------------------------------------
  // getAttribLocation : ๋งํฌ๋œ program ์•ˆ์—์„œ "a_position"์ด๋ผ๋Š”
  // ์ด๋ฆ„์˜ ์ •์  ์…ฐ์ด๋” ์ž…๋ ฅ์ด ๋ช‡ ๋ฒˆ ์Šฌ๋กฏ์— ๋ฐฐ์ •๋˜์—ˆ๋Š”์ง€ ์กฐํšŒ.
  // ์ด๋ฆ„์ด ๋‹ค๋ฅด๊ฑฐ๋‚˜ GLSL ์ปดํŒŒ์ผ๋Ÿฌ๊ฐ€ ์‚ฌ์šฉ๋˜์ง€ ์•Š๋Š” ๋ณ€์ˆ˜๋กœ ํŒ๋‹จํ•ด
  // ์ œ๊ฑฐํ•œ ๊ฒฝ์šฐ -1์ด ๋ฐ˜ํ™˜๋จ.
  const a_position = gl.getAttribLocation(program, "a_position");
  if (a_position === -1) {
    // ๊ฒ€์ฆ ์—†์ด ์ง„ํ–‰ํ•˜๋ฉด vertexAttribPointer๊ฐ€ ์ž˜๋ชป๋œ ์ธ๋ฑ์Šค์—
    // ์กฐ์šฉํžˆ ์ ์šฉ๋˜๊ณ , ํ™”๋ฉด์— ์•„๋ฌด๊ฒƒ๋„ ๊ทธ๋ ค์ง€์ง€ ์•Š๋Š” ์›์ธ ๋ถˆ๋ช…
    // ์ƒํƒœ๋กœ ์ด์–ด์ง.
    throw new Error("a_position ์†์„ฑ์„ ์…ฐ์ด๋”์—์„œ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.");
  }

  // enableVertexAttribArray : ์œ„ ์Šฌ๋กฏ์„ ์‚ฌ์šฉํ•˜๊ฒ ๋‹ค๊ณ  ํ™œ์„ฑํ™”.
  gl.enableVertexAttribArray(a_position);

  // vertexAttribPointer(a_position, 2, FLOAT, ...) : ๋ฐ”๋กœ ์ด ํ˜ธ์ถœ์ด
  // "GPU ๋ฉ”๋ชจ๋ฆฌ์˜ ๋ฐ”์ดํŠธ ๋ฉ์–ด๋ฆฌ๋ฅผ float 2๊ฐœ์”ฉ ๋Š์–ด์„œ, ๊ทธ ๊ฐ๊ฐ์„
  // a_position์ด๋ผ๋Š” ์ •์  ์…ฐ์ด๋” ์ž…๋ ฅ ํ•˜๋‚˜๋กœ ์ทจ๊ธ‰ํ•˜๋ผ"๋Š” ํ•ด์„ ๊ทœ์น™์„
  // ํ™•์ •ํ•˜๋Š” ์ง€์ . ์ด ํ˜ธ์ถœ ์ด์ „๊นŒ์ง€๋Š” positionBuffer๊ฐ€ ๊ทธ๋ƒฅ 8๊ฐœ์˜
  // float์˜€์„ ๋ฟ์ด์ง€๋งŒ, ์ด ํ˜ธ์ถœ ์ดํ›„๋กœ๋Š” GPU๊ฐ€ ๊ทธ๊ฒƒ์„ 4๊ฐœ์˜ ์ •์ ์œผ๋กœ
  // ์ธ์‹ํ•˜๊ฒŒ ๋จ. ์ด ๊ทœ์น™์€ ํ˜„์žฌ ๋ฐ”์ธ๋”ฉ๋œ VAO(vao)์— ๊ธฐ๋ก๋จ.
  gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

  // ----------------------------------------
  // ์œ ๋‹ˆํผ ์œ„์น˜ ์กฐํšŒ
  // ----------------------------------------
  // Attribute๊ฐ€ "์ •์ ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฐ’"์„ ์ „๋‹ฌํ•˜๋Š” ํ†ต๋กœ๋ผ๋ฉด, ์œ ๋‹ˆํผ์€
  // "ํ•œ ๋ฒˆ์˜ ๋“œ๋กœ์šฐ ์ฝœ ๋™์•ˆ ๋ชจ๋“  ์ •์ ยทํ”ฝ์…€์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋˜๋Š” ๊ฐ’"์„
  // ์ „๋‹ฌํ•˜๋Š” ํ†ต๋กœ. ์—ฌ๊ธฐ์„œ๋Š” ์œ„์น˜(location)๋งŒ ๋ฏธ๋ฆฌ ์กฐํšŒํ•ด๋‘๊ณ ,
  // ์‹ค์ œ ๊ฐ’์€ ์•„์ง ์ „๋‹ฌํ•˜์ง€ ์•Š์Œ. ์œ„์น˜ ์ž์ฒด๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ์กด์žฌํ•˜๋Š” ๋™์•ˆ
  // ๋ณ€ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ๋ Œ๋” ๋ฃจํ”„ ๋ฐ–์—์„œ ํ•œ ๋ฒˆ๋งŒ ๊ตฌํ•˜๋ฉด ์ถฉ๋ถ„ํ•จ.
  const u_resolution = gl.getUniformLocation(program, "u_resolution");
  const u_time = gl.getUniformLocation(program, "u_time");
  const u_mouse = gl.getUniformLocation(program, "u_mouse");

  // ----------------------------------------
  // ํ”„๋กœ๊ทธ๋žจ ํ™œ์„ฑํ™”
  // ----------------------------------------
  // useProgram : ์ง€๊ธˆ๊นŒ์ง€ ์ค€๋น„ํ•œ program์„ GPU๊ฐ€ ์‹ค์ œ๋กœ ์‹คํ–‰ํ• 
  // ๋Œ€์ƒ์œผ๋กœ ์ง€์ •. ์ด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ํ”„๋กœ๊ทธ๋žจ์„ ํ•˜๋‚˜๋งŒ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ
  // ์ดˆ๊ธฐํ™” ๋‹จ๊ณ„์—์„œ ํ•œ ๋ฒˆ๋งŒ ํ˜ธ์ถœํ•˜๋ฉด ์ถฉ๋ถ„ํ•˜๊ณ , ์ดํ›„ drawArrays๊ฐ€
  // ํ˜ธ์ถœ๋  ๋•Œ๋งˆ๋‹ค ์ด program์„ ๊ธฐ์ค€์œผ๋กœ ํŒŒ์ดํ”„๋ผ์ธ์ด ์‹คํ–‰๋จ.
  gl.useProgram(program);

  // ----------------------------------------
  // ๋ Œ๋” ๋ฃจํ”„
  // ----------------------------------------
  const startTime = Date.now();

  function render() {
    // ๋งค ํ”„๋ ˆ์ž„ CPU ์ชฝ์—์„œ ๊ณ„์‚ฐ๋˜๋Š” ๊ฐ’. ์•„์ง GPU๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์€ ์ƒํƒœ.
    const time = (Date.now() - startTime) * 0.001;

    // clearColor + clear : ์ด์ „ ํ”„๋ ˆ์ž„์— ๊ธฐ๋ก๋˜์–ด ์žˆ๋˜ ์ƒ‰์ƒ ๋ฒ„ํผ
    // ๋‚ด์šฉ์„ ์ง€์ •ํ•œ ๋ฐฐ๊ฒฝ์ƒ‰(๊ฒ€์€์ƒ‰)์œผ๋กœ ๋ฎ์–ด์”€. ์ด ์‹œ์ ์˜ ํ™”๋ฉด์€
    // ์•„์ง ์ด๋ฒˆ ํ”„๋ ˆ์ž„์˜ ๊ฒฐ๊ณผ๋ฅผ ๋‹ด๊ณ  ์žˆ์ง€ ์•Š์Œ.
    gl.clearColor(0, 0, 0, 1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // uniform2f/1f : CPU ๋ณ€์ˆ˜(canvas.width/height, time, mouseX/Y)์˜
    // ํ˜„์žฌ ๊ฐ’์„ GPU์˜ ์œ ๋‹ˆํผ ์Šฌ๋กฏ์œผ๋กœ ๋ณต์‚ฌ. ์ด ํ˜ธ์ถœ๋“ค์ด ์‹คํ–‰๋˜๋Š”
    // ์ˆœ๊ฐ„ ๋น„๋กœ์†Œ ์ด๋ฒˆ ํ”„๋ ˆ์ž„์˜ ์‹œ๊ฐ„๊ณผ ๋งˆ์šฐ์Šค ์ขŒํ‘œ๊ฐ€ GPU๋กœ ๋„˜์–ด๊ฐ.
    gl.uniform2f(u_resolution, canvas.width, canvas.height);
    gl.uniform1f(u_time, time);
    gl.uniform2f(u_mouse, mouseX, mouseY);

    // bindVertexArray(vao) : init()์—์„œ ๊ธฐ๋กํ•ด๋‘” "๋ฒ„ํผ + attribute
    // ํ•ด์„ ๊ทœ์น™" ๋ฌถ์Œ์„ ๋‹ค์‹œ ํ™œ์„ฑํ™”. ์ด ํ•œ ์ค„๋กœ positionBuffer๋ฅผ
    // 4๊ฐœ์˜ ์ •์ ์œผ๋กœ ํ•ด์„ํ•˜๋Š” ์„ค์ • ์ „์ฒด๊ฐ€ ๋ณต์›๋จ.
    gl.bindVertexArray(vao);

    // drawArrays(TRIANGLE_STRIP, 0, 4) : ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ GPU ํŒŒ์ดํ”„๋ผ์ธ์ด
    // ์ž๋™์œผ๋กœ ์‹คํ–‰๋จ.
    //   1) ์ •์  ์…ฐ์ด๋”๊ฐ€ 4๊ฐœ ์ •์  ๊ฐ๊ฐ์— ๋Œ€ํ•ด ํ•œ ๋ฒˆ์”ฉ ์‹คํ–‰๋˜์–ด
    //      ํด๋ฆฝ ๊ณต๊ฐ„ ์ขŒํ‘œ๋ฅผ ๊ฒฐ์ •.
    //   2) ์‚ผ๊ฐํ˜• ์ŠคํŠธ๋ฆฝ ๋ฐฉ์‹์œผ๋กœ ์ด 4๊ฐœ ์ •์ ์ด 2๊ฐœ์˜ ์‚ผ๊ฐํ˜•(=ํ™”๋ฉด
    //      ์ „์ฒด๋ฅผ ๋ฎ๋Š” ์‚ฌ๊ฐํ˜•)์œผ๋กœ ์กฐ๋ฆฝ๋จ.
    //   3) ๋ž˜์Šคํ„ฐํ™” ๋‹จ๊ณ„์—์„œ ์ด ์‚ผ๊ฐํ˜•๋“ค์ด ๋ทฐํฌํŠธ ํฌ๊ธฐ(1000x1000)์—
    //      ๋งž์ถฐ ๊ฐœ๋ณ„ ํ”ฝ์…€ ํ›„๋ณด๋กœ ์ชผ๊ฐœ์ง.
    //   4) ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๊ฐ€ ์ด ํ”ฝ์…€ ํ•˜๋‚˜ํ•˜๋‚˜์— ๋Œ€ํ•ด ๋…๋ฆฝ์ ์œผ๋กœ
    //      ์‹คํ–‰๋˜๋ฉฐ, ๋ฐฉ๊ธˆ ์ „๋‹ฌํ•œ u_resolution/u_time/u_mouse ๊ฐ’๊ณผ
    //      ํ”ฝ์…€ ์ขŒํ‘œ๋ฅผ ์กฐํ•ฉํ•ด ์ตœ์ข… ์ƒ‰์ƒ์„ ๊ณ„์‚ฐ.
    //   5) ๊ณ„์‚ฐ๋œ ์ƒ‰์ƒ์ด ํ”„๋ ˆ์ž„๋ฒ„ํผ(์ƒ‰์ƒ ๋ฒ„ํผ)์— ๊ธฐ๋ก๋˜๊ณ , ์ด ๋ฒ„ํผ๊ฐ€
    //      ๊ณง ์บ”๋ฒ„์Šค ํ™”๋ฉด์— ๊ทธ๋Œ€๋กœ ํ‘œ์‹œ๋จ.
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

    // requestAnimationFrame : ๋ธŒ๋ผ์šฐ์ €์˜ ๋‹ค์Œ ๋ฆฌํ”„๋ ˆ์‹œ ์ฃผ๊ธฐ์— ๋งž์ถฐ
    // render๋ฅผ ๋‹ค์‹œ ์˜ˆ์•ฝ. ์ด ์žฌ๊ท€ ํ˜ธ์ถœ ๊ตฌ์กฐ ๋•๋ถ„์— ๋‹ค์Œ ํ”„๋ ˆ์ž„์—์„œ
    // time ๊ฐ’์ด ๊ฐฑ์‹ ๋˜์–ด ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋งŒ๋“ค์–ด์ง.
    requestAnimationFrame(render);
  }

  render();
}

// init()์€ ๋น„๋™๊ธฐ ํ•จ์ˆ˜์ด๋ฏ€๋กœ, ๋‚ด๋ถ€์—์„œ ๋˜์ง„ ์˜ˆ์™ธ(fetch ์‹คํŒจ, ์ปดํŒŒ์ผ/๋งํฌ
// ์—๋Ÿฌ, attribute ๋ˆ„๋ฝ ๋“ฑ)๋Š” ์—ฌ๊ธฐ์„œ catchํ•ด์•ผ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์€ Promise
// ๊ฑฐ๋ถ€๋กœ ์ฝ˜์†”์—๋งŒ ์กฐ์šฉํžˆ ๋‚จ๋Š” ๊ฒƒ์„ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ์Œ.
init().catch((err) => {
  console.error(err);
  alert(`์ดˆ๊ธฐํ™” ์‹คํŒจ: ${err.message}`);
});

1. ์บ”๋ฒ„์Šค์™€ ์ปจํ…์ŠคํŠธ โ€” ๊ทธ๋ฆผ์„ ๊ทธ๋ฆด ๋Œ€์ƒ ํ™•๋ณด

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

<canvas> ์—˜๋ฆฌ๋จผํŠธ๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ด€๋ฆฌํ•˜๋Š” ํ”ฝ์…€ ๋ฒ„ํผ์˜ˆ์š”. getContext("webgl2")๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ด ์บ”๋ฒ„์Šค์— ์—ฐ๊ฒฐ๋œ WebGL2 ๋ Œ๋”๋ง ์ปจํ…์ŠคํŠธ(gl)๋ฅผ ์–ป๋Š”๋ฐ, ์ด ์‹œ์ ๋ถ€ํ„ฐ gl์€ GPU ๋“œ๋ผ์ด๋ฒ„์™€ ํ†ต์‹ ํ•˜๋Š” ์ฐฝ๊ตฌ ์—ญํ• ์„ ํ•ด์š”. ์ดํ›„ ๋ชจ๋“  GPU ๊ด€๋ จ ํ˜ธ์ถœ์€ gl.xxx() ํ˜•ํƒœ๋กœ ์ด ์ปจํ…์ŠคํŠธ๋ฅผ ๊ฑฐ์ณ์š”.

2. ๋ทฐํฌํŠธ ์„ค์ • โ€” ๋“œ๋กœ์ž‰ ๋ฒ„ํผ์˜ ์ขŒํ‘œ๊ณ„ ์ •์˜

canvas.width = 1000;
canvas.height = 1000;
gl.viewport(0, 0, canvas.width, canvas.height);

canvas.width/height๋Š” ์‹ค์ œ ํ”ฝ์…€์ด ์ €์žฅ๋˜๋Š” ๋“œ๋กœ์ž‰ ๋ฒ„ํผ์˜ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ •ํ•ด์š”. gl.viewport๋Š” ํด๋ฆฝ ๊ณต๊ฐ„ ์ขŒํ‘œ(-1~1 ๋ฒ”์œ„)๋ฅผ ์ด ๋“œ๋กœ์ž‰ ๋ฒ„ํผ์˜ ํ”ฝ์…€ ์ขŒํ‘œ๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ๋งคํ•‘ ๊ทœ์น™์„ ์„ค์ •ํ•˜๋Š” ํ˜ธ์ถœ์ด์—์š”. ์ด ์‹œ์ ์—๋Š” ์•„์ง ์•„๋ฌด๊ฒƒ๋„ ๊ทธ๋ ค์ง€์ง€ ์•Š์ง€๋งŒ, ์ดํ›„ ์ •์  ์ขŒํ‘œ๊ฐ€ ํ™”๋ฉด์˜ ์–ด๋А ์œ„์น˜์— ๋Œ€์‘ํ• ์ง€๊ฐ€ ์—ฌ๊ธฐ์„œ ๊ฒฐ์ •๋ผ์š”.

3. ์ž…๋ ฅ ์ƒํƒœ โ€” ๋‚˜์ค‘์— ์…ฐ์ด๋”๋กœ ํ˜๋Ÿฌ๊ฐˆ ๊ฐ’ ์ค€๋น„

canvas.addEventListener("mousemove", (e) => {
  mouseX = (e.clientX - rect.left) * scaleX;
  mouseY = canvas.height - (e.clientY - rect.top) * scaleY;
});

๋งˆ์šฐ์Šค ์ขŒํ‘œ๋Š” GPU์™€ ๋ฌด๊ด€ํ•˜๊ฒŒ JavaScript ๋ณ€์ˆ˜(mouseX, mouseY)์—๋งŒ ์ €์žฅ๋ผ์š”. ์ด ๊ฐ’ ์ž์ฒด๋Š” ์•„์ง GPU๋กœ ์ „๋‹ฌ๋˜์ง€ ์•Š์€ ์ƒํƒœ๊ณ , ๋ Œ๋” ๋ฃจํ”„์—์„œ ๋งค ํ”„๋ ˆ์ž„ ์œ ๋‹ˆํผ์œผ๋กœ ์—…๋กœ๋“œ๋  ๋•Œ ๋น„๋กœ์†Œ GPU ์ชฝ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ผ์š”. ์ฆ‰ ์ด ๋‹จ๊ณ„๋Š” "CPU ๋ฉ”๋ชจ๋ฆฌ ์•ˆ์—์„œ์˜ ์ƒํƒœ ๊ฐฑ์‹ "์ผ ๋ฟ์ด์—์š”.

4. ์…ฐ์ด๋” ์†Œ์Šค ๋กœ๋“œ โ€” ํ…์ŠคํŠธ ํŒŒ์ผ์„ ๋ฌธ์ž์—ด๋กœ

async function loadShader(url) {
  const response = await fetch(url);
  return await response.text();
}

.glsl ํŒŒ์ผ์€ GPU๊ฐ€ ๋ฐ”๋กœ ์ดํ•ดํ•˜๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์‚ฌ๋žŒ์ด ์ฝ์„ ์ˆ˜ ์žˆ๋Š” GLSL ์†Œ์Šค ์ฝ”๋“œ ํ…์ŠคํŠธ์˜ˆ์š”. fetch๋กœ ๊ฐ€์ ธ์˜จ ์‹œ์ ์—๋Š” ๊ทธ๋ƒฅ ๋ฌธ์ž์—ด์ด๊ณ , GPU๋Š” ์ด ๋‹จ๊ณ„์—์„œ ์•„๋ฌด๊ฒƒ๋„ ์•Œ์ง€ ๋ชปํ•ด์š”. GLSL ํ…์ŠคํŠธ๊ฐ€ ์‹ค์ œ GPU ๋ช…๋ น์œผ๋กœ ๋ฐ”๋€Œ๋Š” ๊ฑด ๋‹ค์Œ ๋‹จ๊ณ„์ธ ์ปดํŒŒ์ผ์—์„œ์˜ˆ์š”.

5. ์…ฐ์ด๋” ์ปดํŒŒ์ผ โ€” ํ…์ŠคํŠธ์—์„œ GPU ์‹คํ–‰ ๋‹จ์œ„๋กœ

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

์—ฌ๊ธฐ์„œ ์ฒ˜์Œ์œผ๋กœ ๋ฐ์ดํ„ฐ๊ฐ€ CPU์—์„œ GPU ์ชฝ์œผ๋กœ ๋„˜์–ด๊ฐ€์š”. createShader๋กœ GPU ๋“œ๋ผ์ด๋ฒ„์— ๋นˆ ์…ฐ์ด๋” ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค๊ณ , shaderSource๋กœ ๊ทธ ์•ˆ์— GLSL ํ…์ŠคํŠธ๋ฅผ ์ฑ„์šด ๋‹ค์Œ, compileShader๊ฐ€ GPU ๋“œ๋ผ์ด๋ฒ„ ๋‚ด๋ถ€์˜ ์ปดํŒŒ์ผ๋Ÿฌ๋ฅผ ํ˜ธ์ถœํ•ด ์ด ํ…์ŠคํŠธ๋ฅผ GPU๊ฐ€ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ํ˜•ํƒœ๋กœ ๋ณ€ํ™˜ํ•ด์š”. ์ •์  ์…ฐ์ด๋”์™€ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๋Š” ์ด ๊ณผ์ •์„ ๊ฐ๊ฐ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฑฐ์ณ์š”.

6. ํ”„๋กœ๊ทธ๋žจ ๋งํฌ โ€” ๋‘ ์…ฐ์ด๋”์˜ ์—ฐ๊ฒฐ

gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);

์ปดํŒŒ์ผ๋œ ์ •์  ์…ฐ์ด๋”์™€ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๋Š” ์•„์ง ์„œ๋กœ ๋ณ„๊ฐœ์˜ ๊ฐ์ฒด์˜ˆ์š”. linkProgram์€ ์ด ๋‘˜์„ ํ•˜๋‚˜์˜ ํŒŒ์ดํ”„๋ผ์ธ์œผ๋กœ ๋ฌถ์œผ๋ฉด์„œ, ์ •์  ์…ฐ์ด๋”๊ฐ€ out์œผ๋กœ ๋‚ด๋ณด๋‚ด๋Š” ๊ฐ’๊ณผ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๊ฐ€ in์œผ๋กœ ๋ฐ›๋Š” ๊ฐ’์˜ ์ด๋ฆ„ยทํƒ€์ž…์ด ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•ด์š”. ๋งํฌ๊ฐ€ ๋๋‚œ program ๊ฐ์ฒด๊ฐ€ ์ดํ›„ ๋ Œ๋”๋ง์˜ ์‹คํ–‰ ๋‹จ์œ„๊ฐ€ ๋ผ์š”.

7. ์ •์  ๋ฒ„ํผ โ€” CPU ๋ฐฐ์—ด์„ GPU ๋ฉ”๋ชจ๋ฆฌ๋กœ ์—…๋กœ๋“œ

const positions = new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW);

Float32Array๋Š” ์•„์ง JavaScript(CPU) ๋ฉ”๋ชจ๋ฆฌ์— ์žˆ๋Š” ์ˆซ์ž ๋ฐฐ์—ด์ด์—์š”. bufferData๊ฐ€ ํ˜ธ์ถœ๋˜๋Š” ์ˆœ๊ฐ„ ์ด ๋ฐ์ดํ„ฐ๊ฐ€ GPU ๋ฉ”๋ชจ๋ฆฌ๋กœ ๋ณต์‚ฌ๋ผ์š”. ์—ฌ๊ธฐ์„œ ์—…๋กœ๋“œํ•˜๋Š” ๊ฐ’์€ ํด๋ฆฝ ๊ณต๊ฐ„ ์ „์ฒด๋ฅผ ๋ฎ๋Š” ์‚ฌ๊ฐํ˜•(-1~1) ๋„ค ๊ผญ์ง“์ ์œผ๋กœ, ์ด ์ฝ”๋“œ๊ฐ€ ๋ณ„๋„์˜ 3D ์ง€์˜ค๋ฉ”ํŠธ๋ฆฌ ์—†์ด ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๋งŒ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“œ๋Š” ๊ตฌ์กฐ๋ผ๋Š” ๊ฑธ ๋ณด์—ฌ์ค˜์š”. ์ฆ‰ ์ •์  ๋ฐ์ดํ„ฐ์˜ ์—ญํ• ์€ "ํ™”๋ฉด์„ ๊ฐ€๋“ ์ฑ„์šฐ๋Š” ๊ทธ๋ฆผํŒ์„ ํŽผ์น˜๋Š” ๊ฒƒ"๋ฟ์ด๊ณ , ์‹ค์ œ ์ด๋ฏธ์ง€ ํ˜•ํƒœ๋Š” ๋‹ค์Œ ๋‹จ๊ณ„ ์ดํ›„ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๊ฐ€ ๊ฒฐ์ •ํ•ด์š”.

8. Attribute ์—ฐ๊ฒฐ โ€” GPU ๋ฒ„ํผ์™€ ์…ฐ์ด๋” ์ž…๋ ฅ์˜ ๋งคํ•‘

const a_position = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 2, gl.FLOAT, false, 0, 0);

GPU ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ผ๊ฐ„ ๋ฒ„ํผ๋Š” ๊ทธ ์ž์ฒด๋กœ๋Š” ์…ฐ์ด๋”๊ฐ€ ์–ด๋–ป๊ฒŒ ์ฝ์–ด์•ผ ํ• ์ง€ ๋ชจ๋ฅด๋Š” ๋ฐ”์ดํŠธ ๋ฉ์–ด๋ฆฌ์˜ˆ์š”. vertexAttribPointer๋Š” "์ด ๋ฒ„ํผ๋ฅผ 2๊ฐœ์”ฉ(float 2๊ฐœ, ์ฆ‰ x/y) ๋Š์–ด์„œ a_position์ด๋ผ๋Š” ์ด๋ฆ„์˜ ์ •์  ์…ฐ์ด๋” ์ž…๋ ฅ์— ํ•˜๋‚˜์”ฉ ๋„ฃ์–ด๋ผ"๋Š” ํ•ด์„ ๊ทœ์น™์„ ์ •์˜ํ•˜๋Š” ํ˜ธ์ถœ์ด์—์š”. ์ด ๋งคํ•‘ ์ •๋ณด๋Š” VAO(Vertex Array Object)์— ๊ธฐ๋ก๋˜์–ด, ์ดํ›„ bindVertexArray(vao) ํ•œ ๋ฒˆ์œผ๋กœ ์ด ์„ค์ • ์ „์ฒด๋ฅผ ๋‹ค์‹œ ๋ถˆ๋Ÿฌ์˜ฌ ์ˆ˜ ์žˆ์–ด์š”.

9. Uniform ์œ„์น˜ ์กฐํšŒ โ€” ๋งค ํ”„๋ ˆ์ž„ ๋ฐ”๋€Œ๋Š” ์ „์—ญ ์ž…๋ ฅ ์ค€๋น„

const u_resolution = gl.getUniformLocation(program, "u_resolution");
const u_time = gl.getUniformLocation(program, "u_time");
const u_mouse = gl.getUniformLocation(program, "u_mouse");

Attribute๊ฐ€ "์ •์ ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฐ’"์„ ์…ฐ์ด๋”์— ์ „๋‹ฌํ•˜๋Š” ํ†ต๋กœ๋ผ๋ฉด, Uniform์€ "ํ•œ ๋ฒˆ์˜ ๋“œ๋กœ์šฐ ์ฝœ ๋™์•ˆ ๋ชจ๋“  ์ •์ ยทํ”ฝ์…€์— ๋™์ผํ•˜๊ฒŒ ์ ์šฉ๋˜๋Š” ๊ฐ’"์„ ์ „๋‹ฌํ•˜๋Š” ํ†ต๋กœ์˜ˆ์š”. ์—ฌ๊ธฐ์„œ๋Š” ์œ„์น˜(location)๋งŒ ๋ฏธ๋ฆฌ ์กฐํšŒํ•ด๋‘๊ณ , ์‹ค์ œ ๊ฐ’์€ ์•„์ง ์ „๋‹ฌํ•˜์ง€ ์•Š์•„์š”. ์œ„์น˜๋Š” ํ”„๋กœ๊ทธ๋žจ์ด ์กด์žฌํ•˜๋Š” ๋™์•ˆ ๋ณ€ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋” ๋ฃจํ”„ ๋ฐ–์—์„œ ํ•œ ๋ฒˆ๋งŒ ๊ตฌํ•ด๋‘๋ฉด ์ถฉ๋ถ„ํ•ด์š”.

10. ํ”„๋กœ๊ทธ๋žจ ํ™œ์„ฑํ™” โ€” GPU ํŒŒ์ดํ”„๋ผ์ธ์— ์‹คํ–‰ ๋Œ€์ƒ ์ง€์ •

gl.useProgram(program);

์ง€๊ธˆ๊นŒ์ง€ ๋งŒ๋“  ์…ฐ์ด๋” ํ”„๋กœ๊ทธ๋žจ์„ GPU๊ฐ€ ์‹ค์ œ๋กœ ์‹คํ–‰ํ•  ๋Œ€์ƒ์œผ๋กœ ์ง€์ •ํ•˜๋Š” ๋‹จ๊ณ„์˜ˆ์š”. ์ดํ›„ drawArrays๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด GPU๋Š” ์—ฌ๊ธฐ์„œ ์ง€์ •๋œ ํ”„๋กœ๊ทธ๋žจ์„ ๊ธฐ์ค€์œผ๋กœ ํŒŒ์ดํ”„๋ผ์ธ์„ ์‹คํ–‰ํ•ด์š”.

11. ๋ Œ๋” ๋ฃจํ”„ โ€” ๋งค ํ”„๋ ˆ์ž„ ๋ฐ˜๋ณต๋˜๋Š” ๋ฐ์ดํ„ฐ ํ๋ฆ„

function render() {
  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);

  gl.uniform2f(u_resolution, canvas.width, canvas.height);
  gl.uniform1f(u_time, time);
  gl.uniform2f(u_mouse, mouseX, mouseY);

  gl.bindVertexArray(vao);
  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

  requestAnimationFrame(render);
}

์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ๊ฐ€ ์‹ค์ œ๋กœ "์ด๋ฏธ์ง€๊ฐ€ ๋งŒ๋“ค์–ด์ง€๋Š”" ๊ตฌ๊ฐ„์ด์—์š”. ๊ฐ ํ˜ธ์ถœ์ด ๋ฐ์ดํ„ฐ๋ฅผ ์–ด๋–ป๊ฒŒ ํ˜๋ ค๋ณด๋‚ด๋Š”์ง€ ์ˆœ์„œ๋Œ€๋กœ ๋ณด๋ฉด:

  1. clear โ€” ์ด์ „ ํ”„๋ ˆ์ž„์— ๊ทธ๋ ค์ง„ ์ƒ‰์ƒ ๋ฒ„ํผ ๋‚ด์šฉ์„ ์ง€์ •ํ•œ ๋ฐฐ๊ฒฝ์ƒ‰์œผ๋กœ ์ดˆ๊ธฐํ™”ํ•ด์š”.
  2. uniform2f/uniform1f โ€” CPU ์ชฝ ๋ณ€์ˆ˜(canvas.width, time, mouseX/Y)์˜ ํ˜„์žฌ ๊ฐ’์„ GPU์˜ ์œ ๋‹ˆํผ ์Šฌ๋กฏ์— ๋ณต์‚ฌํ•ด์š”. ์ด ์‹œ์ ์— 3๋‹จ๊ณ„์—์„œ ๊ฐฑ์‹ ๋˜๋˜ ๋งˆ์šฐ์Šค ์ขŒํ‘œ๊ฐ€ ๋น„๋กœ์†Œ GPU๋กœ ์ „๋‹ฌ๋ผ์š”.
  3. bindVertexArray(vao) โ€” 7~8๋‹จ๊ณ„์—์„œ ๊ธฐ๋กํ•ด๋‘” ์ •์  ๋ฐ์ดํ„ฐ ํ•ด์„ ๊ทœ์น™์„ ๋‹ค์‹œ ํ™œ์„ฑํ™”ํ•ด์š”.
  4. drawArrays(TRIANGLE_STRIP, 0, 4) โ€” ์—ฌ๊ธฐ์„œ๋ถ€ํ„ฐ GPU ๋‚ด๋ถ€ ํŒŒ์ดํ”„๋ผ์ธ์ด ์ž๋™์œผ๋กœ ์‹คํ–‰๋ผ์š”.
    • ์ •์  ์…ฐ์ด๋”๊ฐ€ ์—…๋กœ๋“œ๋œ 4๊ฐœ ์ •์  ๊ฐ๊ฐ์— ๋Œ€ํ•ด ํ•œ ๋ฒˆ์”ฉ ์‹คํ–‰๋˜๋ฉฐ, ํด๋ฆฝ ๊ณต๊ฐ„ ์ขŒํ‘œ๋ฅผ ๊ฒฐ์ •ํ•ด์š”.
    • ํ”„๋ฆฌ๋ฏธํ‹ฐ๋ธŒ ์กฐ๋ฆฝ์ด ์ด 4๊ฐœ ์ •์ ์„ ์‚ผ๊ฐํ˜• ์ŠคํŠธ๋ฆฝ ๋ฐฉ์‹์œผ๋กœ ๋ฌถ์–ด 2๊ฐœ์˜ ์‚ผ๊ฐํ˜•(ํ™”๋ฉด ์ „์ฒด๋ฅผ ๋ฎ๋Š” ์‚ฌ๊ฐํ˜•)์„ ๊ตฌ์„ฑํ•ด์š”.
    • ๋ž˜์Šคํ„ฐํ™” ๋‹จ๊ณ„์—์„œ ์ด ์‚ผ๊ฐํ˜•๋“ค์ด ๋ทฐํฌํŠธ์— ๋งคํ•‘๋œ ๊ฐœ๋ณ„ ํ”ฝ์…€๋กœ ์ชผ๊ฐœ์ ธ์š”. ์บ”๋ฒ„์Šค๊ฐ€ 1000ร—1000์ด๋ฏ€๋กœ ์ตœ๋Œ€ 100๋งŒ ๊ฐœ์˜ ํ”ฝ์…€ ํ›„๋ณด๊ฐ€ ์ƒ๊ฒจ์š”.
    • ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๊ฐ€ ์ด ํ”ฝ์…€ ํ•˜๋‚˜ํ•˜๋‚˜์— ๋Œ€ํ•ด ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋ผ์š”. ์ด๋•Œ 2๋‹จ๊ณ„์—์„œ ์ „๋‹ฌ๋œ u_resolution, u_time, u_mouse ๊ฐ’๊ณผ ํ”ฝ์…€ ์ขŒํ‘œ(gl_FragCoord ๋“ฑ)๋ฅผ ์กฐํ•ฉํ•ด ๊ฐ ํ”ฝ์…€์˜ ์ตœ์ข… ์ƒ‰์ƒ์„ ๊ณ„์‚ฐํ•ด์š”. ์…ฐ์ด๋” ์•„ํŠธ์˜ ์‹ค์งˆ์ ์ธ "๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋Š” ๋กœ์ง"์€ ์ „๋ถ€ ์ด ๋‹จ๊ณ„, ์ฆ‰ ํ•จ๊ป˜ ์ฒจ๋ถ€ํ•œ GLSL ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋” ์ฝ”๋“œ ์•ˆ์— ์žˆ์–ด์š”.
    • ๊ณ„์‚ฐ๋œ ์ƒ‰์ƒ์ด ์ƒ‰์ƒ ๋ฒ„ํผ(ํ”„๋ ˆ์ž„๋ฒ„ํผ) ์— ๊ธฐ๋ก๋˜๊ณ , ์ด ๋ฒ„ํผ๊ฐ€ ๊ณง ์บ”๋ฒ„์Šค ํ™”๋ฉด์— ํ‘œ์‹œ๋ผ์š”.
  5. requestAnimationFrame(render) โ€” ๋ธŒ๋ผ์šฐ์ €์˜ ๋‹ค์Œ ๋ฆฌํ”„๋ ˆ์‹œ ์ฃผ๊ธฐ์— ๋งž์ถฐ render๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœํ•˜๋„๋ก ์˜ˆ์•ฝํ•ด์š”. ์ด ์žฌ๊ท€์  ํ˜ธ์ถœ ๊ตฌ์กฐ ๋•๋ถ„์— u_time ๊ฐ’์ด ๋งค ํ”„๋ ˆ์ž„ ๊ฐฑ์‹ ๋˜๋ฉฐ ์• ๋‹ˆ๋ฉ”์ด์…˜์ด ๋งŒ๋“ค์–ด์ ธ์š”.

์ •๋ฆฌ โ€” ์ „์ฒด ๋ฐ์ดํ„ฐ ํ๋ฆ„ ํ•œ๋ˆˆ์— ๋ณด๊ธฐ

๋‹จ๊ณ„๋ฐ์ดํ„ฐ์˜ ์œ„์น˜ํ˜•ํƒœ
์…ฐ์ด๋” ํŒŒ์ผ ๋กœ๋“œCPUํ…์ŠคํŠธ ๋ฌธ์ž์—ด
์…ฐ์ด๋” ์ปดํŒŒ์ผ/๋งํฌCPU โ†’ GPUGPU ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ ํ”„๋กœ๊ทธ๋žจ ๊ฐ์ฒด
์ •์  ๋ฒ„ํผ ์—…๋กœ๋“œCPU โ†’ GPUFloat32Array โ†’ GPU ๋ฉ”๋ชจ๋ฆฌ
Attribute ์—ฐ๊ฒฐGPU ๋‚ด๋ถ€๋ฒ„ํผ ๋ฐ”์ดํŠธ๋ฅผ ์ •์  ์…ฐ์ด๋” ์ž…๋ ฅ์œผ๋กœ ํ•ด์„ํ•˜๋Š” ๊ทœ์น™
Uniform ๊ฐ’ ์ „๋‹ฌCPU โ†’ GPU๋งค ํ”„๋ ˆ์ž„ ๊ฐฑ์‹ ๋˜๋Š” ์Šค์นผ๋ผ/๋ฒกํ„ฐ ๊ฐ’
์ •์  ์…ฐ์ด๋” ์‹คํ–‰GPU์ •์ ๋ณ„ ํด๋ฆฝ ๊ณต๊ฐ„ ์ขŒํ‘œ ๊ณ„์‚ฐ
๋ž˜์Šคํ„ฐํ™”GPU์‚ผ๊ฐํ˜•์„ ํ”ฝ์…€ ๋‹จ์œ„๋กœ ๋ถ„ํ•ด
ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋” ์‹คํ–‰GPUํ”ฝ์…€๋ณ„ ์ƒ‰์ƒ ๊ณ„์‚ฐ
ํ”„๋ ˆ์ž„๋ฒ„ํผ ๊ธฐ๋กGPU โ†’ ํ™”๋ฉด์ตœ์ข… ์ด๋ฏธ์ง€

๊ฒฐ๊ตญ WebGL2 ์…ฐ์ด๋” ์•„ํŠธ๋Š” "CPU์—์„œ ์ค€๋น„ํ•œ ์†Œ๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ(์ •์  4๊ฐœ, ์œ ๋‹ˆํผ ๋ช‡ ๊ฐœ)๋ฅผ GPU์— ๋„˜๊ธฐ๊ณ , ๊ทธ ์‚ฌ์ด์˜ ์‹ค์งˆ์ ์ธ ์ด๋ฏธ์ง€ ์—ฐ์‚ฐ์€ ํ”„๋ž˜๊ทธ๋จผํŠธ ์…ฐ์ด๋”๊ฐ€ ํ”ฝ์…€ ๋‹จ์œ„ ๋ณ‘๋ ฌ ์—ฐ์‚ฐ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š”" ๊ตฌ์กฐ์˜ˆ์š”. JS ์ฝ”๋“œ ์ชฝ์€ ์ด ํŒŒ์ดํ”„๋ผ์ธ์„ ๋งค ํ”„๋ ˆ์ž„ ์˜ฌ๋ฐ”๋ฅธ ์ˆœ์„œ๋กœ ํ˜ธ์ถœํ•ด์ฃผ๋Š” ์—ญํ• ์— ๊ฐ€๊น๊ณ , ์‹ค์ œ ์‹œ๊ฐ์  ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐ์ •ํ•˜๋Š” ๋กœ์ง์€ GLSL ์…ฐ์ด๋” ์ฝ”๋“œ ์ชฝ์— ์žˆ์–ด์š”.

profile
Coding Art with Blender / oF / Processing / p5.js / nannou

0๊ฐœ์˜ ๋Œ“๊ธ€