reze-engine 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -1
- package/dist/engine.d.ts +8 -25
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +235 -588
- package/dist/engine_r.d.ts +132 -0
- package/dist/engine_r.d.ts.map +1 -0
- package/dist/engine_r.js +1489 -0
- package/dist/engine_ts.d.ts +143 -0
- package/dist/engine_ts.d.ts.map +1 -0
- package/dist/engine_ts.js +1575 -0
- package/dist/model.d.ts +59 -11
- package/dist/model.d.ts.map +1 -1
- package/dist/model.js +416 -112
- package/package.json +1 -1
- package/src/engine.ts +266 -649
- package/src/model.ts +518 -156
- package/dist/ik.d.ts +0 -32
- package/dist/ik.d.ts.map +0 -1
- package/dist/ik.js +0 -337
- package/dist/pool-scene.d.ts +0 -52
- package/dist/pool-scene.d.ts.map +0 -1
- package/dist/pool-scene.js +0 -1122
- package/dist/pool.d.ts +0 -38
- package/dist/pool.d.ts.map +0 -1
- package/dist/pool.js +0 -422
package/dist/pool-scene.js
DELETED
|
@@ -1,1122 +0,0 @@
|
|
|
1
|
-
// Pool scene renderer - full raymarched pool scene from pool.html
|
|
2
|
-
export class PoolScene {
|
|
3
|
-
constructor(device, format, sampleCount, cameraUniformBuffer, config = {}) {
|
|
4
|
-
this.startTime = performance.now();
|
|
5
|
-
this.waterHeightTextureSize = 512; // Resolution of height map texture
|
|
6
|
-
this.waterPlaneSize = 300.0; // Size of the water plane in world units
|
|
7
|
-
this.waterPlaneSubdivisions = 128; // Number of subdivisions for the plane
|
|
8
|
-
this.moonRadius = 0.05; // Moon angular radius for shader-based rendering
|
|
9
|
-
this.device = device;
|
|
10
|
-
this.format = format;
|
|
11
|
-
this.sampleCount = sampleCount;
|
|
12
|
-
this.cameraUniformBuffer = cameraUniformBuffer;
|
|
13
|
-
this.config = {
|
|
14
|
-
cloudSharpness: config.cloudSharpness ?? 0.001,
|
|
15
|
-
windSpeed: config.windSpeed ?? [-43.0, 32.0],
|
|
16
|
-
bumpFactor: config.bumpFactor ?? 0.05,
|
|
17
|
-
bumpDistance: config.bumpDistance ?? 70.0,
|
|
18
|
-
skyColor: config.skyColor ?? [0.08, 0.08, 0.12],
|
|
19
|
-
moonlightColor: config.moonlightColor ?? [0.4, 0.4, 0.2],
|
|
20
|
-
skyByMoonlightColor: config.skyByMoonlightColor ?? [0.4, 0.2, 0.87],
|
|
21
|
-
waterColor: config.waterColor ?? [0.12, 0.13, 0.16],
|
|
22
|
-
exposure: config.exposure ?? 0.9,
|
|
23
|
-
epsilon: config.epsilon ?? 0.01,
|
|
24
|
-
marchSteps: config.marchSteps ?? 100,
|
|
25
|
-
moonPosition: config.moonPosition ?? [0, 1, 10],
|
|
26
|
-
};
|
|
27
|
-
this.moonPosition = this.config.moonPosition;
|
|
28
|
-
this.createWaterHeightTexture();
|
|
29
|
-
this.createPipeline();
|
|
30
|
-
this.createComputePipeline();
|
|
31
|
-
this.createUniformBuffer();
|
|
32
|
-
this.createWaterPlane();
|
|
33
|
-
this.createWaterPlanePipeline();
|
|
34
|
-
}
|
|
35
|
-
createWaterHeightTexture() {
|
|
36
|
-
// Create texture for water height map (RGBA16Float is filterable, we use R channel for height)
|
|
37
|
-
this.waterHeightTexture = this.device.createTexture({
|
|
38
|
-
size: [this.waterHeightTextureSize, this.waterHeightTextureSize, 1],
|
|
39
|
-
format: "rgba16float",
|
|
40
|
-
usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING,
|
|
41
|
-
});
|
|
42
|
-
this.waterHeightTextureView = this.waterHeightTexture.createView();
|
|
43
|
-
// Create sampler for reading the height map
|
|
44
|
-
this.waterHeightSampler = this.device.createSampler({
|
|
45
|
-
magFilter: "linear",
|
|
46
|
-
minFilter: "linear",
|
|
47
|
-
addressModeU: "repeat",
|
|
48
|
-
addressModeV: "repeat",
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
createComputePipeline() {
|
|
52
|
-
const computeShaderCode = `
|
|
53
|
-
struct Uniforms {
|
|
54
|
-
time: f32,
|
|
55
|
-
_padding1: f32,
|
|
56
|
-
resolution: vec2<f32>,
|
|
57
|
-
modelPos: vec3<f32>,
|
|
58
|
-
_padding2: f32,
|
|
59
|
-
worldBounds: vec4<f32>, // minX, minZ, maxX, maxZ
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
63
|
-
@group(0) @binding(1) var heightMap: texture_storage_2d<rgba16float, write>;
|
|
64
|
-
|
|
65
|
-
// Math matrices (column-major)
|
|
66
|
-
const m: mat3x3<f32> = mat3x3<f32>(
|
|
67
|
-
vec3<f32>(0.00, -0.90, -0.60),
|
|
68
|
-
vec3<f32>(0.90, 0.36, -0.48),
|
|
69
|
-
vec3<f32>(-0.60, -0.48, 0.34)
|
|
70
|
-
);
|
|
71
|
-
|
|
72
|
-
const mr: mat2x2<f32> = mat2x2<f32>(
|
|
73
|
-
0.84147, 0.54030,
|
|
74
|
-
0.54030, -0.84147
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
// Hash function
|
|
78
|
-
fn hash(n: f32) -> f32 {
|
|
79
|
-
return fract(sin(n) * 43758.5453);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
fn hash3(p: vec3<f32>) -> f32 {
|
|
83
|
-
var p3 = fract(p * 0.1031);
|
|
84
|
-
p3 += dot(p3, p3.yzx + 33.33);
|
|
85
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
fn noise3(x: vec3<f32>) -> f32 {
|
|
89
|
-
let p = floor(x);
|
|
90
|
-
var f = fract(x);
|
|
91
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
92
|
-
|
|
93
|
-
let n = p.x + p.y * 57.0 + 113.0 * p.z;
|
|
94
|
-
return mix(
|
|
95
|
-
mix(mix(hash3(p), hash3(p + vec3<f32>(1.0, 0.0, 0.0)), f.x),
|
|
96
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 0.0)), hash3(p + vec3<f32>(1.0, 1.0, 0.0)), f.x), f.y),
|
|
97
|
-
mix(mix(hash3(p + vec3<f32>(0.0, 0.0, 1.0)), hash3(p + vec3<f32>(1.0, 0.0, 1.0)), f.x),
|
|
98
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 1.0)), hash3(p + vec3<f32>(1.0, 1.0, 1.0)), f.x), f.y), f.z
|
|
99
|
-
);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
fn fbm3(p: vec3<f32>) -> f32 {
|
|
103
|
-
var f: f32 = 0.0;
|
|
104
|
-
var p_var = p;
|
|
105
|
-
f = 0.5000 * noise3(p_var);
|
|
106
|
-
p_var = m * p_var * 2.02;
|
|
107
|
-
f += 0.2500 * noise3(p_var);
|
|
108
|
-
p_var = m * p_var * 2.33;
|
|
109
|
-
f += 0.1250 * noise3(p_var);
|
|
110
|
-
p_var = m * p_var * 2.01;
|
|
111
|
-
f += 0.0625 * noise3(p_var);
|
|
112
|
-
return f / 0.9175;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
fn waterHeightMap(pos: vec2<f32>, time: f32) -> f32 {
|
|
116
|
-
var posm = pos;
|
|
117
|
-
posm = mr * posm;
|
|
118
|
-
posm.x += 0.25 * time;
|
|
119
|
-
let f = fbm3(vec3<f32>(posm * 1.9, time * 0.27));
|
|
120
|
-
var height = 0.5 + 0.1 * f;
|
|
121
|
-
height += 0.13 * sin(posm.x * 6.0 + 10.0 * f);
|
|
122
|
-
|
|
123
|
-
// Generate ripples from model position
|
|
124
|
-
let modelPos2D = vec2<f32>(uniforms.modelPos.x, uniforms.modelPos.z);
|
|
125
|
-
let d = length(pos - modelPos2D);
|
|
126
|
-
let ripple1 = 0.15 * cos(d * 50.0 - time * 4.0) * (1.0 - smoothstep(0.0, 1.5, d));
|
|
127
|
-
let ripple2 = 0.08 * cos(d * 35.0 - time * 3.0) * (1.0 - smoothstep(0.0, 2.0, d));
|
|
128
|
-
let ripple3 = 0.05 * cos(d * 25.0 - time * 2.0) * (1.0 - smoothstep(0.0, 3.0, d));
|
|
129
|
-
height += ripple1 + ripple2 + ripple3;
|
|
130
|
-
|
|
131
|
-
return height;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@compute @workgroup_size(8, 8)
|
|
135
|
-
fn cs_main(@builtin(global_invocation_id) globalId: vec3<u32>) {
|
|
136
|
-
let time = uniforms.time + 23.0;
|
|
137
|
-
let texelCoord = vec2<i32>(globalId.xy);
|
|
138
|
-
|
|
139
|
-
// Map texture coordinates to world space
|
|
140
|
-
let uv = vec2<f32>(globalId.xy) / vec2<f32>(${this.waterHeightTextureSize}.0);
|
|
141
|
-
let worldX = mix(uniforms.worldBounds.x, uniforms.worldBounds.z, uv.x);
|
|
142
|
-
let worldZ = mix(uniforms.worldBounds.y, uniforms.worldBounds.w, uv.y);
|
|
143
|
-
let worldPos = vec2<f32>(worldX, worldZ);
|
|
144
|
-
|
|
145
|
-
let height = waterHeightMap(worldPos, time);
|
|
146
|
-
textureStore(heightMap, texelCoord, vec4<f32>(height, 0.0, 0.0, 1.0));
|
|
147
|
-
}
|
|
148
|
-
`;
|
|
149
|
-
const module = this.device.createShaderModule({ code: computeShaderCode });
|
|
150
|
-
this.computePipeline = this.device.createComputePipeline({
|
|
151
|
-
layout: "auto",
|
|
152
|
-
compute: {
|
|
153
|
-
module,
|
|
154
|
-
entryPoint: "cs_main",
|
|
155
|
-
},
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
createPipeline() {
|
|
159
|
-
const shaderCode = `
|
|
160
|
-
struct CameraUniforms {
|
|
161
|
-
view: mat4x4<f32>,
|
|
162
|
-
projection: mat4x4<f32>,
|
|
163
|
-
viewPos: vec3<f32>,
|
|
164
|
-
_padding: f32,
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
struct Uniforms {
|
|
168
|
-
time: f32,
|
|
169
|
-
_padding1: f32,
|
|
170
|
-
resolution: vec2<f32>,
|
|
171
|
-
modelPos: vec3<f32>,
|
|
172
|
-
_padding2: f32,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
176
|
-
@group(0) @binding(1) var<uniform> uniforms: Uniforms;
|
|
177
|
-
|
|
178
|
-
struct VertexOutput {
|
|
179
|
-
@builtin(position) Position: vec4<f32>,
|
|
180
|
-
@location(0) uv: vec2<f32>,
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
@vertex
|
|
184
|
-
fn vs_main(@builtin(vertex_index) VertexIndex: u32) -> VertexOutput {
|
|
185
|
-
var pos = array<vec2<f32>, 6>(
|
|
186
|
-
vec2<f32>(-1.0, -1.0), vec2<f32>(1.0, -1.0), vec2<f32>(-1.0, 1.0),
|
|
187
|
-
vec2<f32>(-1.0, 1.0), vec2<f32>(1.0, -1.0), vec2<f32>(1.0, 1.0)
|
|
188
|
-
);
|
|
189
|
-
|
|
190
|
-
var output: VertexOutput;
|
|
191
|
-
output.Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
|
|
192
|
-
var uv = pos[VertexIndex] * 0.5 + 0.5;
|
|
193
|
-
output.uv = vec2<f32>(uv.x, 1.0 - uv.y);
|
|
194
|
-
return output;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Constants
|
|
198
|
-
const CLOUDSHARPNESS: f32 = ${this.config.cloudSharpness};
|
|
199
|
-
const WINDSPEED: vec2<f32> = vec2<f32>(${this.config.windSpeed[0]}, ${this.config.windSpeed[1]});
|
|
200
|
-
const BUMPFACTOR: f32 = ${this.config.bumpFactor};
|
|
201
|
-
const BUMPDISTANCE: f32 = ${this.config.bumpDistance};
|
|
202
|
-
const SKYCOLOR: vec3<f32> = vec3<f32>(${this.config.skyColor[0]}, ${this.config.skyColor[1]}, ${this.config.skyColor[2]});
|
|
203
|
-
const MOONLIGHTCOLOR: vec3<f32> = vec3<f32>(${this.config.moonlightColor[0]}, ${this.config.moonlightColor[1]}, ${this.config.moonlightColor[2]});
|
|
204
|
-
const SKYBYMOONLIGHTCOLOR: vec3<f32> = vec3<f32>(${this.config.skyByMoonlightColor[0]}, ${this.config.skyByMoonlightColor[1]}, ${this.config.skyByMoonlightColor[2]});
|
|
205
|
-
const WATERCOLOR: vec3<f32> = vec3<f32>(${this.config.waterColor[0]}, ${this.config.waterColor[1]}, ${this.config.waterColor[2]});
|
|
206
|
-
const EXPOSURE: f32 = ${this.config.exposure};
|
|
207
|
-
const EPSILON: f32 = ${this.config.epsilon};
|
|
208
|
-
const MARCHSTEPS: i32 = ${this.config.marchSteps};
|
|
209
|
-
|
|
210
|
-
// Math matrices (column-major)
|
|
211
|
-
const m: mat3x3<f32> = mat3x3<f32>(
|
|
212
|
-
vec3<f32>(0.00, -0.90, -0.60),
|
|
213
|
-
vec3<f32>(0.90, 0.36, -0.48),
|
|
214
|
-
vec3<f32>(-0.60, -0.48, 0.34)
|
|
215
|
-
);
|
|
216
|
-
|
|
217
|
-
const mr: mat2x2<f32> = mat2x2<f32>(
|
|
218
|
-
0.84147, 0.54030,
|
|
219
|
-
0.54030, -0.84147
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
// Hash function
|
|
223
|
-
fn hash(n: f32) -> f32 {
|
|
224
|
-
return fract(sin(n) * 43758.5453);
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Procedural noise
|
|
228
|
-
fn hash3(p: vec3<f32>) -> f32 {
|
|
229
|
-
var p3 = fract(p * 0.1031);
|
|
230
|
-
p3 += dot(p3, p3.yzx + 33.33);
|
|
231
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
fn noise3(x: vec3<f32>) -> f32 {
|
|
235
|
-
let p = floor(x);
|
|
236
|
-
var f = fract(x);
|
|
237
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
238
|
-
|
|
239
|
-
let n = p.x + p.y * 57.0 + 113.0 * p.z;
|
|
240
|
-
return mix(
|
|
241
|
-
mix(mix(hash3(p), hash3(p + vec3<f32>(1.0, 0.0, 0.0)), f.x),
|
|
242
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 0.0)), hash3(p + vec3<f32>(1.0, 1.0, 0.0)), f.x), f.y),
|
|
243
|
-
mix(mix(hash3(p + vec3<f32>(0.0, 0.0, 1.0)), hash3(p + vec3<f32>(1.0, 0.0, 1.0)), f.x),
|
|
244
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 1.0)), hash3(p + vec3<f32>(1.0, 1.0, 1.0)), f.x), f.y), f.z
|
|
245
|
-
);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
fn noise2(x: vec2<f32>) -> f32 {
|
|
249
|
-
let p = floor(x);
|
|
250
|
-
var f = fract(x);
|
|
251
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
252
|
-
|
|
253
|
-
let n = p.x + p.y * 57.0;
|
|
254
|
-
return mix(
|
|
255
|
-
mix(hash(f32(n)), hash(f32(n + 1.0)), f.x),
|
|
256
|
-
mix(hash(f32(n + 57.0)), hash(f32(n + 58.0)), f.x),
|
|
257
|
-
f.y
|
|
258
|
-
);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
fn fbm3(p: vec3<f32>) -> f32 {
|
|
262
|
-
var f: f32 = 0.0;
|
|
263
|
-
var p_var = p;
|
|
264
|
-
f = 0.5000 * noise3(p_var);
|
|
265
|
-
p_var = m * p_var * 2.02;
|
|
266
|
-
f += 0.2500 * noise3(p_var);
|
|
267
|
-
p_var = m * p_var * 2.33;
|
|
268
|
-
f += 0.1250 * noise3(p_var);
|
|
269
|
-
p_var = m * p_var * 2.01;
|
|
270
|
-
f += 0.0625 * noise3(p_var);
|
|
271
|
-
return f / 0.9175;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
fn fbm2(p: vec2<f32>) -> f32 {
|
|
275
|
-
var f: f32 = 0.0;
|
|
276
|
-
var p_var = p;
|
|
277
|
-
f = 0.5000 * noise2(p_var);
|
|
278
|
-
p_var = mr * p_var * 2.02;
|
|
279
|
-
f += 0.2500 * noise2(p_var);
|
|
280
|
-
p_var = mr * p_var * 2.33;
|
|
281
|
-
f += 0.1250 * noise2(p_var);
|
|
282
|
-
p_var = mr * p_var * 2.01;
|
|
283
|
-
f += 0.0625 * noise2(p_var);
|
|
284
|
-
return f / 0.9175;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Height maps
|
|
288
|
-
fn heightMap(pos: vec3<f32>) -> f32 {
|
|
289
|
-
let n = noise2(vec2<f32>(0.0, 4.2) + pos.xz * 0.14);
|
|
290
|
-
return 9.0 * (n - 0.7);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// Intersection functions
|
|
294
|
-
struct IntersectResult {
|
|
295
|
-
hit: bool,
|
|
296
|
-
dist: f32,
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
fn intersectPlane(ro: vec3<f32>, rd: vec3<f32>, height: f32) -> IntersectResult {
|
|
300
|
-
var result: IntersectResult;
|
|
301
|
-
result.hit = false;
|
|
302
|
-
result.dist = 0.0;
|
|
303
|
-
|
|
304
|
-
if (rd.y == 0.0) {
|
|
305
|
-
return result;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
let d = -(ro.y - height) / rd.y;
|
|
309
|
-
let d_clamped = min(100000.0, d);
|
|
310
|
-
if (d_clamped > 0.0) {
|
|
311
|
-
result.hit = true;
|
|
312
|
-
result.dist = d_clamped;
|
|
313
|
-
}
|
|
314
|
-
return result;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
fn intersectHeightMap(ro: vec3<f32>, rd: vec3<f32>, maxdist: f32) -> IntersectResult {
|
|
318
|
-
var result: IntersectResult;
|
|
319
|
-
result.hit = false;
|
|
320
|
-
result.dist = 0.0;
|
|
321
|
-
|
|
322
|
-
var dt: f32 = 0.3;
|
|
323
|
-
var dist: f32 = 0.0;
|
|
324
|
-
|
|
325
|
-
for (var i: i32 = 0; i < MARCHSTEPS; i++) {
|
|
326
|
-
if (result.hit || dist > maxdist) {
|
|
327
|
-
break;
|
|
328
|
-
}
|
|
329
|
-
dist += dt;
|
|
330
|
-
dt = min(dt * 1.1, 0.5);
|
|
331
|
-
let pos = ro + rd * dist;
|
|
332
|
-
if (heightMap(pos) >= pos.y) {
|
|
333
|
-
result.hit = true;
|
|
334
|
-
result.dist = dist;
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
return result;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
struct SphereResult {
|
|
341
|
-
hit: bool,
|
|
342
|
-
dist: f32,
|
|
343
|
-
normal: vec3<f32>,
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
fn intersectSphere(ro: vec3<f32>, rd: vec3<f32>, sph: vec4<f32>) -> SphereResult {
|
|
347
|
-
var result: SphereResult;
|
|
348
|
-
result.hit = false;
|
|
349
|
-
result.dist = 0.0;
|
|
350
|
-
result.normal = vec3<f32>(0.0);
|
|
351
|
-
|
|
352
|
-
let ds = ro - sph.xyz;
|
|
353
|
-
let bs = dot(rd, ds);
|
|
354
|
-
let cs = dot(ds, ds) - sph.w * sph.w;
|
|
355
|
-
let ts = bs * bs - cs;
|
|
356
|
-
|
|
357
|
-
if (ts > 0.0) {
|
|
358
|
-
let ts_val = -bs - sqrt(ts);
|
|
359
|
-
if (ts_val > 0.0) {
|
|
360
|
-
result.hit = true;
|
|
361
|
-
result.dist = ts_val;
|
|
362
|
-
result.normal = normalize(((ro + ts_val * rd) - sph.xyz) / sph.w);
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return result;
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// Cloud density - simplified to always be very light
|
|
369
|
-
fn cloudDensity(rd: vec3<f32>, time: f32) -> f32 {
|
|
370
|
-
let planeResult = intersectPlane(vec3<f32>(0.0, 0.0, 0.0), rd, 500.0);
|
|
371
|
-
let intersection = rd * planeResult.dist;
|
|
372
|
-
|
|
373
|
-
// Very light, consistent cloud cover (no time variation)
|
|
374
|
-
let cloudCover = 0.15;
|
|
375
|
-
// Simplified cloud calculation - much lighter
|
|
376
|
-
var cloud = 0.3 + 0.2 * fbm3(vec3<f32>((intersection.xz + WINDSPEED * time) * 0.001, time * 0.25));
|
|
377
|
-
|
|
378
|
-
cloud += 0.01 * noise2(intersection.xz - WINDSPEED * time * 0.01);
|
|
379
|
-
|
|
380
|
-
// Clamp to keep clouds very light
|
|
381
|
-
cloud = clamp(cloud, 0.0, 0.25);
|
|
382
|
-
|
|
383
|
-
// Simplified - always light clouds, no heavy blocking
|
|
384
|
-
cloud = cloud * 0.3; // Scale down to keep very light
|
|
385
|
-
|
|
386
|
-
return cloud;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Sky color
|
|
390
|
-
fn skyColorFunc(rd: vec3<f32>, time: f32, screenUV: vec2<f32>) -> vec3<f32> {
|
|
391
|
-
// Moon position from config
|
|
392
|
-
let moonPos = vec3<f32>(${this.moonPosition[0]}.0, ${this.moonPosition[1]}.0, ${this.moonPosition[2]}.0);
|
|
393
|
-
let moondir = normalize(moonPos);
|
|
394
|
-
|
|
395
|
-
// Glow follows moon position - strongest when looking directly at moon
|
|
396
|
-
let moonAngle = dot(moondir, rd);
|
|
397
|
-
let moonglow = clamp(moonAngle, 0.0, 1.0);
|
|
398
|
-
var col = SKYCOLOR * moondir.y;
|
|
399
|
-
col += 0.6 * SKYBYMOONLIGHTCOLOR * moonglow;
|
|
400
|
-
col += 0.8 * MOONLIGHTCOLOR * pow(moonglow, 35.0);
|
|
401
|
-
|
|
402
|
-
// Moon rendering - project moon to screen space for perfect circle (like original shader)
|
|
403
|
-
// Project moon position to screen space
|
|
404
|
-
let moonWorldPos = vec4<f32>(moonPos, 1.0);
|
|
405
|
-
let moonViewPos = camera.view * moonWorldPos;
|
|
406
|
-
let moonClipPos = camera.projection * moonViewPos;
|
|
407
|
-
|
|
408
|
-
// Only render moon if it's in front of camera
|
|
409
|
-
if (moonClipPos.w > 0.0) {
|
|
410
|
-
// Convert to NDC then to screen UV (0-1)
|
|
411
|
-
let moonNDC = moonClipPos.xy / moonClipPos.w;
|
|
412
|
-
let moonScreenUV = (moonNDC + 1.0) * 0.5;
|
|
413
|
-
// Flip Y for screen space (WebGPU has Y=0 at top)
|
|
414
|
-
let moonScreenPos = vec2<f32>(moonScreenUV.x, 1.0 - moonScreenUV.y);
|
|
415
|
-
|
|
416
|
-
// Calculate screen-space radius based on moon's angular size
|
|
417
|
-
// moonRadius is in radians, convert to screen-space units
|
|
418
|
-
let moonScreenRadius = ${this.moonRadius * 0.15}; // Adjusted for screen space
|
|
419
|
-
let moonDist2D = length(screenUV - moonScreenPos);
|
|
420
|
-
|
|
421
|
-
// Sharp circular moon disk (like original: length(uvInit-vec2(0.,.04))<.03)
|
|
422
|
-
let moonDisk = 1.0 - smoothstep(moonScreenRadius * 0.95, moonScreenRadius, moonDist2D);
|
|
423
|
-
let moonBrightness = 3.0;
|
|
424
|
-
let moonColor = vec3<f32>(1.0, 0.98, 0.92) * moonBrightness;
|
|
425
|
-
|
|
426
|
-
// Render moon disk
|
|
427
|
-
col = mix(col, moonColor, moonDisk);
|
|
428
|
-
|
|
429
|
-
// Add glow/bloom effect - extends beyond moon disk (like original sun flare)
|
|
430
|
-
let glowRadius = moonScreenRadius * 3.0;
|
|
431
|
-
let glowDist2D = moonDist2D / glowRadius;
|
|
432
|
-
let glowFactor = pow(max(0.0, 1.0 - glowDist2D), 2.0) * 1.5;
|
|
433
|
-
// Only add glow outside the moon disk
|
|
434
|
-
let glowMask = 1.0 - moonDisk;
|
|
435
|
-
col += moonColor * glowFactor * glowMask;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
// Stars only when moon is far or behind camera
|
|
439
|
-
let moonDist = acos(clamp(dot(moondir, rd), -1.0, 1.0));
|
|
440
|
-
if (moonDist > ${this.moonRadius * 3.0}) {
|
|
441
|
-
let rds = rd;
|
|
442
|
-
let v = 1.0 / (2.0 * (1.0 + rds.z));
|
|
443
|
-
let xy = vec2<f32>(rds.y * v, rds.x * v);
|
|
444
|
-
var s = noise2(rds.xz * 134.0);
|
|
445
|
-
s += noise2(rds.xz * 370.0);
|
|
446
|
-
s += noise2(rds.xz * 870.0);
|
|
447
|
-
s = pow(s, 19.0) * 0.00000001 * max(rd.y, 0.0);
|
|
448
|
-
if (s > 0.1) {
|
|
449
|
-
let backStars = vec3<f32>((1.0 - sin(xy.x * 20.0 + time * 13.0 * rds.x + xy.y * 30.0)) * 0.5 * s, s, s);
|
|
450
|
-
col += backStars;
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
// Apply clouds, but don't dim the moon if it's visible
|
|
455
|
-
let cloudDens = cloudDensity(rd, time);
|
|
456
|
-
if (moonDist > 1.0) {
|
|
457
|
-
col *= (1.0 - cloudDens);
|
|
458
|
-
} else {
|
|
459
|
-
// Moon is visible - don't dim it at all, keep it bright
|
|
460
|
-
// Moon stays fully visible through clouds
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
return col;
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
// Trace function (removed bottle intersection)
|
|
467
|
-
struct TraceResult {
|
|
468
|
-
material: i32, // 0=sky, 1=mountain, 2=water
|
|
469
|
-
intersection: vec3<f32>,
|
|
470
|
-
normal: vec3<f32>,
|
|
471
|
-
dist: f32,
|
|
472
|
-
color: vec3<f32>,
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
fn trace(ro: vec3<f32>, rd: vec3<f32>, currentDistance: f32, reflection: bool, time: f32) -> TraceResult {
|
|
476
|
-
var result: TraceResult;
|
|
477
|
-
result.material = 0;
|
|
478
|
-
result.dist = 100000.0;
|
|
479
|
-
result.intersection = vec3<f32>(0.0);
|
|
480
|
-
result.normal = vec3<f32>(0.0, 1.0, 0.0);
|
|
481
|
-
result.color = vec3<f32>(0.0);
|
|
482
|
-
|
|
483
|
-
// Skip mountains and water - only render sky
|
|
484
|
-
// Water is now handled by the 3D water plane mesh
|
|
485
|
-
|
|
486
|
-
// Always return sky color (no water intersection)
|
|
487
|
-
// Calculate screen UV for moon projection
|
|
488
|
-
let q = input.uv;
|
|
489
|
-
result.color = skyColorFunc(rd, time, q);
|
|
490
|
-
|
|
491
|
-
return result;
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
@fragment
|
|
495
|
-
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
496
|
-
let time = uniforms.time + 23.0;
|
|
497
|
-
let q = input.uv;
|
|
498
|
-
var p = -1.0 + 2.0 * q;
|
|
499
|
-
p.y = -p.y; // Flip Y: WebGPU (0,0) is top-left, ShaderToy (0,0) is bottom-left
|
|
500
|
-
p.x *= uniforms.resolution.x / uniforms.resolution.y;
|
|
501
|
-
|
|
502
|
-
// Use engine's camera to reconstruct ray direction
|
|
503
|
-
// Extract camera basis vectors from view matrix
|
|
504
|
-
let viewRight = vec3<f32>(camera.view[0].x, camera.view[1].x, camera.view[2].x);
|
|
505
|
-
let viewUp = vec3<f32>(camera.view[0].y, camera.view[1].y, camera.view[2].y);
|
|
506
|
-
let viewForward = vec3<f32>(camera.view[0].z, camera.view[1].z, camera.view[2].z);
|
|
507
|
-
|
|
508
|
-
// Extract FOV and aspect from projection matrix
|
|
509
|
-
let f = camera.projection[1].y;
|
|
510
|
-
let aspect = f / camera.projection[0].x;
|
|
511
|
-
let fovY = 2.0 * atan(1.0 / f);
|
|
512
|
-
|
|
513
|
-
// Compute ray direction in view space
|
|
514
|
-
let tanHalfFov = tan(fovY * 0.5);
|
|
515
|
-
let viewDir = vec3<f32>(
|
|
516
|
-
p.x * aspect * tanHalfFov,
|
|
517
|
-
p.y * tanHalfFov,
|
|
518
|
-
-1.0 // In view space, forward is -Z
|
|
519
|
-
);
|
|
520
|
-
|
|
521
|
-
// Transform to world space
|
|
522
|
-
let rd = normalize(
|
|
523
|
-
viewDir.x * viewRight +
|
|
524
|
-
viewDir.y * viewUp +
|
|
525
|
-
(-viewDir.z) * viewForward
|
|
526
|
-
);
|
|
527
|
-
|
|
528
|
-
// Ray origin is camera position
|
|
529
|
-
let ro = camera.viewPos;
|
|
530
|
-
|
|
531
|
-
var traceResult = trace(ro, rd, 0.0, false, time);
|
|
532
|
-
|
|
533
|
-
// No water reflections in background - water plane handles its own reflections
|
|
534
|
-
|
|
535
|
-
var col = pow(traceResult.color, vec3<f32>(EXPOSURE));
|
|
536
|
-
col = clamp(col, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
537
|
-
|
|
538
|
-
col *= 0.25 + 0.75 * pow(16.0 * q.x * q.y * (1.0 - q.x) * (1.0 - q.y), 0.15);
|
|
539
|
-
|
|
540
|
-
return vec4<f32>(col, 1.0);
|
|
541
|
-
}
|
|
542
|
-
`;
|
|
543
|
-
const module = this.device.createShaderModule({ code: shaderCode });
|
|
544
|
-
this.pipeline = this.device.createRenderPipeline({
|
|
545
|
-
layout: "auto",
|
|
546
|
-
vertex: {
|
|
547
|
-
module,
|
|
548
|
-
entryPoint: "vs_main",
|
|
549
|
-
},
|
|
550
|
-
fragment: {
|
|
551
|
-
module,
|
|
552
|
-
entryPoint: "fs_main",
|
|
553
|
-
targets: [{ format: this.format }],
|
|
554
|
-
},
|
|
555
|
-
primitive: {
|
|
556
|
-
topology: "triangle-list",
|
|
557
|
-
},
|
|
558
|
-
multisample: {
|
|
559
|
-
count: this.sampleCount,
|
|
560
|
-
},
|
|
561
|
-
depthStencil: {
|
|
562
|
-
depthWriteEnabled: false, // Background doesn't write depth - model will render on top
|
|
563
|
-
depthCompare: "always", // Always pass depth test
|
|
564
|
-
format: "depth24plus-stencil8",
|
|
565
|
-
},
|
|
566
|
-
});
|
|
567
|
-
}
|
|
568
|
-
createUniformBuffer() {
|
|
569
|
-
// Uniforms: time, padding, resX, resY, modelPos.x, modelPos.y, modelPos.z, padding
|
|
570
|
-
const uniformBufferSize = 32;
|
|
571
|
-
this.uniformBuffer = this.device.createBuffer({
|
|
572
|
-
size: uniformBufferSize,
|
|
573
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
574
|
-
});
|
|
575
|
-
this.bindGroup = this.device.createBindGroup({
|
|
576
|
-
layout: this.pipeline.getBindGroupLayout(0),
|
|
577
|
-
entries: [
|
|
578
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
579
|
-
{ binding: 1, resource: { buffer: this.uniformBuffer } },
|
|
580
|
-
],
|
|
581
|
-
});
|
|
582
|
-
// Create compute bind group
|
|
583
|
-
const computeUniformBufferSize = 48; // time, padding, resX, resY, modelPos.xyz, padding, worldBounds.xyzw
|
|
584
|
-
this.computeUniformBuffer = this.device.createBuffer({
|
|
585
|
-
size: computeUniformBufferSize,
|
|
586
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
587
|
-
});
|
|
588
|
-
this.computeBindGroup = this.device.createBindGroup({
|
|
589
|
-
layout: this.computePipeline.getBindGroupLayout(0),
|
|
590
|
-
entries: [
|
|
591
|
-
{ binding: 0, resource: { buffer: this.computeUniformBuffer } },
|
|
592
|
-
{ binding: 1, resource: this.waterHeightTextureView },
|
|
593
|
-
],
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
dispatchCompute(encoder, width, height, modelPos = [0, 0, 0]) {
|
|
597
|
-
const now = performance.now();
|
|
598
|
-
const time = (now - this.startTime) / 1000;
|
|
599
|
-
// Calculate world bounds for height map (centered around model position)
|
|
600
|
-
const worldSize = 50.0;
|
|
601
|
-
const worldBounds = [
|
|
602
|
-
modelPos[0] - worldSize, // minX
|
|
603
|
-
modelPos[2] - worldSize, // minZ
|
|
604
|
-
modelPos[0] + worldSize, // maxX
|
|
605
|
-
modelPos[2] + worldSize, // maxZ
|
|
606
|
-
];
|
|
607
|
-
// Write compute uniforms: time, padding, resX, resY, modelPos.xyz, padding, worldBounds.xyzw
|
|
608
|
-
const computeUniforms = new Float32Array([
|
|
609
|
-
time,
|
|
610
|
-
0,
|
|
611
|
-
width,
|
|
612
|
-
height,
|
|
613
|
-
modelPos[0],
|
|
614
|
-
modelPos[1],
|
|
615
|
-
modelPos[2],
|
|
616
|
-
0,
|
|
617
|
-
worldBounds[0],
|
|
618
|
-
worldBounds[1],
|
|
619
|
-
worldBounds[2],
|
|
620
|
-
worldBounds[3],
|
|
621
|
-
]);
|
|
622
|
-
this.device.queue.writeBuffer(this.computeUniformBuffer, 0, computeUniforms);
|
|
623
|
-
// Dispatch compute shader to generate water height map
|
|
624
|
-
const computePass = encoder.beginComputePass();
|
|
625
|
-
computePass.setPipeline(this.computePipeline);
|
|
626
|
-
computePass.setBindGroup(0, this.computeBindGroup);
|
|
627
|
-
computePass.dispatchWorkgroups(Math.ceil(this.waterHeightTextureSize / 8), Math.ceil(this.waterHeightTextureSize / 8), 1);
|
|
628
|
-
computePass.end();
|
|
629
|
-
}
|
|
630
|
-
createWaterPlane() {
|
|
631
|
-
const subdivisions = this.waterPlaneSubdivisions;
|
|
632
|
-
const size = this.waterPlaneSize;
|
|
633
|
-
const halfSize = size / 2.0;
|
|
634
|
-
// Generate vertices
|
|
635
|
-
const vertices = [];
|
|
636
|
-
const indices = [];
|
|
637
|
-
const step = size / subdivisions;
|
|
638
|
-
// Create grid of vertices
|
|
639
|
-
for (let z = 0; z <= subdivisions; z++) {
|
|
640
|
-
for (let x = 0; x <= subdivisions; x++) {
|
|
641
|
-
const posX = -halfSize + x * step;
|
|
642
|
-
const posZ = -halfSize + z * step;
|
|
643
|
-
const u = x / subdivisions;
|
|
644
|
-
const v = z / subdivisions;
|
|
645
|
-
// Position (x, y, z) - y will be displaced by height map in shader
|
|
646
|
-
vertices.push(posX, 15.0, posZ);
|
|
647
|
-
// UV coordinates for sampling height map
|
|
648
|
-
vertices.push(u, v);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
// Create indices for triangles
|
|
652
|
-
for (let z = 0; z < subdivisions; z++) {
|
|
653
|
-
for (let x = 0; x < subdivisions; x++) {
|
|
654
|
-
const i = z * (subdivisions + 1) + x;
|
|
655
|
-
// First triangle
|
|
656
|
-
indices.push(i);
|
|
657
|
-
indices.push(i + 1);
|
|
658
|
-
indices.push(i + subdivisions + 1);
|
|
659
|
-
// Second triangle
|
|
660
|
-
indices.push(i + 1);
|
|
661
|
-
indices.push(i + subdivisions + 2);
|
|
662
|
-
indices.push(i + subdivisions + 1);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
// Create vertex buffer
|
|
666
|
-
const vertexData = new Float32Array(vertices);
|
|
667
|
-
this.waterPlaneVertexBuffer = this.device.createBuffer({
|
|
668
|
-
size: vertexData.byteLength,
|
|
669
|
-
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
|
|
670
|
-
});
|
|
671
|
-
this.device.queue.writeBuffer(this.waterPlaneVertexBuffer, 0, vertexData);
|
|
672
|
-
// Create index buffer
|
|
673
|
-
const indexData = new Uint32Array(indices);
|
|
674
|
-
this.waterPlaneIndexBuffer = this.device.createBuffer({
|
|
675
|
-
size: indexData.byteLength,
|
|
676
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
677
|
-
});
|
|
678
|
-
this.device.queue.writeBuffer(this.waterPlaneIndexBuffer, 0, indexData);
|
|
679
|
-
}
|
|
680
|
-
createWaterPlanePipeline() {
|
|
681
|
-
const shaderCode = `
|
|
682
|
-
struct CameraUniforms {
|
|
683
|
-
view: mat4x4<f32>,
|
|
684
|
-
projection: mat4x4<f32>,
|
|
685
|
-
viewPos: vec3<f32>,
|
|
686
|
-
_padding: f32,
|
|
687
|
-
};
|
|
688
|
-
|
|
689
|
-
struct Uniforms {
|
|
690
|
-
time: f32,
|
|
691
|
-
_padding1: f32,
|
|
692
|
-
resolution: vec2<f32>,
|
|
693
|
-
modelPos: vec3<f32>,
|
|
694
|
-
_padding2: f32,
|
|
695
|
-
};
|
|
696
|
-
|
|
697
|
-
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
698
|
-
@group(0) @binding(1) var<uniform> uniforms: Uniforms;
|
|
699
|
-
@group(0) @binding(2) var waterHeightSampler: sampler;
|
|
700
|
-
@group(0) @binding(3) var waterHeightTexture: texture_2d<f32>;
|
|
701
|
-
|
|
702
|
-
struct VertexInput {
|
|
703
|
-
@location(0) position: vec3<f32>,
|
|
704
|
-
@location(1) uv: vec2<f32>,
|
|
705
|
-
};
|
|
706
|
-
|
|
707
|
-
struct VertexOutput {
|
|
708
|
-
@builtin(position) Position: vec4<f32>,
|
|
709
|
-
@location(0) worldPos: vec3<f32>,
|
|
710
|
-
@location(1) uv: vec2<f32>,
|
|
711
|
-
@location(2) viewPos: vec3<f32>,
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
const WATER_HEIGHT_WORLD_SIZE: f32 = 50.0;
|
|
715
|
-
const WATER_HEIGHT_SCALE: f32 = 0.1; // Scale factor for height displacement
|
|
716
|
-
|
|
717
|
-
@vertex
|
|
718
|
-
fn vs_main(input: VertexInput) -> VertexOutput {
|
|
719
|
-
// Sample height map to displace vertex
|
|
720
|
-
let modelPos2D = vec2<f32>(uniforms.modelPos.x, uniforms.modelPos.z);
|
|
721
|
-
let worldPos2D = vec2<f32>(input.position.x, input.position.z);
|
|
722
|
-
let offset = worldPos2D - modelPos2D;
|
|
723
|
-
let texCoord = (offset / WATER_HEIGHT_WORLD_SIZE) * 0.5 + 0.5;
|
|
724
|
-
|
|
725
|
-
let height = textureSampleLevel(waterHeightTexture, waterHeightSampler, texCoord, 0.0).r;
|
|
726
|
-
|
|
727
|
-
// Displace vertex position based on height map
|
|
728
|
-
let displacedPos = vec3<f32>(
|
|
729
|
-
input.position.x,
|
|
730
|
-
input.position.y + height * WATER_HEIGHT_SCALE,
|
|
731
|
-
input.position.z
|
|
732
|
-
);
|
|
733
|
-
|
|
734
|
-
// Transform to clip space
|
|
735
|
-
let worldPos = displacedPos;
|
|
736
|
-
let worldPos4 = vec4<f32>(worldPos, 1.0);
|
|
737
|
-
let viewPos4 = camera.view * worldPos4;
|
|
738
|
-
let clipPos = camera.projection * viewPos4;
|
|
739
|
-
|
|
740
|
-
var output: VertexOutput;
|
|
741
|
-
output.worldPos = worldPos;
|
|
742
|
-
output.uv = input.uv;
|
|
743
|
-
output.viewPos = viewPos4.xyz;
|
|
744
|
-
output.Position = clipPos;
|
|
745
|
-
return output;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
const WATERCOLOR: vec3<f32> = vec3<f32>(${this.config.waterColor[0]}, ${this.config.waterColor[1]}, ${this.config.waterColor[2]});
|
|
749
|
-
const MOONLIGHTCOLOR: vec3<f32> = vec3<f32>(${this.config.moonlightColor[0]}, ${this.config.moonlightColor[1]}, ${this.config.moonlightColor[2]});
|
|
750
|
-
const SKYCOLOR: vec3<f32> = vec3<f32>(${this.config.skyColor[0]}, ${this.config.skyColor[1]}, ${this.config.skyColor[2]});
|
|
751
|
-
const SKYBYMOONLIGHTCOLOR: vec3<f32> = vec3<f32>(${this.config.skyByMoonlightColor[0]}, ${this.config.skyByMoonlightColor[1]}, ${this.config.skyByMoonlightColor[2]});
|
|
752
|
-
const CLOUDSHARPNESS: f32 = ${this.config.cloudSharpness};
|
|
753
|
-
const WINDSPEED: vec2<f32> = vec2<f32>(${this.config.windSpeed[0]}, ${this.config.windSpeed[1]});
|
|
754
|
-
const EXPOSURE: f32 = ${this.config.exposure};
|
|
755
|
-
const EPSILON: f32 = ${this.config.epsilon};
|
|
756
|
-
|
|
757
|
-
// Math matrices (column-major)
|
|
758
|
-
const m: mat3x3<f32> = mat3x3<f32>(
|
|
759
|
-
vec3<f32>(0.00, -0.90, -0.60),
|
|
760
|
-
vec3<f32>(0.90, 0.36, -0.48),
|
|
761
|
-
vec3<f32>(-0.60, -0.48, 0.34)
|
|
762
|
-
);
|
|
763
|
-
|
|
764
|
-
// Hash function
|
|
765
|
-
fn hash(n: f32) -> f32 {
|
|
766
|
-
return fract(sin(n) * 43758.5453);
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
fn hash3(p: vec3<f32>) -> f32 {
|
|
770
|
-
var p3 = fract(p * 0.1031);
|
|
771
|
-
p3 += dot(p3, p3.yzx + 33.33);
|
|
772
|
-
return fract((p3.x + p3.y) * p3.z);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
fn noise3(x: vec3<f32>) -> f32 {
|
|
776
|
-
let p = floor(x);
|
|
777
|
-
var f = fract(x);
|
|
778
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
779
|
-
|
|
780
|
-
let n = p.x + p.y * 57.0 + 113.0 * p.z;
|
|
781
|
-
return mix(
|
|
782
|
-
mix(mix(hash3(p), hash3(p + vec3<f32>(1.0, 0.0, 0.0)), f.x),
|
|
783
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 0.0)), hash3(p + vec3<f32>(1.0, 1.0, 0.0)), f.x), f.y),
|
|
784
|
-
mix(mix(hash3(p + vec3<f32>(0.0, 0.0, 1.0)), hash3(p + vec3<f32>(1.0, 0.0, 1.0)), f.x),
|
|
785
|
-
mix(hash3(p + vec3<f32>(0.0, 1.0, 1.0)), hash3(p + vec3<f32>(1.0, 1.0, 1.0)), f.x), f.y), f.z
|
|
786
|
-
);
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
fn noise2(x: vec2<f32>) -> f32 {
|
|
790
|
-
let p = floor(x);
|
|
791
|
-
var f = fract(x);
|
|
792
|
-
f = f * f * (3.0 - 2.0 * f);
|
|
793
|
-
|
|
794
|
-
let n = p.x + p.y * 57.0;
|
|
795
|
-
return mix(
|
|
796
|
-
mix(hash(f32(n)), hash(f32(n + 1.0)), f.x),
|
|
797
|
-
mix(hash(f32(n + 57.0)), hash(f32(n + 58.0)), f.x),
|
|
798
|
-
f.y
|
|
799
|
-
);
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
fn fbm3(p: vec3<f32>) -> f32 {
|
|
803
|
-
var f: f32 = 0.0;
|
|
804
|
-
var p_var = p;
|
|
805
|
-
f = 0.5000 * noise3(p_var);
|
|
806
|
-
p_var = m * p_var * 2.02;
|
|
807
|
-
f += 0.2500 * noise3(p_var);
|
|
808
|
-
p_var = m * p_var * 2.33;
|
|
809
|
-
f += 0.1250 * noise3(p_var);
|
|
810
|
-
p_var = m * p_var * 2.01;
|
|
811
|
-
f += 0.0625 * noise3(p_var);
|
|
812
|
-
return f / 0.9175;
|
|
813
|
-
}
|
|
814
|
-
|
|
815
|
-
// Intersection functions for cloud density
|
|
816
|
-
struct IntersectResult {
|
|
817
|
-
hit: bool,
|
|
818
|
-
dist: f32,
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
fn intersectPlane(ro: vec3<f32>, rd: vec3<f32>, height: f32) -> IntersectResult {
|
|
822
|
-
var result: IntersectResult;
|
|
823
|
-
result.hit = false;
|
|
824
|
-
result.dist = 0.0;
|
|
825
|
-
|
|
826
|
-
if (rd.y == 0.0) {
|
|
827
|
-
return result;
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
let d = -(ro.y - height) / rd.y;
|
|
831
|
-
let d_clamped = min(100000.0, d);
|
|
832
|
-
if (d_clamped > 0.0) {
|
|
833
|
-
result.hit = true;
|
|
834
|
-
result.dist = d_clamped;
|
|
835
|
-
}
|
|
836
|
-
return result;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
// Cloud density - simplified to always be very light
|
|
840
|
-
fn cloudDensity(rd: vec3<f32>, time: f32) -> f32 {
|
|
841
|
-
let planeResult = intersectPlane(vec3<f32>(0.0, 0.0, 0.0), rd, 500.0);
|
|
842
|
-
let intersection = rd * planeResult.dist;
|
|
843
|
-
|
|
844
|
-
// Very light, consistent cloud cover (no time variation)
|
|
845
|
-
let cloudCover = 0.15;
|
|
846
|
-
// Simplified cloud calculation - much lighter
|
|
847
|
-
var cloud = 0.3 + 0.2 * fbm3(vec3<f32>((intersection.xz + WINDSPEED * time) * 0.001, time * 0.25));
|
|
848
|
-
|
|
849
|
-
cloud += 0.01 * noise2(intersection.xz - WINDSPEED * time * 0.01);
|
|
850
|
-
|
|
851
|
-
// Clamp to keep clouds very light
|
|
852
|
-
cloud = clamp(cloud, 0.0, 0.25);
|
|
853
|
-
|
|
854
|
-
// Simplified - always light clouds, no heavy blocking
|
|
855
|
-
cloud = cloud * 0.3; // Scale down to keep very light
|
|
856
|
-
|
|
857
|
-
return cloud;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
struct SphereResult {
|
|
861
|
-
hit: bool,
|
|
862
|
-
dist: f32,
|
|
863
|
-
normal: vec3<f32>,
|
|
864
|
-
}
|
|
865
|
-
|
|
866
|
-
fn intersectSphere(ro: vec3<f32>, rd: vec3<f32>, sph: vec4<f32>) -> SphereResult {
|
|
867
|
-
var result: SphereResult;
|
|
868
|
-
result.hit = false;
|
|
869
|
-
result.dist = 0.0;
|
|
870
|
-
result.normal = vec3<f32>(0.0);
|
|
871
|
-
|
|
872
|
-
let ds = ro - sph.xyz;
|
|
873
|
-
let bs = dot(rd, ds);
|
|
874
|
-
let cs = dot(ds, ds) - sph.w * sph.w;
|
|
875
|
-
let ts = bs * bs - cs;
|
|
876
|
-
|
|
877
|
-
if (ts > 0.0) {
|
|
878
|
-
let ts_val = -bs - sqrt(ts);
|
|
879
|
-
if (ts_val > 0.0) {
|
|
880
|
-
result.hit = true;
|
|
881
|
-
result.dist = ts_val;
|
|
882
|
-
result.normal = normalize(((ro + ts_val * rd) - sph.xyz) / sph.w);
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
return result;
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// Sky color function (for reflections - uses angular distance since reflections are already distorted)
|
|
889
|
-
fn skyColorFunc(rd: vec3<f32>, time: f32) -> vec3<f32> {
|
|
890
|
-
// Moon position from config
|
|
891
|
-
let moonPos = vec3<f32>(${this.moonPosition[0]}.0, ${this.moonPosition[1]}.0, ${this.moonPosition[2]}.0);
|
|
892
|
-
let moondir = normalize(moonPos);
|
|
893
|
-
|
|
894
|
-
// Glow follows moon position - strongest when looking directly at moon
|
|
895
|
-
let moonAngle = dot(moondir, rd);
|
|
896
|
-
let moonglow = clamp(moonAngle, 0.0, 1.0);
|
|
897
|
-
var col = SKYCOLOR * moondir.y;
|
|
898
|
-
col += 0.6 * SKYBYMOONLIGHTCOLOR * moonglow;
|
|
899
|
-
col += 0.8 * MOONLIGHTCOLOR * pow(moonglow, 35.0);
|
|
900
|
-
|
|
901
|
-
// Moon rendering - use angular distance (OK for reflections which are already distorted)
|
|
902
|
-
let moonAngularRadius = ${this.moonRadius};
|
|
903
|
-
let angleToMoon = acos(clamp(dot(moondir, rd), -1.0, 1.0));
|
|
904
|
-
let moonDist = angleToMoon / moonAngularRadius;
|
|
905
|
-
|
|
906
|
-
// Sharp circular moon disk
|
|
907
|
-
let moonDisk = 1.0 - smoothstep(0.95, 1.0, moonDist);
|
|
908
|
-
let moonBrightness = 3.0;
|
|
909
|
-
let moonColor = vec3<f32>(1.0, 0.98, 0.92) * moonBrightness;
|
|
910
|
-
|
|
911
|
-
// Render moon disk
|
|
912
|
-
col = mix(col, moonColor, moonDisk);
|
|
913
|
-
|
|
914
|
-
// Add glow/bloom effect
|
|
915
|
-
let glowDist = angleToMoon / (moonAngularRadius * 2.0);
|
|
916
|
-
let glowFactor = pow(max(0.0, 1.0 - glowDist), 2.5) * 1.2;
|
|
917
|
-
let glowMask = 1.0 - moonDisk;
|
|
918
|
-
col += moonColor * glowFactor * glowMask;
|
|
919
|
-
|
|
920
|
-
// Stars only when not looking at moon
|
|
921
|
-
if (moonDist > 1.5) {
|
|
922
|
-
let rds = rd;
|
|
923
|
-
let v = 1.0 / (2.0 * (1.0 + rds.z));
|
|
924
|
-
let xy = vec2<f32>(rds.y * v, rds.x * v);
|
|
925
|
-
var s = noise2(rds.xz * 134.0);
|
|
926
|
-
s += noise2(rds.xz * 370.0);
|
|
927
|
-
s += noise2(rds.xz * 870.0);
|
|
928
|
-
s = pow(s, 19.0) * 0.00000001 * max(rd.y, 0.0);
|
|
929
|
-
if (s > 0.1) {
|
|
930
|
-
let backStars = vec3<f32>((1.0 - sin(xy.x * 20.0 + time * 13.0 * rds.x + xy.y * 30.0)) * 0.5 * s, s, s);
|
|
931
|
-
col += backStars;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
|
|
935
|
-
// Apply clouds, but don't dim the moon if it's visible
|
|
936
|
-
let cloudDens = cloudDensity(rd, time);
|
|
937
|
-
if (moonDist > 1.0) {
|
|
938
|
-
col *= (1.0 - cloudDens);
|
|
939
|
-
} else {
|
|
940
|
-
// Moon is visible - don't dim it at all, keep it bright
|
|
941
|
-
// Moon stays fully visible through clouds
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
return col;
|
|
945
|
-
}
|
|
946
|
-
|
|
947
|
-
fn calculateNormal(pos: vec2<f32>, time: f32) -> vec3<f32> {
|
|
948
|
-
let modelPos2D = vec2<f32>(uniforms.modelPos.x, uniforms.modelPos.z);
|
|
949
|
-
let offset = pos - modelPos2D;
|
|
950
|
-
let texCoord = (offset / WATER_HEIGHT_WORLD_SIZE) * 0.5 + 0.5;
|
|
951
|
-
|
|
952
|
-
let dx = vec2<f32>(EPSILON, 0.0);
|
|
953
|
-
let dz = vec2<f32>(0.0, EPSILON);
|
|
954
|
-
|
|
955
|
-
let heightL = textureSampleLevel(waterHeightTexture, waterHeightSampler, texCoord - dx * 0.5, 0.0).r;
|
|
956
|
-
let heightR = textureSampleLevel(waterHeightTexture, waterHeightSampler, texCoord + dx * 0.5, 0.0).r;
|
|
957
|
-
let heightD = textureSampleLevel(waterHeightTexture, waterHeightSampler, texCoord - dz * 0.5, 0.0).r;
|
|
958
|
-
let heightU = textureSampleLevel(waterHeightTexture, waterHeightSampler, texCoord + dz * 0.5, 0.0).r;
|
|
959
|
-
|
|
960
|
-
let normal = vec3<f32>(
|
|
961
|
-
(heightL - heightR) * WATER_HEIGHT_SCALE / EPSILON,
|
|
962
|
-
1.0,
|
|
963
|
-
(heightD - heightU) * WATER_HEIGHT_SCALE / EPSILON
|
|
964
|
-
);
|
|
965
|
-
return normalize(normal);
|
|
966
|
-
}
|
|
967
|
-
|
|
968
|
-
@fragment
|
|
969
|
-
fn fs_main(input: VertexOutput) -> @location(0) vec4<f32> {
|
|
970
|
-
let time = uniforms.time + 23.0;
|
|
971
|
-
|
|
972
|
-
// Calculate distance from camera
|
|
973
|
-
let dist = length(input.viewPos);
|
|
974
|
-
|
|
975
|
-
// Calculate normal from height map
|
|
976
|
-
let normal = calculateNormal(input.worldPos.xz, time);
|
|
977
|
-
|
|
978
|
-
// Calculate view direction (from water surface to camera)
|
|
979
|
-
let viewDir = normalize(camera.viewPos - input.worldPos);
|
|
980
|
-
|
|
981
|
-
// Calculate reflection direction
|
|
982
|
-
let reflectDir = reflect(-viewDir, normal);
|
|
983
|
-
|
|
984
|
-
// Sample sky color with clouds in reflection direction
|
|
985
|
-
let skyReflection = skyColorFunc(reflectDir, time);
|
|
986
|
-
|
|
987
|
-
// Also sample sky color directly above (for atmospheric blending)
|
|
988
|
-
let skyColorAbove = skyColorFunc(vec3<f32>(0.0, 1.0, 0.0), time);
|
|
989
|
-
|
|
990
|
-
// Moon position from config
|
|
991
|
-
let moonPos = vec3<f32>(${this.moonPosition[0]}.0, ${this.moonPosition[1]}.0, ${this.moonPosition[2]}.0);
|
|
992
|
-
let moondir = normalize(moonPos);
|
|
993
|
-
|
|
994
|
-
// Calculate lighting
|
|
995
|
-
let diff = clamp(dot(normal, moondir), 0.0, 1.0);
|
|
996
|
-
|
|
997
|
-
// Base water color with lighting - blend more with sky color
|
|
998
|
-
var col = mix(WATERCOLOR, SKYCOLOR * 1.2, 0.3) * MOONLIGHTCOLOR * diff * 1.5;
|
|
999
|
-
|
|
1000
|
-
// Add reflection (fresnel effect - more reflection at glancing angles)
|
|
1001
|
-
let fresnel = pow(1.0 - max(dot(viewDir, normal), 0.0), 2.0);
|
|
1002
|
-
let reflectStrength = 0.9 * fresnel;
|
|
1003
|
-
col = mix(col, skyReflection, reflectStrength);
|
|
1004
|
-
|
|
1005
|
-
// Distance fog - blend with sky color at distance (updated for larger plane)
|
|
1006
|
-
let fogStart = 60.0;
|
|
1007
|
-
let fogEnd = 200.0;
|
|
1008
|
-
let fogFactor = smoothstep(fogStart, fogEnd, dist);
|
|
1009
|
-
col = mix(col, skyColorAbove, fogFactor * 0.8);
|
|
1010
|
-
|
|
1011
|
-
// Edge fading - fade out at the edges of the plane (updated for larger plane)
|
|
1012
|
-
let edgeDistance = length(input.worldPos.xz - vec2<f32>(uniforms.modelPos.x, uniforms.modelPos.z));
|
|
1013
|
-
let edgeFadeStart = 100.0;
|
|
1014
|
-
let edgeFadeEnd = 180.0;
|
|
1015
|
-
// Smoother, more gradual edge fade
|
|
1016
|
-
let edgeFade = 1.0 - smoothstep(edgeFadeStart, edgeFadeEnd, edgeDistance);
|
|
1017
|
-
|
|
1018
|
-
// Atmospheric perspective - blend more with sky at distance and edges for better integration
|
|
1019
|
-
// More gradual blending for smoother transition
|
|
1020
|
-
let atmosphericBlend = fogFactor * 0.7 + (1.0 - edgeFade) * 0.8;
|
|
1021
|
-
col = mix(col, skyColorAbove, atmosphericBlend);
|
|
1022
|
-
|
|
1023
|
-
// Alpha based on distance and edge fade (more transparent to see model through water)
|
|
1024
|
-
var alpha = 0.8;
|
|
1025
|
-
alpha *= edgeFade;
|
|
1026
|
-
// Fade out at very far distances (updated for larger plane)
|
|
1027
|
-
alpha *= (1.0 - smoothstep(180.0, 300.0, dist) * 0.5);
|
|
1028
|
-
|
|
1029
|
-
// Apply exposure
|
|
1030
|
-
col = pow(col, vec3<f32>(EXPOSURE));
|
|
1031
|
-
col = clamp(col, vec3<f32>(0.0), vec3<f32>(1.0));
|
|
1032
|
-
|
|
1033
|
-
return vec4<f32>(col, alpha);
|
|
1034
|
-
}
|
|
1035
|
-
`;
|
|
1036
|
-
const module = this.device.createShaderModule({ code: shaderCode });
|
|
1037
|
-
this.waterPlanePipeline = this.device.createRenderPipeline({
|
|
1038
|
-
layout: "auto",
|
|
1039
|
-
vertex: {
|
|
1040
|
-
module,
|
|
1041
|
-
entryPoint: "vs_main",
|
|
1042
|
-
buffers: [
|
|
1043
|
-
{
|
|
1044
|
-
arrayStride: 5 * 4, // 3 floats (position) + 2 floats (uv)
|
|
1045
|
-
attributes: [
|
|
1046
|
-
{ shaderLocation: 0, offset: 0, format: "float32x3" }, // position
|
|
1047
|
-
{ shaderLocation: 1, offset: 12, format: "float32x2" }, // uv
|
|
1048
|
-
],
|
|
1049
|
-
},
|
|
1050
|
-
],
|
|
1051
|
-
},
|
|
1052
|
-
fragment: {
|
|
1053
|
-
module,
|
|
1054
|
-
entryPoint: "fs_main",
|
|
1055
|
-
targets: [
|
|
1056
|
-
{
|
|
1057
|
-
format: this.format,
|
|
1058
|
-
blend: {
|
|
1059
|
-
color: {
|
|
1060
|
-
srcFactor: "src-alpha",
|
|
1061
|
-
dstFactor: "one-minus-src-alpha",
|
|
1062
|
-
},
|
|
1063
|
-
alpha: {
|
|
1064
|
-
srcFactor: "one",
|
|
1065
|
-
dstFactor: "one-minus-src-alpha",
|
|
1066
|
-
},
|
|
1067
|
-
},
|
|
1068
|
-
},
|
|
1069
|
-
],
|
|
1070
|
-
},
|
|
1071
|
-
primitive: {
|
|
1072
|
-
topology: "triangle-list",
|
|
1073
|
-
cullMode: "none", // Water plane is visible from both sides
|
|
1074
|
-
},
|
|
1075
|
-
multisample: {
|
|
1076
|
-
count: this.sampleCount,
|
|
1077
|
-
},
|
|
1078
|
-
depthStencil: {
|
|
1079
|
-
depthWriteEnabled: false, // Don't write depth so model can show through water
|
|
1080
|
-
depthCompare: "less", // Test depth to avoid rendering behind background and model
|
|
1081
|
-
format: "depth24plus-stencil8",
|
|
1082
|
-
},
|
|
1083
|
-
});
|
|
1084
|
-
// Create bind group for water plane (same as background, uses same resources)
|
|
1085
|
-
this.waterPlaneBindGroup = this.device.createBindGroup({
|
|
1086
|
-
layout: this.waterPlanePipeline.getBindGroupLayout(0),
|
|
1087
|
-
entries: [
|
|
1088
|
-
{ binding: 0, resource: { buffer: this.cameraUniformBuffer } },
|
|
1089
|
-
{ binding: 1, resource: { buffer: this.uniformBuffer } },
|
|
1090
|
-
{ binding: 2, resource: this.waterHeightSampler },
|
|
1091
|
-
{ binding: 3, resource: this.waterHeightTextureView },
|
|
1092
|
-
],
|
|
1093
|
-
});
|
|
1094
|
-
}
|
|
1095
|
-
renderWaterPlane(pass, width, height, modelPos = [0, 0, 0]) {
|
|
1096
|
-
const now = performance.now();
|
|
1097
|
-
const time = (now - this.startTime) / 1000;
|
|
1098
|
-
// Write uniforms: time, padding, resX, resY, modelPos.x, modelPos.y, modelPos.z, padding
|
|
1099
|
-
const uniforms = new Float32Array([time, 0, width, height, modelPos[0], modelPos[1], modelPos[2], 0]);
|
|
1100
|
-
this.device.queue.writeBuffer(this.uniformBuffer, 0, uniforms);
|
|
1101
|
-
// Render water plane
|
|
1102
|
-
pass.setPipeline(this.waterPlanePipeline);
|
|
1103
|
-
pass.setBindGroup(0, this.waterPlaneBindGroup);
|
|
1104
|
-
pass.setVertexBuffer(0, this.waterPlaneVertexBuffer);
|
|
1105
|
-
pass.setIndexBuffer(this.waterPlaneIndexBuffer, "uint32");
|
|
1106
|
-
pass.drawIndexed(this.waterPlaneSubdivisions * this.waterPlaneSubdivisions * 6, 1, 0, 0);
|
|
1107
|
-
}
|
|
1108
|
-
render(pass, width, height, modelPos = [0, 0, 0]) {
|
|
1109
|
-
const now = performance.now();
|
|
1110
|
-
const time = (now - this.startTime) / 1000;
|
|
1111
|
-
// Write render uniforms: time, padding, resX, resY, modelPos.x, modelPos.y, modelPos.z, padding
|
|
1112
|
-
const uniforms = new Float32Array([time, 0, width, height, modelPos[0], modelPos[1], modelPos[2], 0]);
|
|
1113
|
-
this.device.queue.writeBuffer(this.uniformBuffer, 0, uniforms);
|
|
1114
|
-
// Render pool scene
|
|
1115
|
-
pass.setPipeline(this.pipeline);
|
|
1116
|
-
pass.setBindGroup(0, this.bindGroup);
|
|
1117
|
-
pass.draw(6, 1, 0, 0);
|
|
1118
|
-
}
|
|
1119
|
-
getSkyColor() {
|
|
1120
|
-
return this.config.skyColor;
|
|
1121
|
-
}
|
|
1122
|
-
}
|