Spaces:
Running
Running
File size: 8,770 Bytes
993eabd |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>3D Cymatic Display in WebGL2</title>
<style>
body, html { margin: 0; height: 100%; overflow: hidden; background: #000; }
canvas { width: 100%; height: 100%; display: block; }
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<!-- Include glMatrix and webgl-utils scripts -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/gl-matrix-min.js"></script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script type="text/javascript">
"use strict";
function main() {
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl2");
if (!gl) {
console.error("WebGL 2 not supported");
return;
}
// Vertex shader: displaces a grid using two vibrational modes and computes normals
const vsSource = `#version 300 es
precision highp float;
in vec3 a_position;
uniform float u_time;
uniform mat4 u_MVP;
out vec3 v_normal;
out vec3 v_position;
const float PI = 3.14159;
void main() {
// Amplitudes for two modes
float A1 = 0.1;
float A2 = 0.05;
// Mode 1: Fundamental vibration (nodal lines at the boundaries)
float mode1 = A1 * sin(PI * (a_position.x + 1.0) / 2.0)
* sin(PI * (a_position.y + 1.0) / 2.0)
* cos(3.0 * u_time);
// Mode 2: A higher-order vibration for additional detail
float mode2 = A2 * sin(2.0 * PI * (a_position.x + 1.0) / 2.0)
* sin(PI * (a_position.y + 1.0) / 2.0)
* cos(5.0 * u_time);
float z = mode1 + mode2;
// Compute partial derivatives for normal calculation
float dx1 = A1 * (PI/2.0) * cos(PI*(a_position.x+1.0)/2.0)
* sin(PI*(a_position.y+1.0)/2.0) * cos(3.0 * u_time);
float dx2 = A2 * (2.0*PI/2.0) * cos(2.0*PI*(a_position.x+1.0)/2.0)
* sin(PI*(a_position.y+1.0)/2.0) * cos(5.0 * u_time);
float dfdx = dx1 + dx2;
float dy1 = A1 * (PI/2.0) * sin(PI*(a_position.x+1.0)/2.0)
* cos(PI*(a_position.y+1.0)/2.0) * cos(3.0 * u_time);
float dy2 = A2 * (PI/2.0) * sin(2.0*PI*(a_position.x+1.0)/2.0)
* cos(PI*(a_position.y+1.0)/2.0) * cos(5.0 * u_time);
float dfdy = dy1 + dy2;
vec3 displacedPos = vec3(a_position.x, a_position.y, z);
// The normal is computed as the normalized cross of the tangent derivatives.
// For a height field, an approximate normal is: (-dfdx, -dfdy, 1)
vec3 normal = normalize(vec3(-dfdx, -dfdy, 1.0));
v_normal = normal;
v_position = displacedPos;
gl_Position = u_MVP * vec4(displacedPos, 1.0);
}`;
// Fragment shader: uses Phong shading for realistic lighting
const fsSource = `#version 300 es
precision highp float;
in vec3 v_normal;
in vec3 v_position;
uniform vec3 u_lightPos;
uniform vec3 u_viewPos;
out vec4 outColor;
void main() {
vec3 normal = normalize(v_normal);
vec3 lightDir = normalize(u_lightPos - v_position);
float diff = max(dot(normal, lightDir), 0.0);
vec3 ambient = vec3(0.2);
vec3 diffuse = diff * vec3(0.7, 0.7, 0.8);
vec3 viewDir = normalize(u_viewPos - v_position);
vec3 reflectDir = reflect(-lightDir, normal);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
vec3 specular = vec3(0.3) * spec;
vec3 color = ambient + diffuse + specular;
outColor = vec4(color, 1.0);
}`;
// Create the shader program using webgl-utils
const program = webglUtils.createProgramFromSources(gl, [vsSource, fsSource]);
// Look up attribute and uniform locations
const positionAttribLocation = gl.getAttribLocation(program, "a_position");
const timeUniformLocation = gl.getUniformLocation(program, "u_time");
const mvpUniformLocation = gl.getUniformLocation(program, "u_MVP");
const lightPosUniformLocation = gl.getUniformLocation(program, "u_lightPos");
const viewPosUniformLocation = gl.getUniformLocation(program, "u_viewPos");
// Create a grid covering [-1,1]x[-1,1]
const gridSize = 150;
const positions = [];
for (let j = 0; j <= gridSize; j++) {
for (let i = 0; i <= gridSize; i++) {
// Map grid coordinates to [-1,1]
let x = (i / gridSize) * 2 - 1;
let y = (j / gridSize) * 2 - 1;
positions.push(x, y, 0);
}
}
// Generate indices for triangles
const indices = [];
for (let j = 0; j < gridSize; j++) {
for (let i = 0; i < gridSize; i++) {
let a = j * (gridSize + 1) + i;
let b = a + 1;
let c = a + (gridSize + 1);
let d = c + 1;
indices.push(a, b, c);
indices.push(b, d, c);
}
}
// Create and bind a Vertex Array Object
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// Create and fill the position buffer
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(positionAttribLocation);
gl.vertexAttribPointer(positionAttribLocation, 3, gl.FLOAT, false, 0, 0);
// Create and fill the index buffer
const indexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint32Array(indices), gl.STATIC_DRAW);
// Set up camera matrices using glMatrix
const fieldOfView = 45 * Math.PI / 180;
const near = 0.1;
const far = 100;
let projectionMatrix = glMatrix.mat4.create();
let viewMatrix = glMatrix.mat4.create();
let modelMatrix = glMatrix.mat4.create();
const eye = [0, -2.5, 2.5]; // Camera position
const center = [0, 0, 0]; // Look at center of plate
const up = [0, 0, 1];
glMatrix.mat4.perspective(projectionMatrix, fieldOfView, canvas.clientWidth / canvas.clientHeight, near, far);
glMatrix.mat4.lookAt(viewMatrix, eye, center, up);
// Optionally, rotate the model slightly for a dramatic view
glMatrix.mat4.rotateX(modelMatrix, modelMatrix, -0.5);
let mvpMatrix = glMatrix.mat4.create();
glMatrix.mat4.multiply(mvpMatrix, viewMatrix, modelMatrix);
glMatrix.mat4.multiply(mvpMatrix, projectionMatrix, mvpMatrix);
// Set light and view positions (for the shader)
const lightPos = [2.0, -2.0, 3.0];
const viewPos = eye;
// Animation loop
let startTime = null;
function render(now) {
if (!startTime) startTime = now;
const timeInSeconds = (now - startTime) * 0.001;
// Resize canvas if needed
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Update the projection in case the canvas size changed
glMatrix.mat4.perspective(projectionMatrix, fieldOfView, gl.canvas.clientWidth / gl.canvas.clientHeight, near, far);
glMatrix.mat4.lookAt(viewMatrix, eye, center, up);
glMatrix.mat4.multiply(mvpMatrix, viewMatrix, modelMatrix);
glMatrix.mat4.multiply(mvpMatrix, projectionMatrix, mvpMatrix);
// Use our program and bind the VAO
gl.useProgram(program);
gl.bindVertexArray(vao);
// Set shader uniforms
gl.uniform1f(timeUniformLocation, timeInSeconds);
gl.uniformMatrix4fv(mvpUniformLocation, false, mvpMatrix);
gl.uniform3fv(lightPosUniformLocation, lightPos);
gl.uniform3fv(viewPosUniformLocation, viewPos);
// Draw the grid
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_INT, 0);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
}
main();
</script>
</body>
</html>
|