πŸͺ© Vortex Points

BamgasiJMΒ·2026λ…„ 6μ›” 26일

WebGL

λͺ©λ‘ 보기
10/14
post-thumbnail

πŸ“ 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

// ============================================================================
// [WebGL2 Vortex Point Rendering]
// - μ†Œμš©λŒμ΄λ₯Ό 이루며 μœ„λ‘œ μ˜¬λΌκ°€λŠ” μ λ“€μ˜ μ• λ‹ˆλ©”μ΄μ…˜
// - VAO(Vertex Array Object) 적용
// - requestAnimationFrame으둜 μ‹€μ‹œκ°„ λ Œλ”λ§
// ============================================================================

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

if (!gl) {
  alert("WebGL2λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠλŠ” ν™˜κ²½μž…λ‹ˆλ‹€.");
}

// --- [Canvas 크기λ₯Ό 1000x1000 κ³ μ •] ---
const CANVAS_SIZE = 1000;
canvas.width = CANVAS_SIZE;
canvas.height = CANVAS_SIZE;
gl.viewport(0, 0, CANVAS_SIZE, CANVAS_SIZE);

// --- [Vertex Shader: μ›ν†΅ν˜• μ’Œν‘œλ‘œ 점의 μœ„μΉ˜ 계산] ---
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; // μž‘μ€ 점의 크기
    }`;

// --- [Fragment Shader: 점의 색상] ---
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);

// --- [Uniform λ‘œμΌ€μ΄μ…˜ νšλ“] ---
const timeLocation = gl.getUniformLocation(program, "time");
const pointCountLocation = gl.getUniformLocation(program, "pointCount");

// --- [버퍼 및 VAO μ„€μ •] ---
const POINT_COUNT = 5000; // 포인트 개수

// 인덱슀 데이터: 0λΆ€ν„° POINT_COUNT-1κΉŒμ§€μ˜ 숫자
const indices = new Float32Array(POINT_COUNT);
for (let i = 0; i < POINT_COUNT; i++) {
  indices[i] = i;
}

// VAO 생성 및 바인딩
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

// VBO 생성 및 데이터 ν• λ‹Ή
const vbo = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
gl.bufferData(gl.ARRAY_BUFFER, indices, gl.STATIC_DRAW);

// Vertex Attribute μ„€μ •
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);

  // Uniform μ—…λ°μ΄νŠΈ
  gl.uniform1f(timeLocation, currentTime);
  gl.uniform1i(pointCountLocation, POINT_COUNT);

  // VAO 바인딩 및 그리기
  gl.bindVertexArray(vao);
  gl.drawArrays(gl.POINTS, 0, POINT_COUNT);

  // λ‹€μŒ ν”„λ ˆμž„ μš”μ²­
  requestAnimationFrame(render);
}

render();

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

0개의 λŒ“κΈ€