@woosh/meep-engine 3.37.0 → 3.38.0

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.
Files changed (106) hide show
  1. package/package.json +1 -1
  2. package/src/engine/ecs/storage/populateEngineSerializationRegistry.d.ts.map +1 -1
  3. package/src/engine/ecs/storage/populateEngineSerializationRegistry.js +2 -0
  4. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts +11 -0
  5. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.d.ts.map +1 -0
  6. package/src/engine/ecs/transform/TRANSFORM64_EVENT_PLACED.js +10 -0
  7. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.d.ts.map +1 -1
  8. package/src/engine/ecs/transform-attachment/TransformAttachmentSystem.js +16 -0
  9. package/src/engine/graphics/ecs/water/Water.d.ts +30 -0
  10. package/src/engine/graphics/ecs/water/Water.d.ts.map +1 -1
  11. package/src/engine/graphics/ecs/water/Water.js +125 -104
  12. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.d.ts.map +1 -1
  13. package/src/engine/graphics/ecs/water/WaterSerializationAdapter.js +13 -1
  14. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts +6 -0
  15. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.d.ts.map +1 -0
  16. package/src/engine/graphics/ecs/water/WaterSerializationUpgrader_1_2.js +18 -0
  17. package/src/engine/graphics3/WaterSystem.d.ts +22 -6
  18. package/src/engine/graphics3/WaterSystem.d.ts.map +1 -1
  19. package/src/engine/graphics3/WaterSystem.js +314 -293
  20. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts +13 -14
  21. package/src/engine/physics/cloth/collider/ClothColliderIndex.d.ts.map +1 -1
  22. package/src/engine/physics/cloth/collider/ClothColliderIndex.js +137 -36
  23. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts +30 -0
  24. package/src/engine/physics/cloth/collider/cloth_collider_bounds.d.ts.map +1 -1
  25. package/src/engine/physics/cloth/collider/cloth_collider_bounds.js +33 -0
  26. package/src/engine/physics/cloth/ecs/ClothColliderFlags.js +2 -1
  27. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts +10 -1
  28. package/src/engine/physics/cloth/ecs/ClothColliderSystem.d.ts.map +1 -1
  29. package/src/engine/physics/cloth/ecs/ClothColliderSystem.js +36 -14
  30. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts +15 -2
  31. package/src/engine/physics/cloth/ecs/ClothInstance.d.ts.map +1 -1
  32. package/src/engine/physics/cloth/ecs/ClothInstance.js +17 -2
  33. package/src/engine/physics/cloth/ecs/ClothWorld.d.ts.map +1 -1
  34. package/src/engine/physics/cloth/ecs/ClothWorld.js +19 -6
  35. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts +18 -20
  36. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.d.ts.map +1 -1
  37. package/src/engine/physics/cloth/ecs/cloth_gather_colliders.js +139 -89
  38. package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
  39. package/src/engine/physics/ecs/PhysicsSystem.js +32 -16
  40. package/src/shade/playground/water_refraction/README.md +149 -0
  41. package/src/shade/playground/water_refraction/index.html +42 -0
  42. package/src/shade/playground/water_refraction/main.d.ts +2 -0
  43. package/src/shade/playground/water_refraction/main.d.ts.map +1 -0
  44. package/src/shade/playground/water_refraction/main.js +201 -0
  45. package/src/shade/playground/water_refraction/world.d.ts +37 -0
  46. package/src/shade/playground/water_refraction/world.d.ts.map +1 -0
  47. package/src/shade/playground/water_refraction/world.js +137 -0
  48. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  49. package/src/shade/renderer/Renderer.js +4 -0
  50. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts +2 -3
  51. package/src/shade/renderer/animation/chunk_pose_accumulator.d.ts.map +1 -1
  52. package/src/shade/renderer/animation/skinning/bounds_refresh/shader_meshlet_bounds_reduce.d.ts.map +1 -1
  53. package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts +8 -0
  54. package/src/shade/renderer/light/environment/chunk_roughness_to_mip_ratio.d.ts.map +1 -0
  55. package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts +0 -6
  56. package/src/shade/renderer/light/environment/chunk_sphere_probe_roughness_to_lod.d.ts.map +1 -1
  57. package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts +1 -1
  58. package/src/shade/renderer/particles/bounds/chunk_particle_bounds.d.ts.map +1 -1
  59. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +17 -0
  60. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
  61. package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +7 -0
  62. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts +4 -0
  63. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.d.ts.map +1 -0
  64. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_renormalization.js +11 -0
  65. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts +4 -0
  66. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.d.ts.map +1 -0
  67. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_rgb_contribution.js +19 -0
  68. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.d.ts.map +1 -1
  69. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_splat_slice.js +5 -3
  70. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts +4 -0
  71. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.d.ts.map +1 -0
  72. package/src/shade/renderer/rasterize/native/avboit/chunk_avboit_split_counts.js +8 -0
  73. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +1 -1
  74. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
  75. package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +21 -2
  76. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.d.ts.map +1 -1
  77. package/src/shade/renderer/rasterize/native/avboit/shader_avboit_resolve.js +4 -8
  78. package/src/shade/renderer/scene/shader_bvh_extract_instance_leaves.d.ts.map +1 -1
  79. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts +12 -0
  80. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_add_f32_storage.d.ts.map +1 -0
  81. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts +12 -0
  82. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_max_f32_storage.d.ts.map +1 -0
  83. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts +12 -0
  84. package/src/shade/renderer/shader/chunk/atomic/chunk_atomic_min_f32_storage.d.ts.map +1 -0
  85. package/src/shade/renderer/shader/chunk/geometry/plane/{plane_ray_raycast.d.ts → chunk_plane_ray_raycast.d.ts} +2 -2
  86. package/src/shade/renderer/shader/chunk/geometry/plane/chunk_plane_ray_raycast.d.ts.map +1 -0
  87. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts +4 -0
  88. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.d.ts.map +1 -0
  89. package/src/shade/renderer/shader/chunk/screen_space/chunk_screen_space_newton_ray.js +101 -0
  90. package/src/shade/renderer/water/GPUWaterRenderer.d.ts +62 -29
  91. package/src/shade/renderer/water/GPUWaterRenderer.d.ts.map +1 -1
  92. package/src/shade/renderer/water/GPUWaterRenderer.js +235 -147
  93. package/src/shade/renderer/water/chunk_water_transport.d.ts +5 -0
  94. package/src/shade/renderer/water/chunk_water_transport.d.ts.map +1 -0
  95. package/src/shade/renderer/water/chunk_water_transport.js +94 -0
  96. package/src/shade/renderer/water/shader_water.d.ts +9 -26
  97. package/src/shade/renderer/water/shader_water.d.ts.map +1 -1
  98. package/src/shade/renderer/water/shader_water.js +384 -246
  99. package/src/shade/renderer/water/shader_water_avboit.d.ts +12 -0
  100. package/src/shade/renderer/water/shader_water_avboit.d.ts.map +1 -0
  101. package/src/shade/renderer/water/shader_water_avboit.js +258 -0
  102. package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
  103. package/src/shade/wgsl/emulator/WGSLLanguageCore.js +13 -0
  104. package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts +0 -29
  105. package/src/shade/renderer/animation/skinning/bounds_refresh/chunk_aabb_atomic_f32.d.ts.map +0 -1
  106. package/src/shade/renderer/shader/chunk/geometry/plane/plane_ray_raycast.d.ts.map +0 -1
@@ -0,0 +1,101 @@
1
+ import { CodeChunk } from "../../compiler/CodeChunk.js";
2
+ import { chunk_is_background } from "../depth/chunk_is_background.js";
3
+ import { chunk_decode_g_buffer_normal } from "../geometry/normal/chunk_decode_g_buffer_normal.js";
4
+ import { chunk_plane_from_normal_and_point } from "../geometry/plane/chunk_plane_from_normal_and_point.js";
5
+ import { chunk_plane_ray_raycast } from "../geometry/plane/chunk_plane_ray_raycast.js";
6
+ import { RAY_STRUCT } from "../geometry/ray/RAY_STRUCT.js";
7
+ import { chunk_ray_at } from "../geometry/ray/chunk_ray_at.js";
8
+ import { chunk_ray_nearest_point } from "../geometry/ray/chunk_ray_nearest_point.js";
9
+ import { chunk_inv_project_position } from "./chunk_inv_project_position.js";
10
+ import { chunk_project_position } from "./chunk_project_position.js";
11
+
12
+ /** Mayer et al. 2026 tangent-plane refinement. All arithmetic is in matrix view space. */
13
+ export const chunk_screen_space_newton_ray = CodeChunk.from(/* language=WGSL */ `
14
+ struct ScreenSpaceRayHit {
15
+ uv: vec2<f32>,
16
+ distance: f32,
17
+ residual: f32,
18
+ iterations: u32,
19
+ // 0 hit, 1 bounds/clip, 2 sky, 3 parallel/invalid, 4 interval, 5 exhausted/residual
20
+ status: u32,
21
+ }
22
+
23
+ fn screen_space_ray_in_bounds(uv: vec2<f32>) -> bool {
24
+ return all(uv >= vec2<f32>(0.0)) && all(uv < vec2<f32>(1.0));
25
+ }
26
+
27
+ fn screen_space_ray_projectable(p: vec3<f32>, projection: mat4x4<f32>) -> bool {
28
+ let clip = projection * vec4<f32>(p, 1.0);
29
+ return clip.w > 1e-6 && all(abs(clip) < vec4<f32>(1e30));
30
+ }
31
+
32
+ // Reconstruct the receiver at the subpixel lookup on its local tangent plane.
33
+ // A texel-center position is a different surface point: projecting its nearest
34
+ // point on the ray can exceed one pixel at grazing angles even for an exact hit.
35
+ fn screen_space_ray_validate(
36
+ ray: Ray, uv: vec2<f32>, depth: texture_2d<f32>, normals: texture_2d<u32>,
37
+ projection: mat4x4<f32>, inverse_projection: mat4x4<f32>, world_to_view: mat4x4<f32>,
38
+ distance_tolerance: f32,
39
+ ) -> ScreenSpaceRayHit {
40
+ var hit = ScreenSpaceRayHit(uv, 0.0, 1e20, 0u, 1u);
41
+ if (!screen_space_ray_in_bounds(uv)) { return hit; }
42
+ let size = textureDimensions(depth, 0);
43
+ let coord = vec2<i32>(uv * vec2<f32>(size));
44
+ let d = textureLoad(depth, coord, 0).r;
45
+ if (is_background(d)) { hit.status = 2u; return hit; }
46
+ let center = (vec2<f32>(coord) + 0.5) / vec2<f32>(size);
47
+ let center_position = inv_project_position(vec3<f32>(center, d), inverse_projection);
48
+ let normal = (world_to_view * vec4f(decode_g_buffer_normal(textureLoad(normals, coord, 0).zw), 0.0)).xyz;
49
+ let lookup_ray = inv_project_position(vec3f(uv, d), inverse_projection);
50
+ let denominator = dot(normal, lookup_ray);
51
+ if (!(abs(denominator) > 1e-6)) { hit.status = 3u; return hit; }
52
+ let scale = dot(normal, center_position) / denominator;
53
+ if (!(scale > 0.0)) { hit.status = 3u; return hit; }
54
+ let p = lookup_ray * scale;
55
+ let travel = dot(p - ray.origin, ray.direction);
56
+ hit.distance = travel;
57
+ if (!(travel > 1e-4 && travel <= ray.tmax)) { hit.status = 4u; return hit; }
58
+ let nearest = ray_nearest_point(ray, p);
59
+ if (!screen_space_ray_projectable(nearest, projection)) { return hit; }
60
+ let pixel_error = length((project_position(nearest, projection).xy - uv) * vec2<f32>(size));
61
+ hit.residual = distance(p, nearest);
62
+ hit.status = 5u;
63
+ if (pixel_error <= 1.0 && hit.residual <= distance_tolerance) { hit.status = 0u; }
64
+ return hit;
65
+ }
66
+
67
+ fn screen_space_newton_ray(
68
+ ray: Ray, seed_uv: vec2<f32>, depth: texture_2d<f32>, normals: texture_2d<u32>,
69
+ projection: mat4x4<f32>, inverse_projection: mat4x4<f32>, world_to_view: mat4x4<f32>,
70
+ max_updates: u32, distance_tolerance: f32,
71
+ ) -> ScreenSpaceRayHit {
72
+ var uv = seed_uv;
73
+ var hit = ScreenSpaceRayHit(uv, 0.0, 1e20, 0u, 5u);
74
+ let size = textureDimensions(depth, 0);
75
+ for (var i = 0u; i < min(max_updates, 16u); i++) {
76
+ hit.iterations = i + 1u;
77
+ if (!screen_space_ray_in_bounds(uv)) { hit.status = 1u; return hit; }
78
+ let coord = vec2<i32>(uv * vec2<f32>(size));
79
+ let d = textureLoad(depth, coord, 0).r;
80
+ if (is_background(d)) { hit.status = 2u; return hit; }
81
+ let center = (vec2<f32>(coord) + 0.5) / vec2<f32>(size);
82
+ let receiver = inv_project_position(vec3<f32>(center, d), inverse_projection);
83
+ // .zw is the geometric face normal; .xy includes material normal mapping.
84
+ let world_normal = decode_g_buffer_normal(textureLoad(normals, coord, 0).zw);
85
+ let normal = (world_to_view * vec4<f32>(world_normal, 0.0)).xyz;
86
+ if (!(abs(dot(ray.direction, normal)) > 1e-4)) { hit.status = 3u; return hit; }
87
+ let plane = plane_from_normal_and_point(normal, receiver);
88
+ let travel = plane_ray_raycast(ray.origin, ray.direction, plane);
89
+ if (!(travel > 1e-4 && travel <= ray.tmax)) { hit.status = 4u; return hit; }
90
+ let next = ray_at(ray.origin, ray.direction, travel);
91
+ if (!screen_space_ray_projectable(next, projection)) { hit.status = 1u; return hit; }
92
+ uv = project_position(next, projection).xy;
93
+ hit = screen_space_ray_validate(ray, uv, depth, normals, projection, inverse_projection, world_to_view, distance_tolerance);
94
+ hit.iterations = i + 1u;
95
+ if (hit.status == 0u || hit.status == 1u || hit.status == 2u) { return hit; }
96
+ }
97
+ return hit;
98
+ }
99
+ `, [RAY_STRUCT.declaration_chunk, chunk_is_background, chunk_decode_g_buffer_normal,
100
+ chunk_plane_from_normal_and_point, chunk_plane_ray_raycast, chunk_ray_at,
101
+ chunk_ray_nearest_point, chunk_inv_project_position, chunk_project_position]);
@@ -1,33 +1,66 @@
1
- /**
2
- * Draws water surfaces over a lit frame.
3
- *
4
- * **Water is a transparent pass on top of opaque**, which is why it lives here rather than in the
5
- * material system: it is not shaded, it does not write the G-buffer, and it needs the finished
6
- * depth of everything behind it in order to know how much water the view is looking through.
7
- *
8
- * The pass reads scene depth and writes the frame's colour, so it belongs at
9
- * {@link FramePhase.AfterLighting} — opaque surfaces lit and composited, transparency not yet run.
10
- */
1
+ /** Production water channel. Only AVBOIT's shared resolve composites its radiance. */
11
2
  export class GPUWaterRenderer {
12
- /**
13
- * Record the water draws into `graph`, over the colour it is given.
14
- *
15
- * @param {object} args
16
- * @param {FrameGraph} args.graph
17
- * @param {WaterSurface[]} args.surfaces
18
- * @param {number} args.color colour resource handle to draw over
19
- * @param {number} args.depth scene depth — **sampled**, which is why it is not also an
20
- * attachment, and why the shader does its own depth test
21
- * @param {GPUBuffer} args.camera
22
- * @returns {number} the colour handle after the draws
23
- */
24
- graph_draw({ graph, surfaces, color, depth, camera }: {
25
- graph: FrameGraph;
26
- surfaces: WaterSurface[];
27
- color: number;
28
- depth: number;
29
- camera: GPUBuffer;
30
- }): number;
3
+ surfaces: any[];
4
+ settings: {
5
+ enabled: boolean;
6
+ max_distance: number;
7
+ distance_tolerance: number;
8
+ max_updates: number;
9
+ fallback_steps: number;
10
+ mode: number;
11
+ reflections: number;
12
+ debug_view: number;
13
+ };
14
+ avboit_is_empty(): boolean;
15
+ /** Optional single-pixel readback on the next rendered water frame. No per-frame readback. */
16
+ request_probe(uv: any): Promise<any>;
17
+ dispose(): void;
18
+ avboit_prepare({ graph, view_ctx, camera, color_texture, depth_texture, normal_texture, hzb_texture, environment_texture }: {
19
+ graph: any;
20
+ view_ctx: any;
21
+ camera: any;
22
+ color_texture: any;
23
+ depth_texture: any;
24
+ normal_texture: any;
25
+ hzb_texture: any;
26
+ environment_texture: any;
27
+ }): {
28
+ background_color: any;
29
+ };
30
+ avboit_occupancy({ graph, camera, cluster_parameters, occupancy }: {
31
+ graph: any;
32
+ camera: any;
33
+ cluster_parameters: any;
34
+ occupancy: any;
35
+ }): any;
36
+ avboit_splat({ graph, camera, warp, voxel_resolution, volume }: {
37
+ graph: any;
38
+ camera: any;
39
+ warp: any;
40
+ voxel_resolution: any;
41
+ volume: any;
42
+ }): {
43
+ extinction: any;
44
+ overflow: any;
45
+ dummy: any;
46
+ };
47
+ avboit_draw({ graph, view_ctx, camera, warp, integral, volumetrics_lut, volumetrics_metadata, accumulators }: {
48
+ graph: any;
49
+ view_ctx: any;
50
+ camera: any;
51
+ warp: any;
52
+ integral: any;
53
+ volumetrics_lut: any;
54
+ volumetrics_metadata: any;
55
+ accumulators: any;
56
+ }): {
57
+ color: any;
58
+ norm: any;
59
+ extinction: any;
60
+ emission: any;
61
+ };
62
+ /** Debug presentation only: called after shared resolve, never sampled by production optics. */
63
+ inspect(graph: any, view_ctx: any, output_resolution: any): number;
31
64
  #private;
32
65
  }
33
66
  //# sourceMappingURL=GPUWaterRenderer.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"GPUWaterRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/water/GPUWaterRenderer.js"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH;IAwBI;;;;;;;;;;;OAWG;IACH;QAR4B,KAAK;QACD,QAAQ,EAA7B,cAAc;QACD,KAAK,EAAlB,MAAM;QACO,KAAK,EAAlB,MAAM;QAEU,MAAM;QACpB,MAAM,CAgBlB;;CA6FJ"}
1
+ {"version":3,"file":"GPUWaterRenderer.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/water/GPUWaterRenderer.js"],"names":[],"mappings":"AAaA,sFAAsF;AACtF;IACI,gBAAc;IACd;;;;;;;;;MACiF;IAOjF,2BAEC;IAED,8FAA8F;IAC9F,qCAgBC;IAED,gBAKC;IAED;;;;;;;;;;;MA4GC;IAQD;;;;;YAMC;IAED;;;;;;;;;;MAOC;IAED;;;;;;;;;;;;;;MAwBC;IAED,gGAAgG;IAChG,mEAYC;;CA4BJ"}
@@ -1,167 +1,255 @@
1
- import { assert } from "../../../core/assert.js";
2
- import { TextureViewDescriptor } from "../../descriptor/texture/TextureViewDescriptor.js";
1
+ import { half_to_float_uint16 } from "../../../core/binary/half_to_float_uint16.js";
2
+ import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
3
+ import { DEFAULT_DEPTH_TEXTURE_VIEW } from "../depth/DEFAULT_DEPTH_TEXTURE_VIEW.js";
4
+ import { graph_compute_pass } from "../shader/graph/compute/graph_compute_pass.js";
5
+ import { graph_image_pass } from "../shader/graph/graph_image_pass.js";
6
+ import { STATIC_GRAPHICS_ENGINE_ASSETS } from "../STATIC_GRAPHICS_ENGINE_ASSETS.js";
3
7
  import { SAMPLER_LINEAR_UPSCALE } from "../texture/SAMPLER_LINEAR_UPSCALE.js";
4
- import { create_water_pipeline, WATER_DRAW_STRUCT, WATER_RESOURCES } from "./shader_water.js";
8
+ import { create_water_pipeline, create_water_finalize_pipeline, WATER_DRAW_STRUCT, WATER_TRACE_STRUCT,
9
+ WATER_RESOURCES, WATER_FINALIZE_RESOURCES } from "./shader_water.js";
10
+ import { shader_water_background_copy, shader_water_debug, shader_water_draw,
11
+ shader_water_occupancy, shader_water_splat, shader_water_confidence_seed,
12
+ shader_water_confidence_x, shader_water_confidence_y } from "./shader_water_avboit.js";
5
13
 
6
- /**
7
- * Vertices per surface. Two triangles, generated from the vertex index — the surface is a rectangle
8
- * at a constant height, so there is nothing to upload.
9
- *
10
- * @type {number}
11
- */
12
- const VERTICES_PER_SURFACE = 6;
13
-
14
- /**
15
- * Draws water surfaces over a lit frame.
16
- *
17
- * **Water is a transparent pass on top of opaque**, which is why it lives here rather than in the
18
- * material system: it is not shaded, it does not write the G-buffer, and it needs the finished
19
- * depth of everything behind it in order to know how much water the view is looking through.
20
- *
21
- * The pass reads scene depth and writes the frame's colour, so it belongs at
22
- * {@link FramePhase.AfterLighting} — opaque surfaces lit and composited, transparency not yet run.
23
- */
14
+ /** Production water channel. Only AVBOIT's shared resolve composites its radiance. */
24
15
  export class GPUWaterRenderer {
25
- /**
26
- * @type {RenderPipelineDescriptor|null}
27
- */
28
- #pipeline = null;
29
-
30
- /**
31
- * @type {string}
32
- */
33
- #pipeline_color_format = "";
34
-
35
- /**
36
- * @param {string} color_format the frame colour this draws over
37
- * @returns {RenderPipelineDescriptor}
38
- */
39
- #obtain_pipeline(color_format) {
40
- if (this.#pipeline === null || this.#pipeline_color_format !== color_format) {
41
- this.#pipeline = create_water_pipeline({ color_format });
42
- this.#pipeline_color_format = color_format;
43
- }
44
-
45
- return this.#pipeline;
16
+ surfaces = [];
17
+ settings = { enabled: true, max_distance: 300, distance_tolerance: 0.15,
18
+ max_updates: 4, fallback_steps: 48, mode: 0, reflections: 1, debug_view: 0 };
19
+ #pipeline = create_water_pipeline();
20
+ #finalize_pipeline = create_water_finalize_pipeline();
21
+ #prepared = new WeakMap();
22
+ #inspection = new WeakMap();
23
+ #probe = null;
24
+
25
+ avboit_is_empty() {
26
+ return !this.settings.enabled || !this.surfaces.some(s => s.visible && s.height_texture !== null);
46
27
  }
47
28
 
48
- /**
49
- * Record the water draws into `graph`, over the colour it is given.
50
- *
51
- * @param {object} args
52
- * @param {FrameGraph} args.graph
53
- * @param {WaterSurface[]} args.surfaces
54
- * @param {number} args.color colour resource handle to draw over
55
- * @param {number} args.depth scene depth — **sampled**, which is why it is not also an
56
- * attachment, and why the shader does its own depth test
57
- * @param {GPUBuffer} args.camera
58
- * @returns {number} the colour handle after the draws
59
- */
60
- graph_draw({ graph, surfaces, color, depth, camera }) {
61
- assert.defined(graph, 'graph');
62
- assert.defined(surfaces, 'surfaces');
63
-
64
- const data = {};
65
-
66
- const pass = graph.add('water', data, (data, resources, context) => {
67
- this.#execute(data, resources, context, surfaces, camera);
29
+ /** Optional single-pixel readback on the next rendered water frame. No per-frame readback. */
30
+ request_probe(uv) {
31
+ if (this.avboit_is_empty()) { return Promise.reject(new Error("No active water to inspect")); }
32
+ if (this.#probe !== null) { return Promise.reject(new Error("Water probe already pending")); }
33
+ if (uv.length !== 2 || !Array.from(uv).every(Number.isFinite)) {
34
+ return Promise.reject(new Error("Water probe requires two finite UV coordinates"));
35
+ }
36
+ return new Promise((resolve, reject) => {
37
+ const probe = { uv: Array.from(uv),
38
+ resolve: value => { clearTimeout(timeout); resolve(value); },
39
+ reject: error => { clearTimeout(timeout); reject(error); } };
40
+ const timeout = setTimeout(() => {
41
+ if (this.#probe === probe) { this.#probe = null; }
42
+ reject(new Error("Water probe timed out; render an active AVBOIT water frame"));
43
+ }, 10000);
44
+ this.#probe = probe;
68
45
  });
69
-
70
- data.depth = pass.read(depth);
71
- data.color = pass.write(pass.read(color));
72
-
73
- return data.color;
74
46
  }
75
47
 
76
- /**
77
- * @param data
78
- * @param {FramePassResources} resources
79
- * @param {GraphicsExecutionContext} context
80
- * @param {WaterSurface[]} surfaces
81
- * @param {GPUBuffer} camera
82
- */
83
- #execute(data, resources, context, surfaces, camera) {
84
- if (surfaces.length === 0) {
85
- return;
86
- }
87
-
88
- const encoder = context.encoder;
89
- const graphics = context.graphics;
90
-
91
- const color_texture = resources.get(data.color);
92
- const depth_texture = resources.get(data.depth);
93
-
94
- const pipeline = this.#obtain_pipeline(color_texture.format);
95
-
96
- const depth_view = depth_texture.obtainView();
97
-
98
- // the height map is read at a coarser rate than its texels and clamped at the shore, which
99
- // is what the upscale sampler is
100
- const sampler = graphics.samplers.obtain(SAMPLER_LINEAR_UPSCALE);
101
-
102
- /*
103
- Every per-draw resource is built before the render pass opens: staging a uniform records a
104
- copy onto the command encoder, and the encoder is locked while a render pass is open on it.
105
- */
106
- const draws = [];
107
-
108
- for (let i = 0; i < surfaces.length; i++) {
109
- const surface = surfaces[i];
110
-
111
- if (!surface.visible) {
112
- continue;
113
- }
114
-
115
- const height = graphics.textures.obtain(surface.height_texture);
48
+ dispose() {
49
+ this.#probe?.reject(new Error("Water system stopped"));
50
+ this.#probe = null;
51
+ this.#prepared = new WeakMap();
52
+ this.#inspection = new WeakMap();
53
+ }
116
54
 
117
- const water = encoder.allocateTransientValueBuffer(WATER_DRAW_STRUCT, {
118
- extent: surface.extent,
119
- height_uv: surface.height_uv,
120
- color_deep: surface.color_deep,
121
- color_shore: surface.color_shore,
122
- level: surface.level,
123
- time: surface.time,
124
- wave_amplitude: surface.wave_amplitude,
125
- wave_speed: surface.wave_speed,
126
- wave_frequency: surface.wave_frequency,
127
- scattering: surface.scattering,
128
- shore_transition: surface.shore_transition
55
+ avboit_prepare({ graph, view_ctx, camera, color_texture, depth_texture, normal_texture,
56
+ hzb_texture, environment_texture }) {
57
+ const surfaces = this.surfaces.filter(s => s.visible && s.height_texture !== null)
58
+ .map(s => ({ ...s, extent: s.extent.slice(), height_uv: s.height_uv.slice(),
59
+ color_deep: s.color_deep.slice(), color_shore: s.color_shore.slice(),
60
+ shore_transition: s.shore_transition.slice(), extinction: s.extinction.slice() }))
61
+ .sort((a, b) => a.entity - b.entity);
62
+ surfaces.forEach((surface, index) => { surface.surface_id = index + 1; });
63
+ const settings = { ...this.settings };
64
+ const probe = this.#probe;
65
+ this.#probe = null;
66
+ const resolution = Array.from(view_ctx.resolution);
67
+ const data = { resolution, frame_time: surfaces[0]?.time ?? 0 };
68
+ const pipeline = this.#pipeline;
69
+ const pass = graph.add("Water / receivers", data, (data, resources, context) => {
70
+ const { encoder, graphics } = context;
71
+ const shared = {
72
+ camera: resources.get(data.camera),
73
+ trace: encoder.allocateTransientValueBuffer(WATER_TRACE_STRUCT, settings),
74
+ tDepth: resources.get(data.scene_depth).obtainView(DEFAULT_DEPTH_TEXTURE_VIEW),
75
+ tNormal: resources.get(data.normal).obtainView(),
76
+ tHZB: resources.get(data.hzb).obtainView(),
77
+ tScene: resources.get(data.color).obtainView(),
78
+ tEnvironment: resources.get(data.environment).obtainView(),
79
+ height_sampler: graphics.samplers.obtain(SAMPLER_LINEAR_UPSCALE),
80
+ };
81
+ // Copies/uniform uploads must be encoded before opening the render pass.
82
+ const draws = surfaces.map(s => WATER_RESOURCES.generateBindings({ ...shared,
83
+ water: encoder.allocateTransientValueBuffer(WATER_DRAW_STRUCT, s),
84
+ terrain_height: graphics.textures.obtain(s.height_texture).obtainView(),
85
+ }, encoder));
86
+ const render = encoder.constructRenderPass({ label: "Water / receivers", pipeline, bindings: [],
87
+ colorAttachments: [data.receiver, data.trace_metadata].map(handle => ({
88
+ view: resources.get(handle).obtainView(), loadOp: "clear", storeOp: "store", clearValue: [0,0,0,0],
89
+ })),
90
+ depthStencilAttachment: { view: resources.get(data.interface_depth).obtainView(DEFAULT_DEPTH_TEXTURE_VIEW),
91
+ depthLoadOp: "clear", depthStoreOp: "store", depthClearValue: 0 },
129
92
  });
130
-
131
- draws.push(WATER_RESOURCES.generateBindings({
132
- camera,
133
- water,
134
- scene_depth: depth_view,
135
- terrain_height: height.obtainView(),
136
- height_sampler: sampler
93
+ for (const bindings of draws) { graphics.setPipelineBindings(render, pipeline, bindings); render.draw(6); }
94
+ render.end();
95
+ });
96
+ for (const [name, handle] of Object.entries({ camera, color: color_texture, scene_depth: depth_texture,
97
+ normal: normal_texture, hzb: hzb_texture, environment: environment_texture })) { data[name] = pass.read(handle); }
98
+ const target = (name, format) => pass.create(`Water ${name}`, TextureResourceDescriptor.from({
99
+ resolution, format, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
100
+ | (probe !== null && format !== "depth32float" ? GPUTextureUsage.COPY_SRC : 0),
101
+ }));
102
+ // Keep the accepted UV and both paths at f32 precision through final shading.
103
+ data.receiver = target("receiver", "rgba32float");
104
+ data.trace_metadata = target("trace metadata", "rgba32float");
105
+ data.interface_depth = target("interface depth", "depth32float");
106
+ let states = this.#prepared.get(graph);
107
+ if (states === undefined) { states = new Map(); this.#prepared.set(graph, states); }
108
+ states.set(camera, data);
109
+ if (settings.debug_view !== 0) {
110
+ let views = this.#inspection.get(graph);
111
+ if (views === undefined) { views = new Map(); this.#inspection.set(graph, views); }
112
+ views.set(view_ctx, { data, mode: settings.debug_view, resolution });
113
+ }
114
+ const visibility_resolution = resolution.map(v => Math.ceil(v / 4));
115
+ const [seeds] = graph_image_pass({ graph, shader: shader_water_confidence_seed,
116
+ inputs: { trace_metadata: data.trace_metadata, scene_depth: data.scene_depth }, output_resolution: visibility_resolution });
117
+ const [horizontal] = graph_image_pass({ graph, shader: shader_water_confidence_x,
118
+ inputs: { distance_field: seeds }, output_resolution: visibility_resolution });
119
+ const [visibility_distance] = graph_image_pass({ graph, shader: shader_water_confidence_y,
120
+ inputs: { distance_field: horizontal }, output_resolution: visibility_resolution });
121
+ data.visibility_distance = visibility_distance;
122
+ const finalize_data = {};
123
+ const finalize_pipeline = this.#finalize_pipeline;
124
+ const finalize = graph.add('Water / coherent optics', finalize_data, (d, resources, {encoder, graphics}) => {
125
+ const shared = {
126
+ camera: resources.get(d.camera), trace: encoder.allocateTransientValueBuffer(WATER_TRACE_STRUCT, settings),
127
+ tDepth: resources.get(d.scene_depth).obtainView(DEFAULT_DEPTH_TEXTURE_VIEW),
128
+ tNormal: resources.get(d.normal).obtainView(), tHZB: resources.get(d.hzb).obtainView(),
129
+ tScene: resources.get(d.color).obtainView(), tEnvironment: resources.get(d.environment).obtainView(),
130
+ receiver: resources.get(d.receiver).obtainView(), trace_metadata: resources.get(d.trace_metadata).obtainView(),
131
+ visibility_distance: resources.get(d.visibility_distance).obtainView(),
132
+ height_sampler: graphics.samplers.obtain(SAMPLER_LINEAR_UPSCALE),
133
+ };
134
+ const draws = surfaces.map(s => WATER_FINALIZE_RESOURCES.generateBindings({...shared,
135
+ water: encoder.allocateTransientValueBuffer(WATER_DRAW_STRUCT, s),
136
+ terrain_height: graphics.textures.obtain(s.height_texture).obtainView(),
137
137
  }, encoder));
138
+ const copy_pipeline = shader_water_background_copy.pipeline;
139
+ const copy_bindings = shader_water_background_copy.descriptor.resources.generateBindings({tScene: shared.tScene}, encoder);
140
+ const render = encoder.constructRenderPass({label: 'Water / coherent optics', pipeline: finalize_pipeline, bindings: [],
141
+ colorAttachments: [d.transmission,d.radiance,d.background].map(handle => ({
142
+ view: resources.get(handle).obtainView(), loadOp: 'clear', storeOp: 'store', clearValue: [0,0,0,0],
143
+ })), depthStencilAttachment: {view: resources.get(d.interface_depth).obtainView(DEFAULT_DEPTH_TEXTURE_VIEW), depthReadOnly: true},
144
+ });
145
+ render.setPipeline(graphics.render_pipelines.obtain(copy_pipeline)); render.record_pipeline(copy_pipeline);
146
+ graphics.setPipelineBindings(render, copy_pipeline, copy_bindings); render.draw(3);
147
+ render.setPipeline(graphics.render_pipelines.obtain(finalize_pipeline)); render.record_pipeline(finalize_pipeline);
148
+ for (const bindings of draws) { graphics.setPipelineBindings(render, finalize_pipeline, bindings); render.draw(6); }
149
+ render.end();
150
+ });
151
+ for (const key of ['camera','scene_depth','normal','hzb','color','environment','receiver','trace_metadata','visibility_distance','interface_depth']) {
152
+ finalize_data[key] = finalize.read(data[key]);
138
153
  }
154
+ const optical_target = (name,format) => finalize.create(`Water ${name}`, TextureResourceDescriptor.from({
155
+ resolution, format, usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
156
+ | (probe !== null ? GPUTextureUsage.COPY_SRC : 0),
157
+ }));
158
+ data.transmission = finalize_data.transmission = optical_target('transmission','rgba32float');
159
+ data.radiance = finalize_data.radiance = optical_target('radiance','rgba16float');
160
+ const background_color = finalize_data.background = optical_target('background','rgba16float');
161
+ if (probe !== null) { this.#record_probe(graph, data, resolution, probe); }
162
+ return { background_color };
163
+ }
139
164
 
140
- if (draws.length === 0) {
141
- return;
142
- }
165
+ #state(graph, camera) {
166
+ const data = this.#prepared.get(graph)?.get(camera);
167
+ if (data === undefined) { throw new Error("Water AVBOIT requires preparation in this graph/view"); }
168
+ return data;
169
+ }
143
170
 
144
- const render_pass = encoder.constructRenderPass({
145
- label: "Water",
146
- pipeline,
147
- // one bind group per surface, so they are set per draw rather than once for the pass
148
- bindings: [],
149
- colorAttachments: [{
150
- loadOp: "load",
151
- storeOp: "store",
152
- view: color_texture.obtainView(TextureViewDescriptor.from({
153
- mipLevelCount: 1,
154
- baseMipLevel: 0
155
- }))
156
- }]
157
- });
171
+ avboit_occupancy({ graph, camera, cluster_parameters, occupancy }) {
172
+ const data = this.#state(graph, camera);
173
+ const { occupancy: out } = graph_compute_pass({ graph, shader: shader_water_occupancy,
174
+ bindings: { camera, settings: { cluster_parameters }, interface_depth: data.interface_depth, occupancy },
175
+ group_count: [Math.ceil(data.resolution[0] / 8), Math.ceil(data.resolution[1] / 8), 1] });
176
+ return out;
177
+ }
158
178
 
159
- for (let i = 0; i < draws.length; i++) {
160
- graphics.setPipelineBindings(render_pass, pipeline, draws[i]);
179
+ avboit_splat({ graph, camera, warp, voxel_resolution, volume }) {
180
+ const data = this.#state(graph, camera);
181
+ const out = graph_compute_pass({ graph, shader: shader_water_splat,
182
+ bindings: { camera, warp, interface_depth: data.interface_depth, transmission: data.transmission,
183
+ extinction_volume: volume.extinction, overflow_slice: volume.overflow },
184
+ group_count: [Math.ceil(voxel_resolution[0]/8), Math.ceil(voxel_resolution[1]/8), 1] });
185
+ return { extinction: out.extinction_volume, overflow: out.overflow_slice, dummy: volume.dummy };
186
+ }
161
187
 
162
- render_pass.draw(VERTICES_PER_SURFACE);
188
+ avboit_draw({ graph, view_ctx, camera, warp, integral, volumetrics_lut, volumetrics_metadata, accumulators }) {
189
+ const optical = this.#state(graph, camera);
190
+ // Inspection consumes the same completed shared integral as the production contribution.
191
+ optical.warp = warp; optical.integral = integral;
192
+ const data = {};
193
+ const pass = graph.add("Water / AVBOIT draw", data, (data, resources, { encoder, graphics }) => {
194
+ shader_water_draw.draw({ encoder, bindings: {
195
+ camera: resources.get(data.camera), view: view_ctx.uniform_buffer.buffer,
196
+ warp: resources.get(data.warp), interface_depth: resources.get(data.interface_depth).obtainView(DEFAULT_DEPTH_TEXTURE_VIEW),
197
+ transmission: resources.get(data.transmission).obtainView(), radiance: resources.get(data.radiance).obtainView(),
198
+ tIntegral: resources.get(data.integral).obtainView(), sLinear: graphics.samplers.obtain(SAMPLER_LINEAR_UPSCALE),
199
+ volumetrics_metadata: resources.get(data.volumetrics_metadata), tVolumetrics: resources.get(data.volumetrics_lut).obtainView(),
200
+ tSTBN_vec3: graphics.textures.obtain(STATIC_GRAPHICS_ENGINE_ASSETS.stbn_vec3).obtainView(),
201
+ }, colorAttachments: [data.color,data.norm,data.extinction,data.emission].map(handle => ({
202
+ view: resources.get(handle).obtainView(), loadOp: "load", storeOp: "store",
203
+ })) });
204
+ });
205
+ for (const [key,handle] of Object.entries({camera,warp,integral,volumetrics_lut,volumetrics_metadata,
206
+ interface_depth: optical.interface_depth, transmission: optical.transmission, radiance: optical.radiance})) {
207
+ data[key] = pass.read(handle);
163
208
  }
209
+ for (const [key,handle] of Object.entries(accumulators)) { data[key] = pass.write(pass.read(handle)); }
210
+ this.#prepared.get(graph).delete(camera);
211
+ return { color: data.color, norm: data.norm, extinction: data.extinction, emission: data.emission };
212
+ }
213
+
214
+ /** Debug presentation only: called after shared resolve, never sampled by production optics. */
215
+ inspect(graph, view_ctx, output_resolution) {
216
+ const views = this.#inspection.get(graph);
217
+ const state = views?.get(view_ctx);
218
+ if (state === undefined) { return undefined; }
219
+ views.delete(view_ctx);
220
+ const { data, mode } = state;
221
+ const resolution = output_resolution ?? state.resolution;
222
+ const [color] = graph_image_pass({ graph, shader: shader_water_debug, output_resolution: resolution,
223
+ inputs: { inspection: { mode, resolution }, camera: data.camera, warp: data.warp, tIntegral: data.integral,
224
+ transmission: data.transmission, radiance: data.radiance,
225
+ receiver: data.receiver, interface_depth: data.interface_depth, trace_metadata: data.trace_metadata } });
226
+ return color;
227
+ }
164
228
 
165
- render_pass.end();
229
+ #record_probe(graph, optical, resolution, probe) {
230
+ const data = {};
231
+ const pass = graph.add("Water / pixel probe", data, (data, resources, { encoder, graphics }) => {
232
+ const buffer = graphics.device.createBuffer({ size: 1024, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
233
+ const pixel = probe.uv.map((v, i) => Math.min(resolution[i]-1, Math.max(0, Math.floor(v*resolution[i]))));
234
+ [data.receiver, data.transmission, data.radiance, data.trace_metadata].forEach((handle, i) => {
235
+ encoder.copyTextureToBuffer({ texture: resources.get(handle).gpu_texture, origin: [...pixel, 0] },
236
+ { buffer, offset: i*256, bytesPerRow: 256 }, [1,1,1]);
237
+ });
238
+ encoder.done.then(async () => {
239
+ try {
240
+ await buffer.mapAsync(GPUMapMode.READ);
241
+ const mapped = buffer.getMappedRange();
242
+ const half = offset => Array.from(new Uint16Array(mapped, offset, 4), half_to_float_uint16);
243
+ const radiance = half(512);
244
+ const confidence = radiance[3];
245
+ radiance[3] = new Float32Array(mapped, 768, 4)[1];
246
+ probe.resolve({ pixel, resolution, frame_time: optical.frame_time, receiver: Array.from(new Float32Array(mapped, 0, 4)),
247
+ transmission: Array.from(new Float32Array(mapped, 256, 4)), radiance, confidence, stale: false });
248
+ } catch (error) { probe.reject(error); }
249
+ finally { buffer.destroy(); }
250
+ });
251
+ });
252
+ for (const key of ["receiver", "transmission", "radiance", "trace_metadata"]) { data[key] = pass.read(optical[key]); }
253
+ pass.make_side_effect();
166
254
  }
167
255
  }
@@ -0,0 +1,5 @@
1
+ export const chunk_water_refraction_confidence: CodeChunk;
2
+ export const chunk_water_transport: CodeChunk;
3
+ export const chunk_water_receiver_radiance: CodeChunk;
4
+ import { CodeChunk } from '../shader/compiler/CodeChunk.js';
5
+ //# sourceMappingURL=chunk_water_transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chunk_water_transport.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/water/chunk_water_transport.js"],"names":[],"mappings":"AAKA,0DAeG;AAEH,8CAoBG;AAIH,sDA+CoF;0BA7F1D,iCAAiC"}