
π index.html
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WebGL2 Vortex Point Rendering</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<canvas id="webgl_canvas"></canvas>
<script src="vortex.js"></script>
</body>
</html>
π style.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background-color: #000;
overflow: hidden;
}
#webgl_canvas {
display: block;
width: 1000px;
height: 1000px;
background-color: #000;
border: 1px solid #333;
}
π vortex.js
const canvas = document.getElementById("webgl_canvas");
const gl = canvas.getContext("webgl2");
if (!gl) {
alert("WebGL2λ₯Ό μ§μνμ§ μλ νκ²½μ
λλ€.");
}
const CANVAS_SIZE = 1000;
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;
gl.viewport(0, 0, CANVAS_SIZE, CANVAS_SIZE);
const vsSource = `#version 300 es
precision mediump float;
layout(location = 0) in float vertexIndex;
uniform float time;
uniform int pointCount;
void main(void) {
float idx = vertexIndex;
// μ μ μΈλ±μ€λ₯Ό λ°νμΌλ‘ μν΅ν μ’ν μ€μ
float angle = idx * 0.3; // κ°λ: μΈλ±μ€μ λΉλ‘
float radius = 0.3 + sin(idx * 0.01) * 0.55; // λ°κ²½: λ³λ μλ μν΅ν
// μκ°μ λ°λ₯Έ λμ μ
λ°μ΄νΈ
float heightOffset = time * 0.05; // μλ‘ μ¬λΌκ°λ μλ
float rotationSpeed = time * 0.2; // νμ μλ
float finalAngle = angle + rotationSpeed;
float height = mod(idx * 0.005 + heightOffset, 2.0) - 1.0; // μννλ λμ΄
// μν΅ν μ’ν -> μ§κ΅ μ’ν
float x = radius * cos(finalAngle);
float y = height;
float z = radius * sin(finalAngle);
gl_Position = vec4(x, y, z * 0.5, 1.0);
gl_PointSize = 3.0; // μμ μ μ ν¬κΈ°
}`;
const fsSource = `#version 300 es
precision mediump float;
uniform float time;
out vec4 outColor;
void main(void) {
// μκ°μ λ°λΌ μμμ΄ λ³νλ ν¨κ³Ό
float hue = mod(time * 0.3, 1.0);
vec3 color = vec3(
0.5 + 0.5 * sin(hue * 6.28),
0.5 + 0.5 * sin(hue * 6.28 + 2.0),
0.5 + 0.5 * sin(hue * 6.28 + 4.0)
);
// μ μ μ€μ¬μμ κ°μ₯μλ¦¬λ‘ κ°μλ‘ ν¬λͺ
νκ²
vec2 coord = gl_PointCoord - 0.5;
float dist = length(coord);
float alpha = 1.0 - smoothstep(0.0, 0.5, dist);
outColor = vec4(color, alpha);
}`;
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("Shader Error:", gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fsSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Program Link Error:", gl.getProgramInfoLog(program));
}
gl.useProgram(program);
const timeLocation = gl.getUniformLocation(program, "time");
const pointCountLocation = gl.getUniformLocation(program, "pointCount");
const POINT_COUNT = 5000;
const indices = new Float32Array(POINT_COUNT);
for (let i = 0; i < POINT_COUNT; i++) {
indices[i] = i;
}
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, indices, gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 1, gl.FLOAT, false, 0, 0);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
let startTime = Date.now();
function render() {
const currentTime = (Date.now() - startTime) * 0.001;
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniform1f(timeLocation, currentTime);
gl.uniform1i(pointCountLocation, POINT_COUNT);
gl.bindVertexArray(vao);
gl.drawArrays(gl.POINTS, 0, POINT_COUNT);
requestAnimationFrame(render);
}
render();