Spaces:
Running
Running
| ; | |
| function main() { | |
| // Get a WebGL2 context | |
| /** @type {HTMLCanvasElement} */ | |
| const canvas = document.querySelector("#canvas"); | |
| const gl = canvas.getContext("webgl2"); | |
| if (!gl) { | |
| return; | |
| } | |
| //ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Vertex Shader | |
| // Simply passes along vertex positions. | |
| const vs = `#version 300 es | |
| in vec4 a_position; | |
| void main() { | |
| gl_Position = a_position; | |
| } | |
| `; | |
| //ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Fragment Shader | |
| // This shader rayβmarches a sphere whose radius is perturbed by multiple | |
| // cymatic (sineβbased) modes. Its surface is lit with diffuse and specular | |
| // shading and decorated with intricate stripe textures and a wild color palette. | |
| const fs = `#version 300 es | |
| precision highp float; | |
| uniform vec2 iResolution; | |
| uniform vec2 iMouse; | |
| uniform float iTime; | |
| out vec4 outColor; | |
| // A wild palette function that returns vivid colors. | |
| vec3 wildPalette(float t) { | |
| vec3 a = vec3(0.5, 0.5, 0.5); | |
| vec3 b = vec3(0.5, 0.5, 0.5); | |
| vec3 c = vec3(1.0, 1.0, 1.0); | |
| vec3 d = vec3(0.0, 0.33, 0.67); | |
| return a + b * cos(6.28318 * (c * t + d)); | |
| } | |
| // The sphereβs surface is modulated by several sineβbased modes. | |
| // We compute a βdisplacementβ that is added to the base radius. | |
| float sphereDisplacement(vec3 p, float t) { | |
| float r = length(p); | |
| float theta = acos(p.y / r); | |
| float phi = atan(p.z, p.x); | |
| // Three modes for a rich, cymatic effect: | |
| float d1 = sin(3.0 * theta + t) * sin(4.0 * phi + 1.3 * t); | |
| float d2 = cos(5.0 * theta - 0.7 * t) * sin(2.0 * phi + 1.1 * t); | |
| float d3 = sin(7.0 * theta + 3.0 * t) * cos(6.0 * phi - 2.0 * t); | |
| return 0.2 * (d1 + d2 + d3); | |
| } | |
| // Signed distance function for the deformed (cymatic) sphere. | |
| // Base sphere has radius 1.0; its radius is perturbed by sphereDisplacement. | |
| float sdCymaticSphere(vec3 p, float t) { | |
| return length(p) - (1.0 + sphereDisplacement(p, t)); | |
| } | |
| // Compute the normal at point p via finite differences of the SDF. | |
| vec3 calcNormal(vec3 p, float t) { | |
| float eps = 0.001; | |
| vec3 n; | |
| n.x = sdCymaticSphere(p + vec3(eps, 0.0, 0.0), t) - sdCymaticSphere(p - vec3(eps, 0.0, 0.0), t); | |
| n.y = sdCymaticSphere(p + vec3(0.0, eps, 0.0), t) - sdCymaticSphere(p - vec3(0.0, eps, 0.0), t); | |
| n.z = sdCymaticSphere(p + vec3(0.0, 0.0, eps), t) - sdCymaticSphere(p - vec3(0.0, 0.0, eps), t); | |
| return normalize(n); | |
| } | |
| // Rayβmarching routine to find the intersection of a ray with the deformed sphere. | |
| float raymarch(vec3 ro, vec3 rd, float t, out vec3 pos) { | |
| float depth = 0.0; | |
| for (int i = 0; i < 100; i++) { | |
| pos = ro + rd * depth; | |
| float dist = sdCymaticSphere(pos, t); | |
| if (abs(dist) < 0.001) { | |
| return depth; | |
| } | |
| depth += dist; | |
| if (depth >= 20.0) break; | |
| } | |
| return -1.0; | |
| } | |
| void main() { | |
| // Normalized pixel coordinates (centered on zero) | |
| vec2 uv = (gl_FragCoord.xy - 0.5 * iResolution.xy) / iResolution.y; | |
| // Use mouse position to control the cameraβs azimuth and pitch. | |
| float angle = iMouse.x / iResolution.x * 6.28318; | |
| float pitch = mix(0.3, 1.2, iMouse.y / iResolution.y); | |
| float radius = 4.0; | |
| vec3 ro = vec3( | |
| radius * cos(pitch) * cos(angle), | |
| radius * sin(pitch), | |
| radius * cos(pitch) * sin(angle) | |
| ); | |
| vec3 target = vec3(0.0); | |
| // Build a simple camera coordinate system. | |
| vec3 forward = normalize(target - ro); | |
| vec3 right = normalize(cross(forward, vec3(0.0, 1.0, 0.0))); | |
| vec3 up = cross(right, forward); | |
| // Compute the ray direction. | |
| vec3 rd = normalize(forward + uv.x * right + uv.y * up); | |
| // Rayβmarch the scene. | |
| vec3 pos; | |
| float d = raymarch(ro, rd, iTime, pos); | |
| vec3 color; | |
| if (d > 0.0) { | |
| // Surface hit: compute normal for lighting. | |
| vec3 normal = calcNormal(pos, iTime); | |
| vec3 lightDir = normalize(vec3(0.8, 1.0, 0.6)); | |
| float diff = max(dot(normal, lightDir), 0.0); | |
| vec3 viewDir = normalize(ro - pos); | |
| vec3 halfDir = normalize(lightDir + viewDir); | |
| float spec = pow(max(dot(normal, halfDir), 0.0), 32.0); | |
| // Compute spherical coordinates for the hit point. | |
| float rPos = length(pos); | |
| float theta = acos(pos.y / rPos); | |
| float phi = atan(pos.z, pos.x); | |
| // Use the cymatic displacement to drive the wild color palette. | |
| float sp = sphereDisplacement(pos, iTime); | |
| float factor = sp * 5.0; | |
| vec3 baseColor = wildPalette(factor + sin(iTime)); | |
| // Add intricate stripe textures via highβfrequency sine patterns. | |
| float stripes = sin(10.0 * phi + iTime) * sin(10.0 * theta + iTime); | |
| baseColor *= 0.5 + 0.5 * stripes; | |
| // Combine the base color with lighting. | |
| color = baseColor * diff + vec3(0.2) * spec; | |
| color = mix(color, baseColor, 0.3); | |
| } else { | |
| // No hit: use a subtle background gradient. | |
| color = mix(vec3(0.0, 0.0, 0.1), vec3(0.0), uv.y + 0.5); | |
| } | |
| outColor = vec4(color, 1.0); | |
| } | |
| `; | |
| //ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Create and compile the shader program using webgl-utils. | |
| const program = webglUtils.createProgramFromSources(gl, [vs, fs]); | |
| // Look up attribute and uniform locations. | |
| const positionAttributeLocation = gl.getAttribLocation(program, "a_position"); | |
| const resolutionLocation = gl.getUniformLocation(program, "iResolution"); | |
| const mouseLocation = gl.getUniformLocation(program, "iMouse"); | |
| const timeLocation = gl.getUniformLocation(program, "iTime"); | |
| // Create a vertex array object (VAO) and bind it. | |
| const vao = gl.createVertexArray(); | |
| gl.bindVertexArray(vao); | |
| // Create a buffer and put a fullβscreen quad in it. | |
| const positionBuffer = gl.createBuffer(); | |
| gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); | |
| gl.bufferData( | |
| gl.ARRAY_BUFFER, | |
| new Float32Array([ | |
| -1, -1, | |
| 1, -1, | |
| -1, 1, | |
| -1, 1, | |
| 1, -1, | |
| 1, 1, | |
| ]), | |
| gl.STATIC_DRAW | |
| ); | |
| // Enable the attribute. | |
| gl.enableVertexAttribArray(positionAttributeLocation); | |
| gl.vertexAttribPointer( | |
| positionAttributeLocation, | |
| 2, // 2 components per vertex | |
| gl.FLOAT, // data type is float | |
| false, | |
| 0, | |
| 0 | |
| ); | |
| //ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Set up mouse/touch interactions. | |
| const playpauseElem = document.querySelector(".playpause"); | |
| const inputElem = document.querySelector(".divcanvas"); | |
| inputElem.addEventListener("mouseover", requestFrame); | |
| inputElem.addEventListener("mouseout", cancelFrame); | |
| let mouseX = 0; | |
| let mouseY = 0; | |
| function setMousePosition(e) { | |
| const rect = inputElem.getBoundingClientRect(); | |
| mouseX = e.clientX - rect.left; | |
| mouseY = rect.height - (e.clientY - rect.top) - 1; | |
| } | |
| inputElem.addEventListener("mousemove", setMousePosition); | |
| inputElem.addEventListener("touchstart", (e) => { | |
| e.preventDefault(); | |
| playpauseElem.classList.add("playpausehide"); | |
| requestFrame(); | |
| }, { passive: false }); | |
| inputElem.addEventListener("touchmove", (e) => { | |
| e.preventDefault(); | |
| setMousePosition(e.touches[0]); | |
| }, { passive: false }); | |
| inputElem.addEventListener("touchend", (e) => { | |
| e.preventDefault(); | |
| playpauseElem.classList.remove("playpausehide"); | |
| cancelFrame(); | |
| }, { passive: false }); | |
| //ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| // Animation loop. | |
| let requestId; | |
| function requestFrame() { | |
| if (!requestId) { | |
| requestId = requestAnimationFrame(render); | |
| } | |
| } | |
| function cancelFrame() { | |
| if (requestId) { | |
| cancelAnimationFrame(requestId); | |
| requestId = undefined; | |
| } | |
| } | |
| let then = 0; | |
| let time = 0; | |
| function render(now) { | |
| requestId = undefined; | |
| now *= 0.001; // Convert to seconds. | |
| const elapsedTime = Math.min(now - then, 0.1); | |
| time += elapsedTime; | |
| then = now; | |
| webglUtils.resizeCanvasToDisplaySize(gl.canvas); | |
| gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); | |
| gl.useProgram(program); | |
| gl.bindVertexArray(vao); | |
| // Set uniforms. | |
| gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height); | |
| gl.uniform2f(mouseLocation, mouseX, mouseY); | |
| gl.uniform1f(timeLocation, time); | |
| gl.drawArrays(gl.TRIANGLES, 0, 6); | |
| requestFrame(); | |
| } | |
| requestFrame(); | |
| requestAnimationFrame(cancelFrame); | |
| } | |
| main(); | |