waves
This commit is contained in:
350
waves.html
Normal file
350
waves.html
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebGL FFT Waves</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #1a202c; /* dark gray */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
#webgl-canvas {
|
||||||
|
background-color: #2d3748; /* dark blue-gray */
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
width: 100%;
|
||||||
|
max-width: 800px;
|
||||||
|
height: 600px;
|
||||||
|
margin: 1rem;
|
||||||
|
cursor: grab;
|
||||||
|
}
|
||||||
|
#webgl-canvas:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="p-6 bg-gray-800 rounded-xl shadow-lg m-4 w-full max-w-2xl text-center">
|
||||||
|
<h1 class="text-2xl font-bold mb-4 text-white">WebGL FFT-Generated Waves (Wireframe)</h1>
|
||||||
|
<canvas id="webgl-canvas"></canvas>
|
||||||
|
<p class="text-sm mt-4 text-gray-400">
|
||||||
|
Click and drag to orbit the camera, and use the mouse scroll to zoom in and out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Get the canvas and WebGL context
|
||||||
|
const canvas = document.getElementById('webgl-canvas');
|
||||||
|
const gl = canvas.getContext('webgl');
|
||||||
|
|
||||||
|
if (!gl) {
|
||||||
|
console.error("Unable to initialize WebGL. Your browser may not support it.");
|
||||||
|
const errorMessage = document.createElement('p');
|
||||||
|
errorMessage.textContent = 'Error: WebGL is not supported in this browser.';
|
||||||
|
document.body.appendChild(errorMessage);
|
||||||
|
throw new Error('WebGL not supported.');
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shaders ---
|
||||||
|
// The vertex shader now sums up multiple sine waves to create the complex wave pattern
|
||||||
|
const vsSource = `
|
||||||
|
precision mediump float;
|
||||||
|
attribute vec4 a_position;
|
||||||
|
|
||||||
|
uniform mat4 u_modelViewMatrix;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform float u_time;
|
||||||
|
|
||||||
|
varying vec3 v_color;
|
||||||
|
|
||||||
|
#define NUM_WAVES 10 // Number of sine waves to sum
|
||||||
|
|
||||||
|
vec3 calculateWave(vec3 p, vec2 direction, float amplitude, float frequency, float speed, float time) {
|
||||||
|
float wavePhase = dot(direction, p.xz) * frequency + time * speed;
|
||||||
|
float waveHeight = amplitude * sin(wavePhase);
|
||||||
|
|
||||||
|
return vec3(p.x, p.y + waveHeight, p.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 position = a_position;
|
||||||
|
|
||||||
|
// Sum multiple sine waves to simulate a complex FFT-like spectrum
|
||||||
|
float waveHeight = 0.0;
|
||||||
|
float x = a_position.x;
|
||||||
|
float z = a_position.z;
|
||||||
|
|
||||||
|
// Manually define wave parameters for the sum, with adjusted frequencies for the larger plane
|
||||||
|
waveHeight += 0.1 * sin(u_time * 1.5 + x * 2.5);
|
||||||
|
waveHeight += 0.05 * sin(u_time * 2.0 + z * 4.0);
|
||||||
|
waveHeight += 0.08 * sin(u_time * 1.8 + x * 2.0 + z * 2.0);
|
||||||
|
waveHeight += 0.03 * sin(u_time * 2.5 + x * -1.5 + z * 3.0);
|
||||||
|
waveHeight += 0.06 * sin(u_time * 1.2 + x * 3.5 - z * 1.0);
|
||||||
|
waveHeight += 0.04 * sin(u_time * 2.1 + x * -2.5 - z * 2.5);
|
||||||
|
// Added smaller waves with higher frequencies and lower amplitudes
|
||||||
|
waveHeight += 0.02 * sin(u_time * 3.0 + x * 10.0 + z * 5.0);
|
||||||
|
waveHeight += 0.015 * sin(u_time * 3.5 + x * -8.0 + z * 8.0);
|
||||||
|
waveHeight += 0.01 * sin(u_time * 4.0 + x * 12.0 - z * 12.0);
|
||||||
|
|
||||||
|
position.y += waveHeight;
|
||||||
|
|
||||||
|
gl_Position = u_projectionMatrix * u_modelViewMatrix * position;
|
||||||
|
|
||||||
|
// Simple coloring based on the wave height for visual effect
|
||||||
|
float colorFactor = (position.y / 0.5) + 0.5;
|
||||||
|
v_color = vec3(0.1, 0.4, 0.8) * colorFactor; // Dark blue
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// The fragment shader remains simple, just coloring the pixels
|
||||||
|
const fsSource = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec3 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_FragColor = vec4(v_color, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// --- Shader and Program Initialization Functions ---
|
||||||
|
function compileShader(gl, type, source) {
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
|
||||||
|
gl.deleteShader(shader);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initShaderProgram(gl, vsSource, fsSource) {
|
||||||
|
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||||
|
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||||
|
const shaderProgram = gl.createProgram();
|
||||||
|
gl.attachShader(shaderProgram, vertexShader);
|
||||||
|
gl.attachShader(shaderProgram, fragmentShader);
|
||||||
|
gl.linkProgram(shaderProgram);
|
||||||
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||||
|
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
|
||||||
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
const programInfo = {
|
||||||
|
attribs: {
|
||||||
|
position: gl.getAttribLocation(shaderProgram, 'a_position'),
|
||||||
|
},
|
||||||
|
uniforms: {
|
||||||
|
projectionMatrix: gl.getUniformLocation(shaderProgram, 'u_projectionMatrix'),
|
||||||
|
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'u_modelViewMatrix'),
|
||||||
|
time: gl.getUniformLocation(shaderProgram, 'u_time'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- Geometry ---
|
||||||
|
// Increase the GRID_SIZE for a larger and more detailed plane
|
||||||
|
const GRID_SIZE = 100;
|
||||||
|
const PLANE_SCALE = 2.0; // The scale of the plane
|
||||||
|
const vertexData = [];
|
||||||
|
const indices = [];
|
||||||
|
|
||||||
|
// Generate vertices
|
||||||
|
for (let i = 0; i < GRID_SIZE; i++) {
|
||||||
|
for (let j = 0; j < GRID_SIZE; j++) {
|
||||||
|
const x = ((j / (GRID_SIZE - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const z = ((i / (GRID_SIZE - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const y = 0;
|
||||||
|
vertexData.push(x, y, z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate indices for lines to create a wireframe grid
|
||||||
|
for (let i = 0; i < GRID_SIZE - 1; i++) {
|
||||||
|
for (let j = 0; j < GRID_SIZE - 1; j++) {
|
||||||
|
const i1 = i * GRID_SIZE + j;
|
||||||
|
const i2 = i * GRID_SIZE + j + 1;
|
||||||
|
const i3 = (i + 1) * GRID_SIZE + j;
|
||||||
|
// Horizontal line
|
||||||
|
indices.push(i1, i2);
|
||||||
|
// Vertical line
|
||||||
|
indices.push(i1, i3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add the last row and column lines
|
||||||
|
for (let i = 0; i < GRID_SIZE - 1; i++) {
|
||||||
|
const lastRowIndex = i * GRID_SIZE + (GRID_SIZE - 1);
|
||||||
|
indices.push(lastRowIndex, (i + 1) * GRID_SIZE + (GRID_SIZE - 1));
|
||||||
|
const lastColIndex = (GRID_SIZE - 1) * GRID_SIZE + i;
|
||||||
|
indices.push(lastColIndex, lastColIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const indexBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||||
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indices), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.vertexAttribPointer(programInfo.attribs.position, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.enableVertexAttribArray(programInfo.attribs.position);
|
||||||
|
|
||||||
|
// --- Camera & Matrix Functions ---
|
||||||
|
let isDragging = false;
|
||||||
|
let prevMouseX = 0;
|
||||||
|
let prevMouseY = 0;
|
||||||
|
let cameraRadius = 5.0; // Increased radius to view the larger plane
|
||||||
|
let alpha = Math.PI / 4;
|
||||||
|
let beta = Math.PI / 4;
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', () => {
|
||||||
|
isDragging = false;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const deltaX = e.clientX - prevMouseX;
|
||||||
|
const deltaY = e.clientY - prevMouseY;
|
||||||
|
alpha += deltaX * 0.005;
|
||||||
|
beta += deltaY * 0.005;
|
||||||
|
beta = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, beta));
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add a wheel event listener for zooming
|
||||||
|
canvas.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault(); // Prevent page scroll
|
||||||
|
const zoomSpeed = 0.1;
|
||||||
|
if (e.deltaY > 0) {
|
||||||
|
// Zoom out
|
||||||
|
cameraRadius += zoomSpeed;
|
||||||
|
} else {
|
||||||
|
// Zoom in, but don't go too close
|
||||||
|
cameraRadius -= zoomSpeed;
|
||||||
|
cameraRadius = Math.max(1.0, cameraRadius); // Minimum radius of 1.0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
const fieldOfView = 45 * Math.PI / 180;
|
||||||
|
let aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
const zNear = 0.1;
|
||||||
|
const zFar = 100.0;
|
||||||
|
const projectionMatrix = new Float32Array(16);
|
||||||
|
|
||||||
|
function perspective(out, fovy, aspect, near, far) {
|
||||||
|
const f = 1.0 / Math.tan(fovy / 2);
|
||||||
|
out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||||
|
out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0;
|
||||||
|
out[8] = 0; out[9] = 0; out[10] = (near + far) / (near - far); out[11] = -1;
|
||||||
|
out[12] = 0; out[13] = 0; out[14] = (2 * far * near) / (near - far); out[15] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookAt(out, eye, center, up) {
|
||||||
|
let x0, x1, x2, y0, y1, y2, z0, z1, z2, len;
|
||||||
|
let eyex = eye[0]; let eyey = eye[1]; let eyez = eye[2];
|
||||||
|
let upx = up[0]; let upy = up[1]; let upz = up[2];
|
||||||
|
let centerx = center[0]; let centery = center[1]; let centerz = center[2];
|
||||||
|
|
||||||
|
z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz;
|
||||||
|
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
|
||||||
|
z0 *= len; z1 *= len; z2 *= len;
|
||||||
|
x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0;
|
||||||
|
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
|
||||||
|
if (!len) {
|
||||||
|
x0 = 0; x1 = 0; x2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
x0 *= len; x1 *= len; x2 *= len;
|
||||||
|
}
|
||||||
|
y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0;
|
||||||
|
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
|
||||||
|
if (!len) {
|
||||||
|
y0 = 0; y1 = 0; y2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
y0 *= len; y1 *= len; y2 *= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0;
|
||||||
|
out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0;
|
||||||
|
out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0;
|
||||||
|
out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
|
||||||
|
out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
|
||||||
|
out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
|
||||||
|
out[15] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Animation Loop ---
|
||||||
|
let then = 0;
|
||||||
|
function render(now) {
|
||||||
|
now *= 0.001;
|
||||||
|
const deltaTime = now - then;
|
||||||
|
then = now;
|
||||||
|
|
||||||
|
const displayWidth = canvas.clientWidth;
|
||||||
|
const displayHeight = canvas.clientHeight;
|
||||||
|
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
|
||||||
|
canvas.width = displayWidth;
|
||||||
|
canvas.height = displayHeight;
|
||||||
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.clearColor(0.1, 0.1, 0.1, 1.0);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
|
||||||
|
// Calculate camera position based on current angles and radius
|
||||||
|
const eye = [
|
||||||
|
cameraRadius * Math.sin(beta) * Math.sin(alpha),
|
||||||
|
cameraRadius * Math.cos(beta),
|
||||||
|
cameraRadius * Math.sin(beta) * Math.cos(alpha)
|
||||||
|
];
|
||||||
|
const center = [0, 0, 0];
|
||||||
|
const up = [0, 1, 0];
|
||||||
|
|
||||||
|
// Create the view matrix using the lookAt function
|
||||||
|
const modelViewMatrix = new Float32Array(16);
|
||||||
|
lookAt(modelViewMatrix, eye, center, up);
|
||||||
|
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.projectionMatrix, false, projectionMatrix);
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.modelViewMatrix, false, modelViewMatrix);
|
||||||
|
gl.uniform1f(programInfo.uniforms.time, now);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
|
||||||
|
gl.drawElements(gl.LINES, indices.length, gl.UNSIGNED_SHORT, 0);
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
539
waves2.html
Normal file
539
waves2.html
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebGL Wave Simulation (Optimized CPU Calculation)</title>
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #1a202c; /* dark gray */
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: white;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #2d3748; /* dark blue-gray */
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
margin: 1rem;
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#webgl-canvas {
|
||||||
|
background-color: #1a202c;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: none; /* Disable touch gestures on the canvas */
|
||||||
|
}
|
||||||
|
#webgl-canvas:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.wave-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #4a5568;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.control-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: #cbd5e0;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #4299e1;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #4299e1;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
}
|
||||||
|
.toggle-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background-color: #4299e1;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.toggle-button:hover {
|
||||||
|
background-color: #2b6cb0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-2xl font-bold mb-4 text-white">WebGL Wave Simulation (Optimized CPU Calculation)</h1>
|
||||||
|
<canvas id="webgl-canvas"></canvas>
|
||||||
|
<div class="controls">
|
||||||
|
<button id="toggle-render-mode" class="toggle-button">Toggle Solid</button>
|
||||||
|
<div class="wave-controls">
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="amplitude-slider" class="text-gray-300 text-sm">Amplitude</label>
|
||||||
|
<input type="range" id="amplitude-slider" min="0.1" max="1" step="0.01" value="0.5">
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="speed-slider" class="text-gray-300 text-sm">Animation Speed</label>
|
||||||
|
<input type="range" id="speed-slider" min="0.001" max="2" step="0.001" value="0.5">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm mt-2 text-gray-400">
|
||||||
|
Click and drag to orbit the camera, and use the mouse scroll to zoom in and out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Use a self-executing function to avoid polluting the global scope.
|
||||||
|
(function() {
|
||||||
|
// Get the canvas and WebGL context.
|
||||||
|
const canvas = document.getElementById('webgl-canvas');
|
||||||
|
const gl = canvas.getContext('webgl');
|
||||||
|
|
||||||
|
if (!gl) {
|
||||||
|
console.error("Unable to initialize WebGL. Your browser may not support it.");
|
||||||
|
const errorMessage = document.createElement('p');
|
||||||
|
errorMessage.textContent = 'Error: WebGL is not supported in this browser.';
|
||||||
|
document.body.querySelector('.container').appendChild(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shaders ---
|
||||||
|
// The vertex shader is now very simple and efficient.
|
||||||
|
const vsSource = `
|
||||||
|
precision highp float;
|
||||||
|
attribute vec3 a_position;
|
||||||
|
attribute vec3 a_normal;
|
||||||
|
|
||||||
|
uniform mat4 u_modelViewMatrix;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_amplitude;
|
||||||
|
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying vec3 v_normal;
|
||||||
|
|
||||||
|
const float G = 9.81; // Gravity
|
||||||
|
|
||||||
|
#define NUM_WAVES 16
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// This is a simplified wave summation that approximates the IFFT result.
|
||||||
|
// It is not a true FFT but provides a good visual effect.
|
||||||
|
float waveHeight = 0.0;
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_WAVES; i++) {
|
||||||
|
float f_i = float(i);
|
||||||
|
// Create a unique wave vector for each loop iteration.
|
||||||
|
// We use f_i + 1.0 to prevent k_len from being 0 on the first iteration.
|
||||||
|
vec2 k = vec2(cos(f_i), sin(f_i)) * (f_i + 1.0);
|
||||||
|
float k_len = length(k);
|
||||||
|
float omega = sqrt(G * k_len);
|
||||||
|
float phase = dot(k, a_position.xz) - omega * u_time;
|
||||||
|
waveHeight += sin(phase) * u_amplitude / k_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 newPosition = vec3(a_position.x, a_position.y + waveHeight, a_position.z);
|
||||||
|
|
||||||
|
gl_Position = u_projectionMatrix * u_modelViewMatrix * vec4(newPosition, 1.0);
|
||||||
|
|
||||||
|
// Pass data to fragment shader.
|
||||||
|
v_color = vec3(0.1, 0.4, 0.8);
|
||||||
|
v_normal = a_normal;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// The fragment shader remains simple for now.
|
||||||
|
const fsSource = `
|
||||||
|
precision mediump float;
|
||||||
|
varying vec3 v_color;
|
||||||
|
varying vec3 v_normal;
|
||||||
|
|
||||||
|
uniform mat4 u_modelViewMatrix;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// Simple diffuse lighting
|
||||||
|
vec3 lightDirection = normalize(vec3(0.5, 1.0, 0.5));
|
||||||
|
vec3 normal = normalize(v_normal);
|
||||||
|
float light = dot(normal, lightDirection);
|
||||||
|
|
||||||
|
// Combine color and light
|
||||||
|
vec3 finalColor = v_color * (light * 0.5 + 0.5);
|
||||||
|
gl_FragColor = vec4(finalColor, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// --- Shader and Program Initialization Functions ---
|
||||||
|
function compileShader(gl, type, source) {
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
|
||||||
|
gl.deleteShader(shader);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initShaderProgram(gl, vsSource, fsSource) {
|
||||||
|
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||||
|
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||||
|
if (!vertexShader || !fragmentShader) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const shaderProgram = gl.createProgram();
|
||||||
|
gl.attachShader(shaderProgram, vertexShader);
|
||||||
|
gl.attachShader(shaderProgram, fragmentShader);
|
||||||
|
gl.linkProgram(shaderProgram);
|
||||||
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||||
|
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
|
||||||
|
if (!shaderProgram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
const programInfo = {
|
||||||
|
attribs: {
|
||||||
|
position: gl.getAttribLocation(shaderProgram, 'a_position'),
|
||||||
|
normal: gl.getAttribLocation(shaderProgram, 'a_normal'),
|
||||||
|
},
|
||||||
|
uniforms: {
|
||||||
|
projectionMatrix: gl.getUniformLocation(shaderProgram, 'u_projectionMatrix'),
|
||||||
|
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'u_modelViewMatrix'),
|
||||||
|
time: gl.getUniformLocation(shaderProgram, 'u_time'),
|
||||||
|
amplitude: gl.getUniformLocation(shaderProgram, 'u_amplitude'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// --- UI Controls ---
|
||||||
|
let amplitude = 0.5;
|
||||||
|
let speedFactor = 0.5;
|
||||||
|
|
||||||
|
const amplitudeSlider = document.getElementById('amplitude-slider');
|
||||||
|
amplitudeSlider.addEventListener('input', (e) => {
|
||||||
|
amplitude = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
const speedSlider = document.getElementById('speed-slider');
|
||||||
|
speedSlider.addEventListener('input', (e) => {
|
||||||
|
speedFactor = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Geometry ---
|
||||||
|
const N = 128;
|
||||||
|
const vertexData = [];
|
||||||
|
const normalData = [];
|
||||||
|
const indicesTriangles = [];
|
||||||
|
const indicesLines = [];
|
||||||
|
|
||||||
|
const PLANE_SCALE = 2.0;
|
||||||
|
|
||||||
|
// Generate vertices, normals, and indices
|
||||||
|
for (let i = 0; i < N; i++) {
|
||||||
|
for (let j = 0; j < N; j++) {
|
||||||
|
const x = ((j / (N - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const z = ((i / (N - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const y = 0;
|
||||||
|
vertexData.push(x, y, z);
|
||||||
|
normalData.push(0, 1, 0);
|
||||||
|
|
||||||
|
// Generate indices for triangles (solid)
|
||||||
|
if (i < N - 1 && j < N - 1) {
|
||||||
|
const i1 = i * N + j;
|
||||||
|
const i2 = i * N + j + 1;
|
||||||
|
const i3 = (i + 1) * N + j;
|
||||||
|
const i4 = (i + 1) * N + j + 1;
|
||||||
|
|
||||||
|
indicesTriangles.push(i1, i2, i3);
|
||||||
|
indicesTriangles.push(i2, i4, i3);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate indices for lines (wireframe)
|
||||||
|
if (j < N - 1) indicesLines.push(i * N + j, i * N + j + 1);
|
||||||
|
if (i < N - 1) indicesLines.push(i * N + j, (i + 1) * N + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const normalBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(normalData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const indexBufferTriangles = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferTriangles);
|
||||||
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indicesTriangles), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const indexBufferLines = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferLines);
|
||||||
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indicesLines), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
// Configure vertex attributes
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.vertexAttribPointer(programInfo.attribs.position, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.enableVertexAttribArray(programInfo.attribs.position);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, normalBuffer);
|
||||||
|
gl.vertexAttribPointer(programInfo.attribs.normal, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.enableVertexAttribArray(programInfo.attribs.normal);
|
||||||
|
|
||||||
|
// --- Camera & Matrix Functions ---
|
||||||
|
let isDragging = false;
|
||||||
|
let prevMouseX = 0;
|
||||||
|
let prevMouseY = 0;
|
||||||
|
let cameraRadius = 6.0;
|
||||||
|
let alpha = Math.PI / 4;
|
||||||
|
let beta = Math.PI / 4;
|
||||||
|
let renderMode = gl.LINES; // Starts with wireframe
|
||||||
|
const toggleButton = document.getElementById('toggle-render-mode');
|
||||||
|
toggleButton.textContent = 'Toggle Solid';
|
||||||
|
|
||||||
|
// Event listeners for camera controls
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
canvas.addEventListener('touchstart', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
prevMouseX = touch.clientX;
|
||||||
|
prevMouseY = touch.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', () => { isDragging = false; });
|
||||||
|
canvas.addEventListener('touchend', () => { isDragging = false; });
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const deltaX = e.clientX - prevMouseX;
|
||||||
|
const deltaY = e.clientY - prevMouseY;
|
||||||
|
alpha += deltaX * 0.005;
|
||||||
|
beta += deltaY * 0.005;
|
||||||
|
beta = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, beta));
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('touchmove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const deltaX = touch.clientX - prevMouseX;
|
||||||
|
const deltaY = touch.clientY - prevMouseY;
|
||||||
|
alpha += deltaX * 0.005;
|
||||||
|
beta += deltaY * 0.005;
|
||||||
|
beta = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, beta));
|
||||||
|
prevMouseX = touch.clientX;
|
||||||
|
prevMouseY = touch.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const zoomSpeed = 0.1;
|
||||||
|
cameraRadius += e.deltaY > 0 ? zoomSpeed : -zoomSpeed;
|
||||||
|
cameraRadius = Math.max(1.0, Math.min(20.0, cameraRadius));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle button functionality
|
||||||
|
toggleButton.addEventListener('click', () => {
|
||||||
|
if (renderMode === gl.TRIANGLES) {
|
||||||
|
renderMode = gl.LINES;
|
||||||
|
toggleButton.textContent = 'Toggle Solid';
|
||||||
|
} else {
|
||||||
|
renderMode = gl.TRIANGLES;
|
||||||
|
toggleButton.textContent = 'Toggle Wireframe';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldOfView = 45 * Math.PI / 180;
|
||||||
|
let aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
const zNear = 0.1;
|
||||||
|
const zFar = 100.0;
|
||||||
|
const projectionMatrix = new Float32Array(16);
|
||||||
|
const modelViewMatrix = new Float32Array(16);
|
||||||
|
|
||||||
|
// Matrix utility functions
|
||||||
|
function identity(out) {
|
||||||
|
out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||||
|
out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0;
|
||||||
|
out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0;
|
||||||
|
out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function perspective(out, fovy, aspect, near, far) {
|
||||||
|
const f = 1.0 / Math.tan(fovy / 2);
|
||||||
|
out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||||
|
out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0;
|
||||||
|
out[8] = 0; out[9] = 0; out[10] = (near + far) / (near - far); out[11] = -1;
|
||||||
|
out[12] = 0; out[13] = 0; out[14] = (2 * far * near) / (near - far); out[15] = 0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookAt(out, eye, center, up) {
|
||||||
|
let eyex = eye[0], eyey = eye[1], eyez = eye[2];
|
||||||
|
let upx = up[0], upy = up[1], upz = up[2];
|
||||||
|
let centerx = center[0], centery = center[1], centerz = center[2];
|
||||||
|
|
||||||
|
if (Math.abs(eyex - centerx) < 0.0001 && Math.abs(eyey - centery) < 0.0001 && Math.abs(eyez - centerz) < 0.0001) {
|
||||||
|
return identity(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
let z0, z1, z2, x0, x1, x2, y0, y1, y2, len;
|
||||||
|
|
||||||
|
// Z-axis: from eye to center, then normalized
|
||||||
|
z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz;
|
||||||
|
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
|
||||||
|
z0 *= len; z1 *= len; z2 *= len;
|
||||||
|
|
||||||
|
// X-axis: cross product of up and z, then normalized
|
||||||
|
x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0;
|
||||||
|
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
|
||||||
|
if (len === 0) {
|
||||||
|
x0 = 0; x1 = 0; x2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
x0 *= len; x1 *= len; x2 *= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y-axis: cross product of z and x, then normalized
|
||||||
|
y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0;
|
||||||
|
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
|
||||||
|
if (len === 0) {
|
||||||
|
y0 = 0; y1 = 0; y2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
y0 *= len; y1 *= len; y2 *= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the matrix in COLUMN-MAJOR order, which WebGL expects by default.
|
||||||
|
// Column 1
|
||||||
|
out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0;
|
||||||
|
// Column 2
|
||||||
|
out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0;
|
||||||
|
// Column 3
|
||||||
|
out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0;
|
||||||
|
// Column 4 (translation)
|
||||||
|
out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
|
||||||
|
out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
|
||||||
|
out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
|
||||||
|
out[15] = 1;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Animation Loop ---
|
||||||
|
let then = 0;
|
||||||
|
function render(now) {
|
||||||
|
now *= 0.001 * speedFactor; // Apply speed factor here
|
||||||
|
const deltaTime = now - then;
|
||||||
|
then = now;
|
||||||
|
|
||||||
|
const displayWidth = canvas.clientWidth;
|
||||||
|
const displayHeight = canvas.clientHeight;
|
||||||
|
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
|
||||||
|
canvas.width = displayWidth;
|
||||||
|
canvas.height = displayHeight;
|
||||||
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.clearColor(0.1, 0.1, 0.1, 1.0);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
// Send the new uniform values to the shader
|
||||||
|
gl.uniform1f(programInfo.uniforms.time, now);
|
||||||
|
gl.uniform1f(programInfo.uniforms.amplitude, amplitude);
|
||||||
|
|
||||||
|
// Calculate camera position and matrices
|
||||||
|
const eye = [
|
||||||
|
cameraRadius * Math.sin(beta) * Math.sin(alpha),
|
||||||
|
cameraRadius * Math.cos(beta),
|
||||||
|
cameraRadius * Math.sin(beta) * Math.cos(alpha)
|
||||||
|
];
|
||||||
|
const center = [0, 0, 0];
|
||||||
|
const up = [0, 1, 0];
|
||||||
|
|
||||||
|
lookAt(modelViewMatrix, eye, center, up);
|
||||||
|
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.projectionMatrix, false, projectionMatrix);
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.modelViewMatrix, false, modelViewMatrix);
|
||||||
|
|
||||||
|
// Draw the ocean based on the selected render mode
|
||||||
|
if (renderMode === gl.TRIANGLES) {
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferTriangles);
|
||||||
|
gl.drawElements(gl.TRIANGLES, indicesTriangles.length, gl.UNSIGNED_SHORT, 0);
|
||||||
|
} else {
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferLines);
|
||||||
|
gl.drawElements(gl.LINES, indicesLines.length, gl.UNSIGNED_SHORT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
558
waves3.html
Normal file
558
waves3.html
Normal file
@@ -0,0 +1,558 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>WebGL Realistic Water (Gerstner Waves)</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: #1a202c;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: white;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.container {
|
||||||
|
padding: 1.5rem;
|
||||||
|
background-color: #2d3748;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
margin: 1rem;
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
#webgl-canvas {
|
||||||
|
background-color: #1a202c;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 600px;
|
||||||
|
cursor: grab;
|
||||||
|
touch-action: none;
|
||||||
|
}
|
||||||
|
#webgl-canvas:active {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
.controls {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
}
|
||||||
|
.wave-controls {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background-color: #4a5568;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: inset 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
.control-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
min-width: 150px;
|
||||||
|
}
|
||||||
|
input[type="range"] {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 100%;
|
||||||
|
height: 8px;
|
||||||
|
background: #cbd5e0;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
input[type="range"]::-webkit-slider-thumb {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #4299e1;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
}
|
||||||
|
input[type="range"]::-moz-range-thumb {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
background: #4299e1;
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px solid #fff;
|
||||||
|
}
|
||||||
|
.toggle-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
background-color: #4299e1;
|
||||||
|
color: white;
|
||||||
|
font-weight: 600;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
.toggle-button:hover {
|
||||||
|
background-color: #2b6cb0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<h1 class="text-2xl font-bold mb-4 text-white">WebGL Realistic Water (Gerstner Waves)</h1>
|
||||||
|
<canvas id="webgl-canvas"></canvas>
|
||||||
|
<div class="controls">
|
||||||
|
<button id="toggle-render-mode" class="toggle-button">Toggle Wireframe</button>
|
||||||
|
<div class="wave-controls">
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="amplitude-slider" class="text-gray-300 text-sm">Amplitude</label>
|
||||||
|
<input type="range" id="amplitude-slider" min="0.01" max="1.0" step="0.01" value="0.25">
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="speed-slider" class="text-gray-300 text-sm">Animation Speed</label>
|
||||||
|
<input type="range" id="speed-slider" min="0.001" max="2" step="0.001" value="0.5">
|
||||||
|
</div>
|
||||||
|
<div class="control-group">
|
||||||
|
<label for="light-slider" class="text-gray-300 text-sm">Light Angle</label>
|
||||||
|
<input type="range" id="light-slider" min="-3.14" max="3.14" step="0.01" value="0.5">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p class="text-sm mt-2 text-gray-400">
|
||||||
|
Click and drag to orbit the camera, and use the mouse scroll to zoom in and out.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
(function() {
|
||||||
|
const canvas = document.getElementById('webgl-canvas');
|
||||||
|
const gl = canvas.getContext('webgl');
|
||||||
|
|
||||||
|
if (!gl) {
|
||||||
|
console.error("Unable to initialize WebGL. Your browser may not support it.");
|
||||||
|
const errorMessage = document.createElement('p');
|
||||||
|
errorMessage.textContent = 'Error: WebGL is not supported in this browser.';
|
||||||
|
document.body.querySelector('.container').appendChild(errorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Shaders (Updated for Gerstner waves) ---
|
||||||
|
const vsSource = `
|
||||||
|
precision highp float;
|
||||||
|
attribute vec3 a_position;
|
||||||
|
|
||||||
|
uniform mat4 u_modelViewMatrix;
|
||||||
|
uniform mat4 u_projectionMatrix;
|
||||||
|
uniform float u_time;
|
||||||
|
uniform float u_amplitude;
|
||||||
|
|
||||||
|
varying vec3 v_worldPosition;
|
||||||
|
varying vec3 v_normal;
|
||||||
|
|
||||||
|
const float G = 9.81; // Gravity
|
||||||
|
#define NUM_WAVES 16
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 newPosition = vec4(a_position, 1.0);
|
||||||
|
vec3 finalNormal = vec3(0.0, 0.0, 0.0);
|
||||||
|
vec2 D = a_position.xz; // Horizontal position
|
||||||
|
float N_y = 0.0;
|
||||||
|
|
||||||
|
// Sum of multiple Gerstner waves
|
||||||
|
for (int i = 0; i < NUM_WAVES; i++) {
|
||||||
|
float f_i = float(i) + 1.0;
|
||||||
|
float k = 2.0 + f_i * 0.2;
|
||||||
|
vec2 dir = normalize(vec2(cos(f_i * 0.1), sin(f_i * 0.2)));
|
||||||
|
float amp = u_amplitude / (f_i * 1.5);
|
||||||
|
float speed = u_time * 0.5;
|
||||||
|
|
||||||
|
float w = sqrt(G * k);
|
||||||
|
float phase = dot(dir, D) * k - w * speed;
|
||||||
|
float c = cos(phase);
|
||||||
|
float s = sin(phase);
|
||||||
|
|
||||||
|
// Gerstner wave displacement
|
||||||
|
newPosition.x += amp * dir.x * c;
|
||||||
|
newPosition.y += amp * s;
|
||||||
|
newPosition.z += amp * dir.y * c;
|
||||||
|
|
||||||
|
// Gerstner wave normal contribution
|
||||||
|
// Note: The correct normal is calculated by taking the cross product of the partial derivatives.
|
||||||
|
// Here, we simply sum the normal contributions to approximate the final normal.
|
||||||
|
finalNormal.x -= dir.x * amp * k * c;
|
||||||
|
finalNormal.y += amp * k * s; // This is the vertical component of the normal's derivative
|
||||||
|
finalNormal.z -= dir.y * amp * k * c;
|
||||||
|
N_y += (1.0 - amp * k * s);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalNormal.y = N_y;
|
||||||
|
|
||||||
|
gl_Position = u_projectionMatrix * u_modelViewMatrix * newPosition;
|
||||||
|
v_worldPosition = newPosition.xyz;
|
||||||
|
v_normal = normalize(finalNormal);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const fsSource = `
|
||||||
|
precision mediump float;
|
||||||
|
|
||||||
|
varying vec3 v_worldPosition;
|
||||||
|
varying vec3 v_normal;
|
||||||
|
|
||||||
|
uniform mat4 u_modelViewMatrix;
|
||||||
|
uniform vec3 u_cameraPosition;
|
||||||
|
uniform vec3 u_lightPosition;
|
||||||
|
|
||||||
|
uniform vec3 u_waterDeepColor;
|
||||||
|
uniform vec3 u_waterShallowColor;
|
||||||
|
uniform vec3 u_skyColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 viewDirection = normalize(u_cameraPosition - v_worldPosition);
|
||||||
|
vec3 lightDirection = normalize(u_lightPosition - v_worldPosition);
|
||||||
|
vec3 normal = normalize(v_normal);
|
||||||
|
|
||||||
|
// PBR-like calculations
|
||||||
|
float fresnel = pow(1.0 - max(0.0, dot(viewDirection, normal)), 5.0);
|
||||||
|
|
||||||
|
vec3 reflectedDirection = reflect(-lightDirection, normal);
|
||||||
|
float specular = pow(max(0.0, dot(reflectedDirection, viewDirection)), 128.0);
|
||||||
|
vec3 reflectionColor = u_skyColor * fresnel + vec3(1.0, 1.0, 1.0) * specular;
|
||||||
|
|
||||||
|
// Refraction (depth-based color)
|
||||||
|
float waterDepth = v_worldPosition.y;
|
||||||
|
vec3 refractionColor = mix(u_waterShallowColor, u_waterDeepColor, clamp(waterDepth * 0.5 + 0.5, 0.0, 1.0));
|
||||||
|
|
||||||
|
// Simple foam effect at wave peaks
|
||||||
|
float foam = smoothstep(0.9, 1.0, normal.y);
|
||||||
|
vec3 foamColor = vec3(1.0, 1.0, 1.0);
|
||||||
|
|
||||||
|
vec3 finalColor = mix(refractionColor, reflectionColor, fresnel) + foam * foamColor;
|
||||||
|
|
||||||
|
gl_FragColor = vec4(finalColor, 1.0);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
// --- JavaScript (same as before) ---
|
||||||
|
function compileShader(gl, type, source) {
|
||||||
|
const shader = gl.createShader(type);
|
||||||
|
gl.shaderSource(shader, source);
|
||||||
|
gl.compileShader(shader);
|
||||||
|
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
|
||||||
|
console.error('An error occurred compiling the shaders: ' + gl.getShaderInfoLog(shader));
|
||||||
|
gl.deleteShader(shader);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initShaderProgram(gl, vsSource, fsSource) {
|
||||||
|
const vertexShader = compileShader(gl, gl.VERTEX_SHADER, vsSource);
|
||||||
|
const fragmentShader = compileShader(gl, gl.FRAGMENT_SHADER, fsSource);
|
||||||
|
if (!vertexShader || !fragmentShader) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const shaderProgram = gl.createProgram();
|
||||||
|
gl.attachShader(shaderProgram, vertexShader);
|
||||||
|
gl.attachShader(shaderProgram, fragmentShader);
|
||||||
|
gl.linkProgram(shaderProgram);
|
||||||
|
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
|
||||||
|
console.error('Unable to initialize the shader program: ' + gl.getProgramInfoLog(shaderProgram));
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return shaderProgram;
|
||||||
|
}
|
||||||
|
|
||||||
|
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
|
||||||
|
if (!shaderProgram) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
const programInfo = {
|
||||||
|
attribs: {
|
||||||
|
position: gl.getAttribLocation(shaderProgram, 'a_position'),
|
||||||
|
},
|
||||||
|
uniforms: {
|
||||||
|
projectionMatrix: gl.getUniformLocation(shaderProgram, 'u_projectionMatrix'),
|
||||||
|
modelViewMatrix: gl.getUniformLocation(shaderProgram, 'u_modelViewMatrix'),
|
||||||
|
time: gl.getUniformLocation(shaderProgram, 'u_time'),
|
||||||
|
amplitude: gl.getUniformLocation(shaderProgram, 'u_amplitude'),
|
||||||
|
cameraPosition: gl.getUniformLocation(shaderProgram, 'u_cameraPosition'),
|
||||||
|
lightPosition: gl.getUniformLocation(shaderProgram, 'u_lightPosition'),
|
||||||
|
waterDeepColor: gl.getUniformLocation(shaderProgram, 'u_waterDeepColor'),
|
||||||
|
waterShallowColor: gl.getUniformLocation(shaderProgram, 'u_waterShallowColor'),
|
||||||
|
skyColor: gl.getUniformLocation(shaderProgram, 'u_skyColor'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
let amplitude = 0.25;
|
||||||
|
let speedFactor = 0.5;
|
||||||
|
let lightAngle = 0.5;
|
||||||
|
|
||||||
|
const amplitudeSlider = document.getElementById('amplitude-slider');
|
||||||
|
amplitudeSlider.addEventListener('input', (e) => {
|
||||||
|
amplitude = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
const speedSlider = document.getElementById('speed-slider');
|
||||||
|
speedSlider.addEventListener('input', (e) => {
|
||||||
|
speedFactor = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
const lightSlider = document.getElementById('light-slider');
|
||||||
|
lightSlider.addEventListener('input', (e) => {
|
||||||
|
lightAngle = parseFloat(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
const N = 128;
|
||||||
|
const vertexData = [];
|
||||||
|
const indicesTriangles = [];
|
||||||
|
const indicesLines = [];
|
||||||
|
|
||||||
|
const PLANE_SCALE = 2.0;
|
||||||
|
|
||||||
|
for (let i = 0; i < N; i++) {
|
||||||
|
for (let j = 0; j < N; j++) {
|
||||||
|
const x = ((j / (N - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const z = ((i / (N - 1)) * 2 - 1) * PLANE_SCALE;
|
||||||
|
const y = 0;
|
||||||
|
vertexData.push(x, y, z);
|
||||||
|
|
||||||
|
if (i < N - 1 && j < N - 1) {
|
||||||
|
const i1 = i * N + j;
|
||||||
|
const i2 = i * N + j + 1;
|
||||||
|
const i3 = (i + 1) * N + j;
|
||||||
|
const i4 = (i + 1) * N + j + 1;
|
||||||
|
|
||||||
|
indicesTriangles.push(i1, i2, i3);
|
||||||
|
indicesTriangles.push(i2, i4, i3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (j < N - 1) indicesLines.push(i * N + j, i * N + j + 1);
|
||||||
|
if (i < N - 1) indicesLines.push(i * N + j, (i + 1) * N + j);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const positionBuffer = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const indexBufferTriangles = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferTriangles);
|
||||||
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indicesTriangles), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
const indexBufferLines = gl.createBuffer();
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferLines);
|
||||||
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(indicesLines), gl.STATIC_DRAW);
|
||||||
|
|
||||||
|
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
|
||||||
|
gl.vertexAttribPointer(programInfo.attribs.position, 3, gl.FLOAT, false, 0, 0);
|
||||||
|
gl.enableVertexAttribArray(programInfo.attribs.position);
|
||||||
|
|
||||||
|
let isDragging = false;
|
||||||
|
let prevMouseX = 0;
|
||||||
|
let prevMouseY = 0;
|
||||||
|
let cameraRadius = 6.0;
|
||||||
|
let alpha = Math.PI / 4;
|
||||||
|
let beta = Math.PI / 4;
|
||||||
|
let renderMode = gl.LINES;
|
||||||
|
const toggleButton = document.getElementById('toggle-render-mode');
|
||||||
|
toggleButton.textContent = 'Toggle Wireframe';
|
||||||
|
|
||||||
|
canvas.addEventListener('mousedown', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
canvas.addEventListener('touchstart', (e) => {
|
||||||
|
isDragging = true;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
prevMouseX = touch.clientX;
|
||||||
|
prevMouseY = touch.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('mouseup', () => { isDragging = false; });
|
||||||
|
canvas.addEventListener('touchend', () => { isDragging = false; });
|
||||||
|
|
||||||
|
canvas.addEventListener('mousemove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const deltaX = e.clientX - prevMouseX;
|
||||||
|
const deltaY = e.clientY - prevMouseY;
|
||||||
|
alpha += deltaX * 0.005;
|
||||||
|
beta += deltaY * 0.005;
|
||||||
|
beta = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, beta));
|
||||||
|
prevMouseX = e.clientX;
|
||||||
|
prevMouseY = e.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('touchmove', (e) => {
|
||||||
|
if (!isDragging) return;
|
||||||
|
const touch = e.touches[0];
|
||||||
|
const deltaX = touch.clientX - prevMouseX;
|
||||||
|
const deltaY = touch.clientY - prevMouseY;
|
||||||
|
alpha += deltaX * 0.005;
|
||||||
|
beta += deltaY * 0.005;
|
||||||
|
beta = Math.max(-Math.PI / 2 + 0.1, Math.min(Math.PI / 2 - 0.1, beta));
|
||||||
|
prevMouseX = touch.clientX;
|
||||||
|
prevMouseY = touch.clientY;
|
||||||
|
});
|
||||||
|
|
||||||
|
canvas.addEventListener('wheel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const zoomSpeed = 0.1;
|
||||||
|
cameraRadius += e.deltaY > 0 ? zoomSpeed : -zoomSpeed;
|
||||||
|
cameraRadius = Math.max(1.0, Math.min(20.0, cameraRadius));
|
||||||
|
});
|
||||||
|
|
||||||
|
toggleButton.addEventListener('click', () => {
|
||||||
|
if (renderMode === gl.TRIANGLES) {
|
||||||
|
renderMode = gl.LINES;
|
||||||
|
toggleButton.textContent = 'Toggle Solid';
|
||||||
|
} else {
|
||||||
|
renderMode = gl.TRIANGLES;
|
||||||
|
toggleButton.textContent = 'Toggle Wireframe';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fieldOfView = 45 * Math.PI / 180;
|
||||||
|
let aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
const zNear = 0.1;
|
||||||
|
const zFar = 100.0;
|
||||||
|
const projectionMatrix = new Float32Array(16);
|
||||||
|
const modelViewMatrix = new Float32Array(16);
|
||||||
|
|
||||||
|
function identity(out) {
|
||||||
|
out[0] = 1; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||||
|
out[4] = 0; out[5] = 1; out[6] = 0; out[7] = 0;
|
||||||
|
out[8] = 0; out[9] = 0; out[10] = 1; out[11] = 0;
|
||||||
|
out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function perspective(out, fovy, aspect, near, far) {
|
||||||
|
const f = 1.0 / Math.tan(fovy / 2);
|
||||||
|
out[0] = f / aspect; out[1] = 0; out[2] = 0; out[3] = 0;
|
||||||
|
out[4] = 0; out[5] = f; out[6] = 0; out[7] = 0;
|
||||||
|
out[8] = 0; out[9] = 0; out[10] = (near + far) / (near - far); out[11] = -1;
|
||||||
|
out[12] = 0; out[13] = 0; out[14] = (2 * far * near) / (near - far); out[15] = 0;
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function lookAt(out, eye, center, up) {
|
||||||
|
let eyex = eye[0], eyey = eye[1], eyez = eye[2];
|
||||||
|
let upx = up[0], upy = up[1], upz = up[2];
|
||||||
|
let centerx = center[0], centery = center[1], centerz = center[2];
|
||||||
|
|
||||||
|
if (Math.abs(eyex - centerx) < 0.0001 && Math.abs(eyey - centery) < 0.0001 && Math.abs(eyez - centerz) < 0.0001) {
|
||||||
|
return identity(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
let z0, z1, z2, x0, x1, x2, y0, y1, y2, len;
|
||||||
|
z0 = eyex - centerx; z1 = eyey - centery; z2 = eyez - centerz;
|
||||||
|
len = 1 / Math.sqrt(z0 * z0 + z1 * z1 + z2 * z2);
|
||||||
|
z0 *= len; z1 *= len; z2 *= len;
|
||||||
|
|
||||||
|
x0 = upy * z2 - upz * z1; x1 = upz * z0 - upx * z2; x2 = upx * z1 - upy * z0;
|
||||||
|
len = Math.sqrt(x0 * x0 + x1 * x1 + x2 * x2);
|
||||||
|
if (len === 0) {
|
||||||
|
x0 = 0; x1 = 0; x2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
x0 *= len; x1 *= len; x2 *= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
y0 = z1 * x2 - z2 * x1; y1 = z2 * x0 - z0 * x2; y2 = z0 * x1 - z1 * x0;
|
||||||
|
len = Math.sqrt(y0 * y0 + y1 * y1 + y2 * y2);
|
||||||
|
if (len === 0) {
|
||||||
|
y0 = 0; y1 = 0; y2 = 0;
|
||||||
|
} else {
|
||||||
|
len = 1 / len;
|
||||||
|
y0 *= len; y1 *= len; y2 *= len;
|
||||||
|
}
|
||||||
|
|
||||||
|
out[0] = x0; out[1] = y0; out[2] = z0; out[3] = 0;
|
||||||
|
out[4] = x1; out[5] = y1; out[6] = z1; out[7] = 0;
|
||||||
|
out[8] = x2; out[9] = y2; out[10] = z2; out[11] = 0;
|
||||||
|
out[12] = -(x0 * eyex + x1 * eyey + x2 * eyez);
|
||||||
|
out[13] = -(y0 * eyex + y1 * eyey + y2 * eyez);
|
||||||
|
out[14] = -(z0 * eyex + z1 * eyey + z2 * eyez);
|
||||||
|
out[15] = 1;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
let then = 0;
|
||||||
|
function render(now) {
|
||||||
|
now *= 0.001 * speedFactor;
|
||||||
|
const deltaTime = now - then;
|
||||||
|
then = now;
|
||||||
|
|
||||||
|
const displayWidth = canvas.clientWidth;
|
||||||
|
const displayHeight = canvas.clientHeight;
|
||||||
|
if (canvas.width !== displayWidth || canvas.height !== displayHeight) {
|
||||||
|
canvas.width = displayWidth;
|
||||||
|
canvas.height = displayHeight;
|
||||||
|
gl.viewport(0, 0, canvas.width, canvas.height);
|
||||||
|
aspect = canvas.clientWidth / canvas.clientHeight;
|
||||||
|
perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
|
||||||
|
}
|
||||||
|
|
||||||
|
gl.clearColor(0.1, 0.1, 0.1, 1.0);
|
||||||
|
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||||
|
gl.enable(gl.DEPTH_TEST);
|
||||||
|
gl.enable(gl.BLEND);
|
||||||
|
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
gl.useProgram(shaderProgram);
|
||||||
|
|
||||||
|
const eye = [
|
||||||
|
cameraRadius * Math.sin(beta) * Math.sin(alpha),
|
||||||
|
cameraRadius * Math.cos(beta),
|
||||||
|
cameraRadius * Math.sin(beta) * Math.cos(alpha)
|
||||||
|
];
|
||||||
|
const center = [0, 0, 0];
|
||||||
|
const up = [0, 1, 0];
|
||||||
|
const lightPosition = [
|
||||||
|
5.0 * Math.cos(lightAngle),
|
||||||
|
5.0,
|
||||||
|
5.0 * Math.sin(lightAngle)
|
||||||
|
];
|
||||||
|
|
||||||
|
lookAt(modelViewMatrix, eye, center, up);
|
||||||
|
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.projectionMatrix, false, projectionMatrix);
|
||||||
|
gl.uniformMatrix4fv(programInfo.uniforms.modelViewMatrix, false, modelViewMatrix);
|
||||||
|
gl.uniform1f(programInfo.uniforms.time, now);
|
||||||
|
gl.uniform1f(programInfo.uniforms.amplitude, amplitude);
|
||||||
|
gl.uniform3fv(programInfo.uniforms.cameraPosition, eye);
|
||||||
|
gl.uniform3fv(programInfo.uniforms.lightPosition, lightPosition);
|
||||||
|
gl.uniform3fv(programInfo.uniforms.waterDeepColor, [0.0, 0.1, 0.3]);
|
||||||
|
gl.uniform3fv(programInfo.uniforms.waterShallowColor, [0.1, 0.4, 0.6]);
|
||||||
|
gl.uniform3fv(programInfo.uniforms.skyColor, [0.6, 0.8, 1.0]);
|
||||||
|
|
||||||
|
if (renderMode === gl.TRIANGLES) {
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferTriangles);
|
||||||
|
gl.drawElements(gl.TRIANGLES, indicesTriangles.length, gl.UNSIGNED_SHORT, 0);
|
||||||
|
} else {
|
||||||
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBufferLines);
|
||||||
|
gl.drawElements(gl.LINES, indicesLines.length, gl.UNSIGNED_SHORT, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.onload = function() {
|
||||||
|
requestAnimationFrame(render);
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user