
WebGL2๋ก ์ ฐ์ด๋ ์ํธ๋ฅผ ๋ง๋ค ๋ ํํ "์ ฐ์ด๋๋ง ์ ์ง๋ฉด ๋๋ค"๊ณ ์๊ฐํ๊ธฐ ์ฝ์ง๋ง, ์ค์ ๋ก ํ๋ฉด์ ํฝ์ ํ๋๊ฐ ์ฐํ๊ธฐ๊น์ง๋ CPU์ GPU ์ฌ์ด๋ฅผ ์ค๊ฐ๋ ์ฌ๋ฌ ๋จ๊ณ์ ๋ฐ์ดํฐ ๋ณํ์ด ์์ด์. ์ด ๊ธ์ ์ฝ๋์ ์คํ ์์๋ฅผ ๊ทธ๋๋ก ๋ฐ๋ผ๊ฐ๋ฉด์, ๊ฐ ๋จ๊ณ์์ ๋ฐ์ดํฐ๊ฐ ์ด๋ค ํํ๋ก ์กด์ฌํ๊ณ ๋ค์ ๋จ๊ณ๋ก ์ด๋ป๊ฒ ์ ๋ฌ๋๋์ง๋ฅผ ์ ๋ฆฌํ ๊ธ์ด์์.
// ========================================
// ์บ๋ฒ์ค ์๋ฆฌ๋จผํธ ์ฐธ์กฐ
// ========================================
// 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}`);
});
const canvas = document.getElementById("webgl_canvas");
const gl = canvas.getContext("webgl2");
<canvas> ์๋ฆฌ๋จผํธ๋ ๋ธ๋ผ์ฐ์ ๊ฐ ๊ด๋ฆฌํ๋ ํฝ์
๋ฒํผ์์. getContext("webgl2")๋ฅผ ํธ์ถํ๋ฉด ์ด ์บ๋ฒ์ค์ ์ฐ๊ฒฐ๋ WebGL2 ๋ ๋๋ง ์ปจํ
์คํธ(gl)๋ฅผ ์ป๋๋ฐ, ์ด ์์ ๋ถํฐ gl์ GPU ๋๋ผ์ด๋ฒ์ ํต์ ํ๋ ์ฐฝ๊ตฌ ์ญํ ์ ํด์. ์ดํ ๋ชจ๋ GPU ๊ด๋ จ ํธ์ถ์ gl.xxx() ํํ๋ก ์ด ์ปจํ
์คํธ๋ฅผ ๊ฑฐ์ณ์.
canvas.width = 1000;
canvas.height = 1000;
gl.viewport(0, 0, canvas.width, canvas.height);
canvas.width/height๋ ์ค์ ํฝ์
์ด ์ ์ฅ๋๋ ๋๋ก์ ๋ฒํผ์ ํฌ๊ธฐ๋ฅผ ๊ฒฐ์ ํด์. gl.viewport๋ ํด๋ฆฝ ๊ณต๊ฐ ์ขํ(-1~1 ๋ฒ์)๋ฅผ ์ด ๋๋ก์ ๋ฒํผ์ ํฝ์
์ขํ๋ก ๋ณํํ๋ ๋งคํ ๊ท์น์ ์ค์ ํ๋ ํธ์ถ์ด์์. ์ด ์์ ์๋ ์์ง ์๋ฌด๊ฒ๋ ๊ทธ๋ ค์ง์ง ์์ง๋ง, ์ดํ ์ ์ ์ขํ๊ฐ ํ๋ฉด์ ์ด๋ ์์น์ ๋์ํ ์ง๊ฐ ์ฌ๊ธฐ์ ๊ฒฐ์ ๋ผ์.
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 ๋ฉ๋ชจ๋ฆฌ ์์์์ ์ํ ๊ฐฑ์ "์ผ ๋ฟ์ด์์.
async function loadShader(url) {
const response = await fetch(url);
return await response.text();
}
.glsl ํ์ผ์ GPU๊ฐ ๋ฐ๋ก ์ดํดํ๋ ๋ฐ์ด๋๋ฆฌ๊ฐ ์๋๋ผ ์ฌ๋์ด ์ฝ์ ์ ์๋ GLSL ์์ค ์ฝ๋ ํ
์คํธ์์. fetch๋ก ๊ฐ์ ธ์จ ์์ ์๋ ๊ทธ๋ฅ ๋ฌธ์์ด์ด๊ณ , GPU๋ ์ด ๋จ๊ณ์์ ์๋ฌด๊ฒ๋ ์์ง ๋ชปํด์. GLSL ํ
์คํธ๊ฐ ์ค์ GPU ๋ช
๋ น์ผ๋ก ๋ฐ๋๋ ๊ฑด ๋ค์ ๋จ๊ณ์ธ ์ปดํ์ผ์์์์.
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
์ฌ๊ธฐ์ ์ฒ์์ผ๋ก ๋ฐ์ดํฐ๊ฐ CPU์์ GPU ์ชฝ์ผ๋ก ๋์ด๊ฐ์. createShader๋ก GPU ๋๋ผ์ด๋ฒ์ ๋น ์
ฐ์ด๋ ๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ณ , shaderSource๋ก ๊ทธ ์์ GLSL ํ
์คํธ๋ฅผ ์ฑ์ด ๋ค์, compileShader๊ฐ GPU ๋๋ผ์ด๋ฒ ๋ด๋ถ์ ์ปดํ์ผ๋ฌ๋ฅผ ํธ์ถํด ์ด ํ
์คํธ๋ฅผ GPU๊ฐ ์คํ ๊ฐ๋ฅํ ํํ๋ก ๋ณํํด์. ์ ์ ์
ฐ์ด๋์ ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋๋ ์ด ๊ณผ์ ์ ๊ฐ๊ฐ ๋
๋ฆฝ์ ์ผ๋ก ๊ฑฐ์ณ์.
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
์ปดํ์ผ๋ ์ ์ ์
ฐ์ด๋์ ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋๋ ์์ง ์๋ก ๋ณ๊ฐ์ ๊ฐ์ฒด์์. linkProgram์ ์ด ๋์ ํ๋์ ํ์ดํ๋ผ์ธ์ผ๋ก ๋ฌถ์ผ๋ฉด์, ์ ์ ์
ฐ์ด๋๊ฐ out์ผ๋ก ๋ด๋ณด๋ด๋ ๊ฐ๊ณผ ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋๊ฐ in์ผ๋ก ๋ฐ๋ ๊ฐ์ ์ด๋ฆยทํ์
์ด ์ผ์นํ๋์ง ๊ฒ์ฆํด์. ๋งํฌ๊ฐ ๋๋ program ๊ฐ์ฒด๊ฐ ์ดํ ๋ ๋๋ง์ ์คํ ๋จ์๊ฐ ๋ผ์.
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 ์ง์ค๋ฉํธ๋ฆฌ ์์ด ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋๋ง์ผ๋ก ์ด๋ฏธ์ง๋ฅผ ๋ง๋๋ ๊ตฌ์กฐ๋ผ๋ ๊ฑธ ๋ณด์ฌ์ค์. ์ฆ ์ ์ ๋ฐ์ดํฐ์ ์ญํ ์ "ํ๋ฉด์ ๊ฐ๋ ์ฑ์ฐ๋ ๊ทธ๋ฆผํ์ ํผ์น๋ ๊ฒ"๋ฟ์ด๊ณ , ์ค์ ์ด๋ฏธ์ง ํํ๋ ๋ค์ ๋จ๊ณ ์ดํ ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋๊ฐ ๊ฒฐ์ ํด์.
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) ํ ๋ฒ์ผ๋ก ์ด ์ค์ ์ ์ฒด๋ฅผ ๋ค์ ๋ถ๋ฌ์ฌ ์ ์์ด์.
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)๋ง ๋ฏธ๋ฆฌ ์กฐํํด๋๊ณ , ์ค์ ๊ฐ์ ์์ง ์ ๋ฌํ์ง ์์์. ์์น๋ ํ๋ก๊ทธ๋จ์ด ์กด์ฌํ๋ ๋์ ๋ณํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ ๋ ๋ฃจํ ๋ฐ์์ ํ ๋ฒ๋ง ๊ตฌํด๋๋ฉด ์ถฉ๋ถํด์.
gl.useProgram(program);
์ง๊ธ๊น์ง ๋ง๋ ์
ฐ์ด๋ ํ๋ก๊ทธ๋จ์ GPU๊ฐ ์ค์ ๋ก ์คํํ ๋์์ผ๋ก ์ง์ ํ๋ ๋จ๊ณ์์. ์ดํ drawArrays๊ฐ ํธ์ถ๋๋ฉด GPU๋ ์ฌ๊ธฐ์ ์ง์ ๋ ํ๋ก๊ทธ๋จ์ ๊ธฐ์ค์ผ๋ก ํ์ดํ๋ผ์ธ์ ์คํํด์.
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);
}
์ฌ๊ธฐ์๋ถํฐ๊ฐ ์ค์ ๋ก "์ด๋ฏธ์ง๊ฐ ๋ง๋ค์ด์ง๋" ๊ตฌ๊ฐ์ด์์. ๊ฐ ํธ์ถ์ด ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ ํ๋ ค๋ณด๋ด๋์ง ์์๋๋ก ๋ณด๋ฉด:
clear โ ์ด์ ํ๋ ์์ ๊ทธ๋ ค์ง ์์ ๋ฒํผ ๋ด์ฉ์ ์ง์ ํ ๋ฐฐ๊ฒฝ์์ผ๋ก ์ด๊ธฐํํด์.uniform2f/uniform1f โ CPU ์ชฝ ๋ณ์(canvas.width, time, mouseX/Y)์ ํ์ฌ ๊ฐ์ GPU์ ์ ๋ํผ ์ฌ๋กฏ์ ๋ณต์ฌํด์. ์ด ์์ ์ 3๋จ๊ณ์์ ๊ฐฑ์ ๋๋ ๋ง์ฐ์ค ์ขํ๊ฐ ๋น๋ก์ GPU๋ก ์ ๋ฌ๋ผ์.bindVertexArray(vao) โ 7~8๋จ๊ณ์์ ๊ธฐ๋กํด๋ ์ ์ ๋ฐ์ดํฐ ํด์ ๊ท์น์ ๋ค์ ํ์ฑํํด์.drawArrays(TRIANGLE_STRIP, 0, 4) โ ์ฌ๊ธฐ์๋ถํฐ GPU ๋ด๋ถ ํ์ดํ๋ผ์ธ์ด ์๋์ผ๋ก ์คํ๋ผ์.u_resolution, u_time, u_mouse ๊ฐ๊ณผ ํฝ์
์ขํ(gl_FragCoord ๋ฑ)๋ฅผ ์กฐํฉํด ๊ฐ ํฝ์
์ ์ต์ข
์์์ ๊ณ์ฐํด์. ์
ฐ์ด๋ ์ํธ์ ์ค์ง์ ์ธ "๊ทธ๋ฆผ ๊ทธ๋ฆฌ๋ ๋ก์ง"์ ์ ๋ถ ์ด ๋จ๊ณ, ์ฆ ํจ๊ป ์ฒจ๋ถํ GLSL ํ๋๊ทธ๋จผํธ ์
ฐ์ด๋ ์ฝ๋ ์์ ์์ด์.requestAnimationFrame(render) โ ๋ธ๋ผ์ฐ์ ์ ๋ค์ ๋ฆฌํ๋ ์ ์ฃผ๊ธฐ์ ๋ง์ถฐ render๋ฅผ ๋ค์ ํธ์ถํ๋๋ก ์์ฝํด์. ์ด ์ฌ๊ท์ ํธ์ถ ๊ตฌ์กฐ ๋๋ถ์ u_time ๊ฐ์ด ๋งค ํ๋ ์ ๊ฐฑ์ ๋๋ฉฐ ์ ๋๋ฉ์ด์
์ด ๋ง๋ค์ด์ ธ์.| ๋จ๊ณ | ๋ฐ์ดํฐ์ ์์น | ํํ |
|---|---|---|
| ์ ฐ์ด๋ ํ์ผ ๋ก๋ | CPU | ํ ์คํธ ๋ฌธ์์ด |
| ์ ฐ์ด๋ ์ปดํ์ผ/๋งํฌ | CPU โ GPU | GPU ์คํ ๊ฐ๋ฅํ ํ๋ก๊ทธ๋จ ๊ฐ์ฒด |
| ์ ์ ๋ฒํผ ์ ๋ก๋ | CPU โ GPU | Float32Array โ GPU ๋ฉ๋ชจ๋ฆฌ |
| Attribute ์ฐ๊ฒฐ | GPU ๋ด๋ถ | ๋ฒํผ ๋ฐ์ดํธ๋ฅผ ์ ์ ์ ฐ์ด๋ ์ ๋ ฅ์ผ๋ก ํด์ํ๋ ๊ท์น |
| Uniform ๊ฐ ์ ๋ฌ | CPU โ GPU | ๋งค ํ๋ ์ ๊ฐฑ์ ๋๋ ์ค์นผ๋ผ/๋ฒกํฐ ๊ฐ |
| ์ ์ ์ ฐ์ด๋ ์คํ | GPU | ์ ์ ๋ณ ํด๋ฆฝ ๊ณต๊ฐ ์ขํ ๊ณ์ฐ |
| ๋์คํฐํ | GPU | ์ผ๊ฐํ์ ํฝ์ ๋จ์๋ก ๋ถํด |
| ํ๋๊ทธ๋จผํธ ์ ฐ์ด๋ ์คํ | GPU | ํฝ์ ๋ณ ์์ ๊ณ์ฐ |
| ํ๋ ์๋ฒํผ ๊ธฐ๋ก | GPU โ ํ๋ฉด | ์ต์ข ์ด๋ฏธ์ง |
๊ฒฐ๊ตญ WebGL2 ์ ฐ์ด๋ ์ํธ๋ "CPU์์ ์ค๋นํ ์๋์ ๋ฐ์ดํฐ(์ ์ 4๊ฐ, ์ ๋ํผ ๋ช ๊ฐ)๋ฅผ GPU์ ๋๊ธฐ๊ณ , ๊ทธ ์ฌ์ด์ ์ค์ง์ ์ธ ์ด๋ฏธ์ง ์ฐ์ฐ์ ํ๋๊ทธ๋จผํธ ์ ฐ์ด๋๊ฐ ํฝ์ ๋จ์ ๋ณ๋ ฌ ์ฐ์ฐ์ผ๋ก ์ฒ๋ฆฌํ๋" ๊ตฌ์กฐ์์. JS ์ฝ๋ ์ชฝ์ ์ด ํ์ดํ๋ผ์ธ์ ๋งค ํ๋ ์ ์ฌ๋ฐ๋ฅธ ์์๋ก ํธ์ถํด์ฃผ๋ ์ญํ ์ ๊ฐ๊น๊ณ , ์ค์ ์๊ฐ์ ๊ฒฐ๊ณผ๋ฅผ ๊ฒฐ์ ํ๋ ๋ก์ง์ GLSL ์ ฐ์ด๋ ์ฝ๋ ์ชฝ์ ์์ด์.