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>