
<!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>
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;
}
/**
* ============================================================================
* [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);