import * as THREE from 'three';
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
document.body.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x030916);
const camera = new THREE.PerspectiveCamera(45, 1, 0.1, 100);
camera.position.set(0, 1.1, 3.6);
camera.lookAt(0, -0.5, -4);
const vertexShader = [
'uniform float uTime;',
'varying vec3 vNormal;',
'varying vec3 vViewDir;',
'varying float vHeight;',
'void main() {',
' vec3 p = position;',
' float h = sin(p.x * 1.6 + uTime * 1.3) * 0.18 + sin(p.y * 2.3 - uTime * 1.7) * 0.12 + sin((p.x + p.y) * 3.1 + uTime * 0.8) * 0.06;',
' p.z += h;',
' vHeight = h;',
' vec3 tangentX = normalize(vec3(1.0, 0.0, 1.6 * cos(p.x * 1.6 + uTime * 1.3) * 0.18));',
' vec3 tangentY = normalize(vec3(0.0, 1.0, 2.3 * cos(p.y * 2.3 - uTime * 1.7) * 0.12));',
' vNormal = normalize(normalMatrix * normalize(cross(tangentX, tangentY)));',
' vec4 mv = modelViewMatrix * vec4(p, 1.0);',
' vViewDir = normalize(-mv.xyz);',
' gl_Position = projectionMatrix * mv;',
'}',
].join('\n');
const fragmentShader = [
'varying vec3 vNormal;',
'varying vec3 vViewDir;',
'varying float vHeight;',
'void main() {',
' vec3 n = normalize(vNormal);',
' vec3 v = normalize(vViewDir);',
' vec3 lightDir = normalize(vec3(0.4, 0.7, 0.5));',
' float fresnel = pow(1.0 - max(dot(n, v), 0.0), 3.0);',
' vec3 deep = vec3(0.02, 0.12, 0.28);',
' vec3 shallow = vec3(0.1, 0.45, 0.55);',
' vec3 base = mix(deep, shallow, clamp(vHeight * 1.5 + 0.5, 0.0, 1.0));',
' vec3 h = normalize(lightDir + v);',
' float spec = pow(max(dot(n, h), 0.0), 60.0);',
' vec3 color = mix(base, vec3(0.7, 0.85, 0.95), fresnel * 0.6) + spec * 0.9;',
' gl_FragColor = vec4(color, 1.0);',
'}',
].join('\n');
const geo = new THREE.PlaneGeometry(12, 12, 130, 130);
const mat = new THREE.ShaderMaterial({ uniforms: { uTime: { value: 0 } }, vertexShader, fragmentShader });
const mesh = new THREE.Mesh(geo, mat);
mesh.rotation.x = -Math.PI / 2;
scene.add(mesh);
function resize() { renderer.setSize(innerWidth, innerHeight); camera.aspect = innerWidth / innerHeight; camera.updateProjectionMatrix(); }
addEventListener('resize', resize); resize();
renderer.setAnimationLoop((t) => {
mat.uniforms.uTime.value = t * 0.001;
renderer.render(scene, camera);
});