const canvas = document.getElementById('glCanvas');
const gl = canvas.getContext('webgl');
gl.enable(gl.DEPTH_TEST);
const vsSource = `
attribute vec3 aPosition;
uniform float uTime;
uniform mat4 uProjection;
varying highp vec3 vPosition;
varying highp vec3 vNormal;
void main() {
vec3 pos = aPosition;
float wave1 = sin(pos.x * 3.0 + uTime) * 0.1;
float wave2 = sin(pos.z * 2.5 - uTime * 0.7) * 0.08;
float wave3 = sin((pos.x + pos.z) * 4.0 + uTime * 1.5) * 0.05;
pos.y += wave1 + wave2 + wave3;
float dx = cos(pos.x * 3.0 + uTime) * 0.3;
float dz = -cos(pos.z * 2.5 - uTime * 0.7) * 0.2;
vec3 tangent = normalize(vec3(1.0, dx, 0.0));
vec3 bitangent = normalize(vec3(0.0, dz, 1.0));
vec3 normal = cross(tangent, bitangent);
vPosition = pos;
vNormal = normal;
gl_Position = uProjection * vec4(pos, 1.0);
}
`;
const fsSource = `
varying highp vec3 vPosition;
varying highp vec3 vNormal;
uniform highp float uTime;
void main() {
highp vec3 normal = normalize(vNormal);
highp vec3 viewDir = normalize(vec3(0.0, 1.0, 2.0) - vPosition);
highp vec3 lightDir = normalize(vec3(1.0, 1.5, 0.5));
// Fresnel effect
highp float fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), 3.0);
// Water colors
highp vec3 deepColor = vec3(0.0, 0.2, 0.4);
highp vec3 shallowColor = vec3(0.0, 0.5, 0.7);
highp vec3 waterColor = mix(deepColor, shallowColor, fresnel);
// Lighting
highp float diff = max(dot(normal, lightDir), 0.0);
highp vec3 reflectDir = reflect(-lightDir, normal);
highp float spec = pow(max(dot(viewDir, reflectDir), 0.0), 128.0);
highp vec3 color = waterColor * (0.3 + diff * 0.7);
color += vec3(1.0) * spec * 0.8;
color = mix(color, vec3(0.7, 0.9, 1.0), fresnel * 0.3);
gl_FragColor = vec4(color, 0.9);
}
`;
function initShaderProgram(gl, vsSource, fsSource) {
const vertexShader = loadShader(gl, gl.VERTEX_SHADER, vsSource);
const fragmentShader = loadShader(gl, gl.FRAGMENT_SHADER, fsSource);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
return shaderProgram;
}
function loadShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
return shader;
}
// Create water grid
const gridSize = 50;
const positions = [];
const indices = [];
for (let z = 0; z < gridSize; z++) {
for (let x = 0; x < gridSize; x++) {
const px = (x / (gridSize - 1) - 0.5) * 4;
const pz = (z / (gridSize - 1) - 0.5) * 4;
positions.push(px, 0, pz);
}
}
for (let z = 0; z < gridSize - 1; z++) {
for (let x = 0; x < gridSize - 1; x++) {
const i = z * gridSize + x;
indices.push(i, i + 1, i + gridSize);
indices.push(i + 1, i + gridSize + 1, i + gridSize);
}
}
const posBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), 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);
const shaderProgram = initShaderProgram(gl, vsSource, fsSource);
const posAttrib = gl.getAttribLocation(shaderProgram, 'aPosition');
const timeUniform = gl.getUniformLocation(shaderProgram, 'uTime');
const projUniform = gl.getUniformLocation(shaderProgram, 'uProjection');
function perspective(fov, aspect, near, far) {
const f = 1.0 / Math.tan(fov / 2);
return [f/aspect,0,0,0, 0,f,0,0, 0,0,(far+near)/(near-far),-1, 0,0,(2*far*near)/(near-far),0];
}
const proj = perspective(Math.PI / 4, 1, 0.1, 100);
let time = 0;
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
function render() {
time += 0.02;
gl.clearColor(0.1, 0.1, 0.2, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, posBuffer);
gl.vertexAttribPointer(posAttrib, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(posAttrib);
gl.useProgram(shaderProgram);
gl.uniform1f(timeUniform, time);
gl.uniformMatrix4fv(projUniform, false, proj);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
requestAnimationFrame(render);
}
render();