πŸͺ© GLSL on Cube

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

WebGL

λͺ©λ‘ 보기
12/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="cube_glsl.js"></script>
    </body>
</html>

πŸ“ style.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body, html {
    width: 100%;
    height: 100%;
    overflow: hidden;
    background-color: #000;
}

#webgl_canvas {
    display: block;
    width: 100%;
    height: 100%;
}

πŸ“ cube_glsl.js

// WebGL2 Shader Artwork: Ripple Cube
// ========================================

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

if (!gl) {
  alert("WebGL2λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠλŠ” λΈŒλΌγ‚¦μ €μž…λ‹ˆλ‹€.");
  throw new Error("WebGL2 not supported");
}

// ========================================
// μΊ”λ²„μŠ€ λ¦¬μ‚¬μ΄μ¦ˆ
// ========================================
function resizeCanvas() {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  gl.viewport(0, 0, canvas.width, canvas.height);
}
resizeCanvas();
window.addEventListener("resize", resizeCanvas);

// ========================================
// 셰이더 μ†ŒμŠ€
// ========================================
const vertexShaderSource = `#version 300 es
precision highp float;

in vec3 a_position;
in vec3 a_normal;

uniform mat4 u_modelMatrix;
uniform mat4 u_viewMatrix;
uniform mat4 u_projectionMatrix;

out vec3 v_normal;
out vec3 v_localPos;

void main() {
    v_localPos = a_position;
    v_normal = a_normal;
    gl_Position = u_projectionMatrix * u_viewMatrix * u_modelMatrix * vec4(a_position, 1.0);
}
`;

const fragmentShaderSource = `#version 300 es
precision highp float;

in vec3 v_normal;
in vec3 v_localPos;

uniform float u_time;

out vec4 fragColor;

void main() {
    vec3 absNormal = abs(v_normal);
    vec3 faceCoord;
    
    if (absNormal.x > absNormal.y && absNormal.x > absNormal.z) {
        faceCoord = vec3(v_localPos.y, v_localPos.z, 0.0);
    } else if (absNormal.y > absNormal.x && absNormal.y > absNormal.z) {
        faceCoord = vec3(v_localPos.x, v_localPos.z, 0.0);
    } else {
        faceCoord = vec3(v_localPos.x, v_localPos.y, 0.0);
    }
    
    float dist = length(faceCoord.xy);
    float wave = sin(dist * 8.0 - u_time * 3.0) * 0.5 + 0.5;
    float attenuation = 1.0 - smoothstep(0.0, 0.7, dist);
    wave = mix(0.5, wave, attenuation);
    
    fragColor = vec4(vec3(wave), 1.0);
}
`;

// ========================================
// 셰이더 컴파일
// ========================================
function createShader(type, source) {
  const shader = gl.createShader(type);
  gl.shaderSource(shader, source);
  gl.compileShader(shader);

  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error(gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

function createProgram(vertexShader, fragmentShader) {
  const program = gl.createProgram();
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
  gl.linkProgram(program);

  if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
    console.error(gl.getProgramInfoLog(program));
    gl.deleteProgram(program);
    return null;
  }
  return program;
}

const vertexShader = createShader(gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(vertexShader, fragmentShader);

// ========================================
// 큐브 μ§€μ˜€λ©”νŠΈλ¦¬
// ========================================
function createCube() {
  const positions = [];
  const normals = [];
  const indices = [];

  const faces = [
    {
      normal: [0, 0, 1],
      vertices: [
        [-0.5, -0.5, 0.5],
        [0.5, -0.5, 0.5],
        [0.5, 0.5, 0.5],
        [-0.5, 0.5, 0.5],
      ],
    },
    {
      normal: [0, 0, -1],
      vertices: [
        [0.5, -0.5, -0.5],
        [-0.5, -0.5, -0.5],
        [-0.5, 0.5, -0.5],
        [0.5, 0.5, -0.5],
      ],
    },
    {
      normal: [0, 1, 0],
      vertices: [
        [-0.5, 0.5, 0.5],
        [0.5, 0.5, 0.5],
        [0.5, 0.5, -0.5],
        [-0.5, 0.5, -0.5],
      ],
    },
    {
      normal: [0, -1, 0],
      vertices: [
        [-0.5, -0.5, -0.5],
        [0.5, -0.5, -0.5],
        [0.5, -0.5, 0.5],
        [-0.5, -0.5, 0.5],
      ],
    },
    {
      normal: [1, 0, 0],
      vertices: [
        [0.5, -0.5, 0.5],
        [0.5, -0.5, -0.5],
        [0.5, 0.5, -0.5],
        [0.5, 0.5, 0.5],
      ],
    },
    {
      normal: [-1, 0, 0],
      vertices: [
        [-0.5, -0.5, -0.5],
        [-0.5, -0.5, 0.5],
        [-0.5, 0.5, 0.5],
        [-0.5, 0.5, -0.5],
      ],
    },
  ];

  let offset = 0;
  faces.forEach((face) => {
    face.vertices.forEach((v) => {
      positions.push(...v);
      normals.push(...face.normal);
    });
    indices.push(
      offset,
      offset + 1,
      offset + 2,
      offset,
      offset + 2,
      offset + 3,
    );
    offset += 4;
  });

  return {
    positions: new Float32Array(positions),
    normals: new Float32Array(normals),
    indices: new Uint16Array(indices),
  };
}

const cube = createCube();

// ========================================
// 버퍼 μ„€μ •
// ========================================
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);

const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, cube.positions, gl.STATIC_DRAW);
const a_position = gl.getAttribLocation(program, "a_position");
gl.enableVertexAttribArray(a_position);
gl.vertexAttribPointer(a_position, 3, gl.FLOAT, false, 0, 0);

const normalBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
gl.bufferData(gl.ARRAY_BUFFER, cube.normals, gl.STATIC_DRAW);
const a_normal = gl.getAttribLocation(program, "a_normal");
gl.enableVertexAttribArray(a_normal);
gl.vertexAttribPointer(a_normal, 3, gl.FLOAT, false, 0, 0);

const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, cube.indices, gl.STATIC_DRAW);

// ========================================
// Uniform μœ„μΉ˜
// ========================================
const uniforms = {
  model: gl.getUniformLocation(program, "u_modelMatrix"),
  view: gl.getUniformLocation(program, "u_viewMatrix"),
  projection: gl.getUniformLocation(program, "u_projectionMatrix"),
  time: gl.getUniformLocation(program, "u_time"),
};

// ========================================
// ν–‰λ ¬ μœ ν‹Έλ¦¬ν‹°
// ========================================
function perspective(fov, aspect, near, far) {
  const f = 1.0 / Math.tan(fov / 2);
  const nf = 1.0 / (near - far);
  return new Float32Array([
    f / aspect,
    0,
    0,
    0,
    0,
    f,
    0,
    0,
    0,
    0,
    (far + near) * nf,
    -1,
    0,
    0,
    2 * far * near * nf,
    0,
  ]);
}

function lookAt(eye, target, up) {
  const sub = (a, b) => [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
  const cross = (a, b) => [
    a[1] * b[2] - a[2] * b[1],
    a[2] * b[0] - a[0] * b[2],
    a[0] * b[1] - a[1] * b[0],
  ];
  const dot = (a, b) => a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
  const norm = (v) => {
    const l = Math.sqrt(dot(v, v));
    return l > 0 ? [v[0] / l, v[1] / l, v[2] / l] : [0, 0, 0];
  };

  const z = norm(sub(eye, target));
  const x = norm(cross(up, z));
  const y = cross(z, x);

  return new Float32Array([
    x[0],
    y[0],
    z[0],
    0,
    x[1],
    y[1],
    z[1],
    0,
    x[2],
    y[2],
    z[2],
    0,
    -dot(x, eye),
    -dot(y, eye),
    -dot(z, eye),
    1,
  ]);
}

function rotateXYZ(rx, ry, rz) {
  const cx = Math.cos(rx),
    sx = Math.sin(rx);
  const cy = Math.cos(ry),
    sy = Math.sin(ry);
  const cz = Math.cos(rz),
    sz = Math.sin(rz);

  return new Float32Array([
    cy * cz,
    cy * sz,
    -sy,
    0,
    sx * sy * cz - cx * sz,
    sx * sy * sz + cx * cz,
    sx * cy,
    0,
    cx * sy * cz + sx * sz,
    cx * sy * sz - sx * cz,
    cx * cy,
    0,
    0,
    0,
    0,
    1,
  ]);
}

// ========================================
// 카메라 쀌 (λŒν•‘)
// ========================================
let targetDistance = 3.0;
let currentDistance = 3.0;

canvas.addEventListener("wheel", (e) => {
  e.preventDefault();
  targetDistance = Math.max(
    1.5,
    Math.min(10.0, targetDistance + e.deltaY * 0.01),
  );
});

// ========================================
// λ Œλ” 루프
// ========================================
const startTime = Date.now();

function render() {
  const time = (Date.now() - startTime) * 0.001;

  currentDistance += (targetDistance - currentDistance) * 0.1;

  gl.clearColor(0, 0, 0, 1);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  gl.enable(gl.DEPTH_TEST);
  gl.useProgram(program);

  const aspect = canvas.width / canvas.height;
  const projectionMatrix = perspective(Math.PI / 4, aspect, 0.1, 100.0);
  const viewMatrix = lookAt([0, 0, currentDistance], [0, 0, 0], [0, 1, 0]);
  const modelMatrix = rotateXYZ(time * 0.21, time * 0.27, time * 0.15);

  gl.uniformMatrix4fv(uniforms.projection, false, projectionMatrix);
  gl.uniformMatrix4fv(uniforms.view, false, viewMatrix);
  gl.uniformMatrix4fv(uniforms.model, false, modelMatrix);
  gl.uniform1f(uniforms.time, time);

  gl.bindVertexArray(vao);
  gl.drawElements(gl.TRIANGLES, cube.indices.length, gl.UNSIGNED_SHORT, 0);

  requestAnimationFrame(render);
}

render();

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

0개의 λŒ“κΈ€