File size: 10,458 Bytes
88fac8b
 
 
 
7bacca7
b623540
88fac8b
 
 
7bacca7
b623540
 
3f03a92
88fac8b
b623540
 
7bacca7
 
 
 
b623540
7bacca7
 
 
 
b623540
 
 
 
7bacca7
 
 
88fac8b
7bacca7
88fac8b
7bacca7
b623540
7bacca7
 
88fac8b
 
b623540
7bacca7
88fac8b
 
 
 
 
 
 
b623540
7bacca7
88fac8b
 
 
7bacca7
 
 
 
88fac8b
 
b623540
7bacca7
 
 
 
b623540
 
 
 
 
 
 
 
 
 
 
7bacca7
21984c5
7bacca7
 
 
b623540
7bacca7
 
 
 
 
b623540
7bacca7
 
b623540
7bacca7
b623540
7bacca7
 
b623540
 
7bacca7
 
b623540
5d17858
bd068fd
e79f077
b623540
7bacca7
 
 
 
 
 
 
 
 
 
 
 
b623540
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7bacca7
 
 
 
 
b623540
 
 
 
7bacca7
 
b623540
 
7bacca7
 
b623540
7bacca7
 
 
 
 
 
 
 
88fac8b
7bacca7
88fac8b
b623540
7bacca7
 
b623540
7bacca7
 
b623540
7bacca7
 
 
 
 
dad985a
 
b623540
 
 
 
 
 
 
 
 
 
 
88fac8b
7bacca7
b623540
7bacca7
 
b623540
 
 
 
7bacca7
b623540
 
 
 
 
 
7bacca7
b623540
 
 
542c40e
b623540
 
 
 
88fac8b
5d17858
88fac8b
 
 
7bacca7
 
 
 
 
88fac8b
 
7bacca7
 
 
 
88fac8b
 
 
 
7bacca7
88fac8b
 
b623540
88fac8b
b623540
7bacca7
b623540
 
 
 
 
7bacca7
 
b623540
7bacca7
88fac8b
 
b623540
 
 
 
 
 
 
88fac8b
 
b623540
 
88fac8b
 
b623540
 
 
 
 
 
 
 
 
7bacca7
 
b623540
 
7bacca7
 
b623540
 
 
 
 
 
 
7bacca7
88fac8b
7bacca7
 
b623540
 
 
88fac8b
 
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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
import * as THREE from 'three';

// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1); // Orthographic for 2D
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// Simulation parameters
const resolution = 512; // Texture resolution
const damping = 0.995; // Slightly stronger damping for smoother waves
const waveSpeed = 0.55; // Faster waves for dynamic feel

// Frame buffers for height and normal maps
const heightTarget1 = new THREE.WebGLRenderTarget(resolution, resolution, {
    type: THREE.FloatType,
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
});
const heightTarget2 = new THREE.WebGLRenderTarget(resolution, resolution, {
    type: THREE.FloatType,
    minFilter: THREE.NearestFilter,
    magFilter: THREE.NearestFilter,
});
const normalTarget = new THREE.WebGLRenderTarget(resolution, resolution, {
    minFilter: THREE.LinearFilter,
    magFilter: THREE.LinearFilter,
});

// Simulation uniforms
const simUniforms = {
    u_time: { value: 0.0 },
    u_resolution: { value: new THREE.Vector2(resolution, resolution) },
    u_mouse: { value: new THREE.Vector2(-1, -1) },
    u_mouseForce: { value: 0.0 },
    u_prevHeight: { value: heightTarget1.texture },
    u_damping: { value: damping },
    u_waveSpeed: { value: waveSpeed },
};

// Simulation vertex shader
const simVertexShader = `
    varying vec2 vUv;
    void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

// Simulation fragment shader (wave equation with background motion)
const simFragmentShader = `
    uniform float u_time;
    uniform vec2 u_resolution;
    uniform vec2 u_mouse;
    uniform float u_mouseForce;
    uniform sampler2D u_prevHeight;
    uniform float u_damping;
    uniform float u_waveSpeed;
    varying vec2 vUv;

    // Noise for randomness and background motion
    float random(vec2 st) {
        return fract(sin(dot(st, vec2(127.1, 311.7))) * 43758.5453123);
    }

    float noise(vec2 st) {
        vec2 i = floor(st);
        vec2 f = fract(st);
        float a = random(i);
        float b = random(i + vec2(1.0, 0.0));
        float c = random(i + vec2(0.0, 1.0));
        float d = random(i + vec2(1.0, 1.0));
        vec2 u = f * f * (3.0 - 2.0 * f);
        return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
    }

    void main() {
        vec2 texel = 1.0 / u_resolution;
        float h = texture2D(u_prevHeight, vUv).r; // Current height
        float h_prev = texture2D(u_prevHeight, vUv).g; // Previous height

        // Sample neighbors
        float h_n = texture2D(u_prevHeight, vUv + vec2(0.0, texel.y)).r;
        float h_s = texture2D(u_prevHeight, vUv - vec2(0.0, texel.y)).r;
        float h_e = texture2D(u_prevHeight, vUv + vec2(texel.x, 0.0)).r;
        float h_w = texture2D(u_prevHeight, vUv - vec2(texel.x, 0.0)).r;

        // Wave equation
        float c2 = u_waveSpeed * u_waveSpeed;
        float newHeight = 2.0 * h - h_prev + c2 * (h_n + h_s + h_e + h_w - 4.0 * h);
        newHeight *= u_damping;

        // Mouse force with randomness
        float dist = length(vUv - u_mouse);
        if (dist < 0.05 && u_mouseForce > 0.0) {
            float rand = noise(vUv + u_time) * 0.1 + 0.9; // Random strength variation
            newHeight += 0.15 * rand * u_mouseForce * exp(-dist * 100.0);
        }

        // Background motion (subtle waves)
        float t = u_time * 2.0;
        float background = noise(vUv * 2.0 + t) * 0.00002;
        background += sin(u_time * 0.5 + vUv.x * 5.0) * 0.0000002; // Periodic pulse
        newHeight += background;

        gl_FragColor = vec4(newHeight, h, 0.0, 1.0);
    }
`;

// Simulation material
const simMaterial = new THREE.ShaderMaterial({
    uniforms: simUniforms,
    vertexShader: simVertexShader,
    fragmentShader: simFragmentShader,
});

// Normal map shader (computes normals from height field)
const normalVertexShader = `
    varying vec2 vUv;
    void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

const normalFragmentShader = `
    uniform sampler2D u_heightMap;
    uniform vec2 u_resolution;
    varying vec2 vUv;

    void main() {
        vec2 texel = 1.0 / u_resolution;
        float h = texture2D(u_heightMap, vUv).r;
        float h_n = texture2D(u_heightMap, vUv + vec2(0.0, texel.y)).r;
        float h_s = texture2D(u_heightMap, vUv - vec2(0.0, texel.y)).r;
        float h_e = texture2D(u_heightMap, vUv + vec2(texel.x, 0.0)).r;
        float h_w = texture2D(u_heightMap, vUv - vec2(texel.x, 0.0)).r;

        // Compute normal using height gradients
        vec3 normal = normalize(vec3(h_w - h_e, h_s - h_n, 0.05));
        gl_FragColor = vec4(normal * 0.5 + 0.5, 1.0); // Pack to [0,1]
    }
`;

const normalMaterial = new THREE.ShaderMaterial({
    uniforms: {
        u_heightMap: { value: heightTarget1.texture },
        u_resolution: { value: new THREE.Vector2(resolution, resolution) },
    },
    vertexShader: normalVertexShader,
    fragmentShader: normalFragmentShader,
});

// Simulation and normal scenes
const simGeometry = new THREE.PlaneGeometry(2, 2);
const simMesh = new THREE.Mesh(simGeometry, simMaterial);
const simScene = new THREE.Scene();
simScene.add(simMesh);

const normalMesh = new THREE.Mesh(simGeometry, normalMaterial);
const normalScene = new THREE.Scene();
normalScene.add(normalMesh);

// Rendering uniforms
const renderUniforms = {
    u_heightMap: { value: heightTarget1.texture },
    u_normalMap: { value: normalTarget.texture },
    u_resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
    u_time: { value: 0.0 },
    u_lightPos: { value: new THREE.Vector2(0.5, 0.5) }, // Dynamic light position
};

// Rendering vertex shader
const renderVertexShader = `
    varying vec2 vUv;
    void main() {
        vUv = uv;
        gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
    }
`;

// Rendering fragment shader (with lighting and glowing effects)
const renderFragmentShader = `
    uniform sampler2D u_heightMap;
    uniform sampler2D u_normalMap;
    uniform vec2 u_resolution;
    uniform float u_time;
    uniform vec2 u_lightPos;
    varying vec2 vUv;

    // Noise for granularity
    float random(vec2 st) {
        return fract(sin(dot(st, vec2(127.1, 311.7))) * 43758.5453123);
    }

    float noise(vec2 st) {
        vec2 i = floor(st);
        vec2 f = fract(st);
        float a = random(i);
        float b = random(i + vec2(1.0, 0.0));
        float c = random(i + vec2(0.0, 1.0));
        float d = random(i + vec2(1.0, 1.0));
        vec2 u = f * f * (3.0 - 2.0 * f);
        return mix(a, b, u.x) + (c - a) * u.y * (1.0 - u.x) + (d - b) * u.x * u.y;
    }

    void main() {
        float height = texture2D(u_heightMap, vUv).r;
        vec3 normal = texture2D(u_normalMap, vUv).xyz * 2.0 - 1.0;
        vec2 p = vUv * u_resolution.xy / min(u_resolution.x, u_resolution.y);

        // Dynamic lighting
        vec3 lightDir = normalize(vec3(u_lightPos - vUv, 0.5));
        float diffuse = max(dot(normal, lightDir), 0.0);
        float specular = pow(diffuse, 32.0) * 0.5;

        // Base color with gradient
        vec3 baseColor = mix(vec3(0.0, 0.2, 0.5), vec3(0.1, 0.7, 1.0), 0.5 + height * 3.0);
        
        // Add granularity
        float n = noise(p * 15.0 + u_time * 0.2) * 0.1;
        baseColor += vec3(n);

        // Glowing wave crests
        float glow = abs(height) > 0.02 ? sin(height * 100.0 + u_time * 2.0) * 0.3 : 0.0;
        vec3 glowColor = vec3(0.2, 0.8, 1.0) * glow;
        vec3 glowColorB = vec3(0.2, 0.8, 1.0);

        // Combine lighting and effects
        vec3 color = baseColor * (0.5 + diffuse) + vec3(specular) + glowColor;
        color = clamp(color, 0.0, 1.0);

        gl_FragColor = vec4(color, 1);
    }
`;

// Rendering material
const renderMaterial = new THREE.ShaderMaterial({
    uniforms: renderUniforms,
    vertexShader: renderVertexShader,
    fragmentShader: renderFragmentShader,
});

// Rendering plane
const renderGeometry = new THREE.PlaneGeometry(2, 2);
const renderMesh = new THREE.Mesh(renderGeometry, renderMaterial);
scene.add(renderMesh);

// Handle window resize
window.addEventListener('resize', () => {
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderUniforms.u_resolution.value.set(window.innerWidth, window.innerHeight);
});

// Mouse interaction (click and drag)
const mouse = new THREE.Vector2();
let isMouseDown = false;
window.addEventListener('mousedown', (event) => {
    isMouseDown = true;
    updateMouse(event);
});
window.addEventListener('mousemove', (event) => {
    if (isMouseDown) updateMouse(event);
});
window.addEventListener('mouseup', () => {
    isMouseDown = false;
    simUniforms.u_mouseForce.value = 0.0;
});

function updateMouse(event) {
    mouse.x = event.clientX / window.innerWidth;
    mouse.y = 1.0 - event.clientY / window.innerHeight;
    simUniforms.u_mouse.value.set(mouse.x, mouse.y);
    simUniforms.u_mouseForce.value = 1.0;
}

// Animation loop
const clock = new THREE.Clock();
let currentHeightTarget = heightTarget1;
let nextHeightTarget = heightTarget2;
function animate() {
    requestAnimationFrame(animate);
    const t = clock.getElapsedTime();
    simUniforms.u_time.value = t;
    renderUniforms.u_time.value = t;

    // Update light position (follows mouse or oscillates)
    renderUniforms.u_lightPos.value.set(
        isMouseDown ? mouse.x : 0.5 + Math.sin(t * 0.5) * 0.3,
        isMouseDown ? mouse.y : 0.5 + Math.cos(t * 0.5) * 0.3
    );

    // Update simulation
    simUniforms.u_prevHeight.value = currentHeightTarget.texture;
    renderer.setRenderTarget(nextHeightTarget);
    renderer.render(simScene, camera);

    // Update normal map
    normalMaterial.uniforms.u_heightMap.value = nextHeightTarget.texture;
    renderer.setRenderTarget(normalTarget);
    renderer.render(normalScene, camera);

    // Render to screen
    renderUniforms.u_heightMap.value = nextHeightTarget.texture;
    renderer.setRenderTarget(null);
    renderer.render(scene, camera);

    // Swap buffers
    const temp = currentHeightTarget;
    currentHeightTarget = nextHeightTarget;
    nextHeightTarget = temp;
}
animate();