๐Ÿงฑ Boilerplate : Yellow Triangle

BamgasiJMยท2026๋…„ 6์›” 26์ผ

WebGL

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

๐Ÿ“ index.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>WebGL2 Basic Triangle</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <canvas id="glCanvas" width="800" height="600"></canvas>
    <script src="app.js"></script>
</body>
</html>

๐Ÿ“ style.css

body {
    margin: 0;
    padding: 0;
    background-color: rgb(44, 44, 44);
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    overflow: hidden;
}

canvas {
    display: block;
    margin: 0 auto;
}

๐Ÿ“ app.js

/**
 * ============================================================================
 * [WebGL2 ๊ธฐ์ดˆ: ์‚ผ๊ฐํ˜• ๋ Œ๋”๋ง ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌํ˜„]
 * ๋ชฉ์ : WebGL2 ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ , Shader Program, VAO, VBO๋ฅผ ๊ตฌ์„ฑํ•˜์—ฌ
 *       GPU ๋ฉ”๋ชจ๋ฆฌ์— ์ •์  ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๊ณ  ํ™”๋ฉด์— ์‚ผ๊ฐํ˜•์„ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
 * ============================================================================
 */

// --- [1. ์ดˆ๊ธฐ ์„ค์ • ๋ฐ ์ปจํ…์ŠคํŠธ ํš๋“] ---
// HTML5 Canvas ์—˜๋ฆฌ๋จผํŠธ๋ฅผ ์ฐธ์กฐํ•˜๊ณ , WebGL2 ๊ทธ๋ž˜ํ”ฝ API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ปจํ…์ŠคํŠธ๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
const canvas = document.getElementById("glCanvas");
const gl = canvas.getContext("webgl2");

// ๋งŒ์•ฝ WebGL2๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €๋ผ๋ฉด ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
if (!gl) {
  console.error("WebGL2๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๋ธŒ๋ผ์šฐ์ €์ž…๋‹ˆ๋‹ค.");
}

// --- [2. ์…ฐ์ด๋” ์†Œ์Šค ์ฝ”๋“œ ์ •์˜ (GLSL 3.00 es)] ---

/**
 * [Vertex Shader]
 * ์—ญํ• : ๊ฐ ์ •์ (Vertex)์˜ ์ขŒํ‘œ๋ฅผ ๊ฒฐ์ •ํ•˜๋ฉฐ, ํด๋ฆฝ ๊ณต๊ฐ„(Clip Space)์œผ๋กœ ๋ณ€ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
 * - layout(location = 0): CPU์—์„œ ์ „๋‹ฌํ•˜๋Š” ์ •์  ์†์„ฑ(attribute)์˜ ์ธ๋ฑ์Šค๋ฅผ 0๋ฒˆ์œผ๋กœ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
 * - a_position: ์ž…๋ ฅ๋ฐ›๋Š” 2์ฐจ์› ์ขŒํ‘œ(vec2)์ž…๋‹ˆ๋‹ค.
 * - gl_Position: GPU ๋‚ด๋ถ€ ๋‚ด์žฅ ๋ณ€์ˆ˜๋กœ, ์ตœ์ข… 4์ฐจ์› ์ขŒํ‘œ(vec4)๋ฅผ ์ „๋‹ฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
 */
const vsSource = `#version 300 es
layout(location = 0) in vec2 a_position;
void main() {
    // vec2 ์ขŒํ‘œ๋ฅผ vec4(x, y, z, w)๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ์ „๋‹ฌ (z=0.0, w=1.0)
    gl_Position = vec4(a_position, 0.0, 1.0);
}`;

/**
 * [Fragment Shader]
 * ์—ญํ• : ๋ž˜์Šคํ„ฐํ™”(Rasterization)๋œ ๊ฐ ํ”ฝ์…€(Fragment)์˜ ์ตœ์ข… ์ƒ‰์ƒ์„ ๊ฒฐ์ •ํ•ฉ๋‹ˆ๋‹ค.
 * - precision mediump float: ๋ถ€๋™ ์†Œ์ˆ˜์  ์—ฐ์‚ฐ์˜ ์ •๋ฐ€๋„๋ฅผ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
 * - outColor: ํ”ฝ์…€์˜ ์ƒ‰์ƒ ๊ฐ’์„ ์ถœ๋ ฅํ•  ์‚ฌ์šฉ์ž ์ •์˜ ๋ณ€์ˆ˜์ž…๋‹ˆ๋‹ค.
 */
const fsSource = `#version 300 es
precision mediump float;
out vec4 outColor;
void main() {
    // RGBA ๊ฐ’ ์„ค์ •: R=1.0(Full), G=0.5(Half), B=0.0(None), A=1.0(Opaque) -> ์ฃผํ™ฉ์ƒ‰
    outColor = vec4(1.0, 0.5, 0.0, 1.0);
}`;

// --- [3. ์…ฐ์ด๋” ์ปดํŒŒ์ผ ๋ฐ ํ”„๋กœ๊ทธ๋žจ ๋งํฌ ํ•จ์ˆ˜] ---

/**
 * @function createShader
 * @description ๊ฐœ๋ณ„ ์…ฐ์ด๋”(Vertex ๋˜๋Š” Fragment)๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ์ปดํŒŒ์ผํ•ฉ๋‹ˆ๋‹ค.
 * @param {WebGL2RenderingContext} gl - WebGL ์ปจํ…์ŠคํŠธ
 * @param {number} type - gl.VERTEX_SHADER ๋˜๋Š” gl.FRAGMENT_SHADER ํƒ€์ž…
 * @param {string} source - GLSL ์†Œ์Šค ์ฝ”๋“œ ๋ฌธ์ž์—ด
 * @returns {WebGLShader} ์ปดํŒŒ์ผ๋œ ์…ฐ์ด๋” ๊ฐ์ฒด
 */
function createShader(gl, type, source) {
  const shader = gl.createShader(type); // 1. ์…ฐ์ด๋” ๊ฐ์ฒด ์ƒ์„ฑ
  gl.shaderSource(shader, source); // 2. ์†Œ์Šค ์ฝ”๋“œ ํ• ๋‹น
  gl.compileShader(shader); // 3. GPU๊ฐ€ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๋ฐ”์ด๋„ˆ๋ฆฌ๋กœ ์ปดํŒŒ์ผ

  // ์ปดํŒŒ์ผ ์—๋Ÿฌ ์ฒดํฌ (์‹ค๋ฌด์—์„œ ๋งค์šฐ ์ค‘์š”)
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error("Shader compile error:", gl.getShaderInfoLog(shader));
  }
  return shader;
}

// ์…ฐ์ด๋” ์ปดํŒŒ์ผ ์‹คํ–‰
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);

/**
 * [Shader Program ์ƒ์„ฑ ๋ฐ ๋งํฌ]
 * ์—ญํ• : ์ปดํŒŒ์ผ๋œ Vertex Shader์™€ Fragment Shader๋ฅผ ํ•˜๋‚˜์˜ ์‹คํ–‰ ๊ฐ€๋Šฅํ•œ 'ํ”„๋กœ๊ทธ๋žจ'์œผ๋กœ ๊ฒฐํ•ฉํ•ฉ๋‹ˆ๋‹ค.
 */
const program = gl.createProgram(); // 1. ํ”„๋กœ๊ทธ๋žจ ๊ฐ์ฒด ์ƒ์„ฑ
gl.attachShader(program, vertexShader); // 2. Vertex Shader ์—ฐ๊ฒฐ
gl.attachShader(program, fragmentShader); // 3. Fragment Shader ์—ฐ๊ฒฐ
gl.linkProgram(program); // 4. ๋‘ ์…ฐ์ด๋”๋ฅผ ์—ฐ๊ฒฐ(Link)ํ•˜์—ฌ ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์„ฑ

// ๋งํฌ ์—๋Ÿฌ ์ฒดํฌ
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
  console.error("Program link error:", gl.getProgramInfoLog(program));
}

// --- [4. ๋ฐ์ดํ„ฐ ์ค€๋น„ ๋ฐ ๋ฒ„ํผ ๊ด€๋ฆฌ (Memory Management)] ---

/**
 * [์ •์  ๋ฐ์ดํ„ฐ ์ •์˜]
 * ์ขŒํ‘œ๊ณ„: Clip Space (-1.0 ~ 1.0 ๋ฒ”์œ„)
 * 1. (0.0, 0.5)   : ์ƒ๋‹จ ์ค‘์•™
 * 2. (-0.5, -0.5) : ์ขŒํ•˜๋‹จ
 * 3. (0.5, -0.5)  : ์šฐํ•˜๋‹จ
 */
const vertices = new Float32Array([0.0, 0.5, -0.5, -0.5, 0.5, -0.5]);

/**
 * [VAO (Vertex Array Object) ์„ค์ •]
 * ์—ญํ• : VBO์˜ ๋ฐ”์ธ๋”ฉ ์ƒํƒœ์™€ ์ •์  ์†์„ฑ(Attribute)์˜ ๋ ˆ์ด์•„์›ƒ ์ •๋ณด๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์–ด ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
 * ์ด ๊ฐ์ฒด๋ฅผ ๋ฐ”์ธ๋”ฉํ•˜๋ฉด ์ด์ „์˜ ๋ณต์žกํ•œ ์ •์  ์„ค์ • ๊ณผ์ •์„ ํ•œ ๋ฒˆ์— ๋ณต๊ตฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
 */
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

/**
 * [VBO (Vertex Buffer Object) ์„ค์ •]
 * ์—ญํ• : CPU์— ์žˆ๋Š” ์ •์  ๋ฐฐ์—ด ๋ฐ์ดํ„ฐ๋ฅผ GPU์˜ ๋น„๋””์˜ค ๋ฉ”๋ชจ๋ฆฌ(VRAM)๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.
 */
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo); // 1. ๋ฒ„ํผ๋ฅผ ARRAY_BUFFER ํƒ€์ž…์œผ๋กœ ๋ฐ”์ธ๋”ฉ
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); // 2. ๋ฐ์ดํ„ฐ๋ฅผ GPU ๋ฉ”๋ชจ๋ฆฌ์— ํ• ๋‹น (์ •์  ๋ฐ์ดํ„ฐ)

/**
 * [Vertex Attribute Pointer ์„ค์ •]
 * ์—ญํ• : GPU๊ฐ€ VBO ๋‚ด๋ถ€์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด์„ํ•˜๋Š” ๋ฐฉ์‹์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
 * @param {number} index - shader์˜ layout(location = 0)์— ์ง€์ •๋œ ์ธ๋ฑ์Šค
 * @param {number} size - ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์„ฑ ์š”์†Œ ๊ฐœ์ˆ˜ (์—ฌ๊ธฐ์„œ๋Š” x, y ์ด๋ฏ€๋กœ 2)
 * @param {number} type - ๋ฐ์ดํ„ฐ ์ž๋ฃŒํ˜• (gl.FLOAT)
 * @param {boolean} normalized - ์ •๊ทœํ™” ์—ฌ๋ถ€
 * @param {number} stride - ๋ฐ์ดํ„ฐ ๊ฐ„์˜ ๊ฐ„๊ฒฉ (bytes ๋‹จ์œ„, 0์ด๋ฉด ์ž๋™์œผ๋กœ ๊ณ„์‚ฐ)
 * @param {number} offset - ๋ฐ์ดํ„ฐ์˜ ์‹œ์ž‘ ์œ„์น˜ (bytes ๋‹จ์œ„)
 */
gl.enableVertexAttribArray(0); // 0๋ฒˆ ์†์„ฑ(a_position) ํ™œ์„ฑํ™”
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

// --- [5. ๋ Œ๋”๋ง ์‹คํ–‰ (Rendering Loop/Process)] ---

// 1. ํ™”๋ฉด ์ดˆ๊ธฐํ™”: ๋ฐฐ๊ฒฝ์ƒ‰์„ ๊ฒ€์€์ƒ‰(RGBA: 0,0,0,1)์œผ๋กœ ์„ค์ •ํ•˜๊ณ  ๋ฒ„ํผ๋ฅผ ๋น„์›๋‹ˆ๋‹ค.
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);

// 2. ์‚ฌ์šฉํ•  ์…ฐ์ด๋” ํ”„๋กœ๊ทธ๋žจ ๊ฒฐ์ •
gl.useProgram(program);

// 3. ์„ค์ •๋œ VAO ๋ฐ”์ธ๋”ฉ (์ •์  ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ GPU์— ์•Œ๋ฆผ)
gl.bindVertexArray(vao);

// 4. ๊ทธ๋ฆฌ๊ธฐ ๋ช…๋ น ์‹คํ–‰: ์‚ผ๊ฐํ˜•(TRIANGLES)์„ 0๋ฒˆ ์ธ๋ฑ์Šค๋ถ€ํ„ฐ 3๊ฐœ์˜ ์ •์ ์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ทธ๋ฆฝ๋‹ˆ๋‹ค.
gl.drawArrays(gl.TRIANGLES, 0, 3);

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

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