@woosh/meep-engine 3.11.2 → 3.11.3
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/build/bundle-worker-terrain.js +1 -1
- package/package.json +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts +16 -2
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/PipelineLayoutDescriptor.js +28 -4
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.d.ts.map +1 -1
- package/src/shade/descriptor/pipeline/compute/ComputePipelineDescriptor.js +5 -2
- package/src/shade/device/IMMEDIATE_DATA_PLAN_2026_08_28.md +564 -451
- package/src/shade/device/ShadeGPUCommandContext.d.ts +18 -2
- package/src/shade/device/ShadeGPUCommandContext.d.ts.map +1 -1
- package/src/shade/device/ShadeGPUCommandContext.js +994 -940
- package/src/shade/device/immediate_data_supported.d.ts +33 -0
- package/src/shade/device/immediate_data_supported.d.ts.map +1 -0
- package/src/shade/device/immediate_data_supported.js +53 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts +18 -0
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUCommandEncoder.js +24 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUComputePassEncoder.js +49 -1
- package/src/shade/device/mock/SoftwareGPUDevice.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPUDevice.js +20 -2
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts +65 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.d.ts.map +1 -0
- package/src/shade/device/mock/SoftwareGPUImmediateData.js +160 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts +7 -0
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.d.ts.map +1 -1
- package/src/shade/device/mock/SoftwareGPURenderPassEncoder.js +50 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts +11 -0
- package/src/shade/device/mock/pipeline_immediate_size.d.ts.map +1 -0
- package/src/shade/device/mock/pipeline_immediate_size.js +18 -0
- package/src/shade/device/pipeline/PipelineLayoutManager.d.ts.map +1 -1
- package/src/shade/device/pipeline/PipelineLayoutManager.js +1 -0
- package/src/shade/renderer/GraphicsContext.d.ts +9 -2
- package/src/shade/renderer/GraphicsContext.d.ts.map +1 -1
- package/src/shade/renderer/GraphicsContext.js +13 -1
- package/src/shade/renderer/atmosphere/GPUSky.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/GPUSky.js +145 -142
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts +34 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/MULTISCATTER_LUT_CONFIG.js +36 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts +20 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/SAMPLER_SKY_LUT.js +25 -0
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts +14 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_get_multiscatter_values.js +163 -177
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts +20 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_multiscatter_sample_direction.js +46 -0
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts +11 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_multiscatter_lut.js +57 -27
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts +19 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sample_transmittance_lut.js +69 -33
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/chunk_sky_lut_compute_uv.js +35 -35
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts +13 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_params.js +38 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts +17 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_texel_to_unit.js +29 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts +21 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.d.ts.map +1 -0
- package/src/shade/renderer/atmosphere/chunk_sky_lut_uv.js +42 -0
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts +2 -2
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_multiscatter_lut.js +165 -84
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_sky_irradiance_lut.js +215 -204
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.d.ts.map +1 -1
- package/src/shade/renderer/atmosphere/shader_transmittance_lut.js +79 -67
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.d.ts.map +1 -1
- package/src/shade/renderer/light/cluster/graph_assign_clsuters.js +6 -2
- package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
- package/src/shade/renderer/postprocess/gtao/GTAO.js +4 -0
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/prepare_meshlet_draw_commands_by_material.js +341 -345
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts +0 -2
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/bucket/shader_read_out_bucket.js +58 -57
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/expand/mesh/graph_expand_meshes_to_meshlets.js +7 -3
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/fast/graph_extract_meshes_from_bucket.js +77 -81
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/standard/graph_rasterize_scene.js +5 -1
- package/src/shade/renderer/shader/ComputeShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ComputeShader.js +6 -2
- package/src/shade/renderer/shader/ImageShader.d.ts.map +1 -1
- package/src/shade/renderer/shader/ImageShader.js +4 -1
- package/src/shade/renderer/shader/ShaderDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/ShaderDescriptor.js +26 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts +18 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/ATMOSPHERE_RADII_DECLARATION.js +32 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts +7 -0
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/atmosphere/PLANET_CENTER_DECLARATION.js +17 -6
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts +21 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/atmosphere/chunk_atmosphere_ray_bounds.js +69 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts +9 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_contains_aabb3.js +22 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts +16 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/aabb3/chunk_aabb3_from_transformed_corners.js +41 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts +11 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_m4.js +15 -19
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts +23 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.d.ts.map +1 -0
- package/src/shade/renderer/shader/chunk/geometry/frustum/chunk_frustum_transform_plane_matrix.js +44 -0
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/octahedron/chunk_uv_octahedral_unit_decode.js +1 -4
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts +7 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.d.ts.map +1 -1
- package/src/shade/renderer/shader/compiler/CodeChunk.js +37 -2
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/compute/compute_binding_read_names.js +6 -0
- package/src/shade/renderer/shader/graph/compute/graph_compute_indirect_pass.js +1 -1
- package/src/shade/renderer/shader/graph/compute/graph_compute_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_image_pass.js +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_bindings.js +7 -0
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts +3 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.d.ts.map +1 -1
- package/src/shade/renderer/shader/graph/graph_prepare_pass_input_data.js +12 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/pass/RenderPassDescriptor.js +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts +21 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.d.ts.map +1 -0
- package/src/shade/renderer/shader/resource/IMMEDIATE_DATA.js +21 -0
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts +59 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.d.ts.map +1 -1
- package/src/shade/renderer/shader/resource/ShaderResourceSetDescriptor.js +195 -2
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts +14 -0
- package/src/shade/renderer/shader/type/type_has_arrays.d.ts.map +1 -0
- package/src/shade/renderer/shader/type/type_has_arrays.js +33 -0
- package/src/shade/renderer/view/GPUViewSkyContext.d.ts.map +1 -1
- package/src/shade/renderer/view/GPUViewSkyContext.js +169 -173
- package/src/shade/renderer/volumetrics/NOTES.md +238 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_local_light_shadow.js +145 -145
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/chunk_volumetrics_sun_optical_depth.js +38 -15
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +14 -2
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +594 -577
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts +16 -0
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +19 -2
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_participating_media.js +29 -18
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_scattering_lut.js +260 -260
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts +53 -3
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_sun_shadow_volume.js +175 -59
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +8 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLLanguageCore.js +1325 -1297
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts +0 -2
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.d.ts.map +0 -1
- package/src/shade/renderer/volumetrics/VOLUMETRICS_WORKGROUP_RESOLUTION.js +0 -1
|
@@ -1,174 +1,170 @@
|
|
|
1
|
-
import { m3_cm_compose_transform } from "../../../core/geom/mat3/m3_cm_compose_transform.js";
|
|
2
|
-
import { FrameGraph } from "../../../engine/graphics/render/frame_graph/FrameGraph.js";
|
|
3
|
-
import { Sampler2D } from "../../../engine/graphics/texture/sampler/Sampler2D.js";
|
|
4
|
-
import sampler2d_to_html_canvas
|
|
5
|
-
from "../../../engine/graphics/texture/sampler/sampler2d_to_html_canvas.js";
|
|
6
|
-
import { writeCssTransformMatrix } from "../../../view/writeCssTransformMatrix.js";
|
|
7
|
-
import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
|
|
8
|
-
import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
|
|
9
|
-
import { SPHERE_PROBE_MIPMAP_LEVELS } from "../light/environment/DEFINES.js";
|
|
10
|
-
import { graph_filter_environment_map } from "../light/environment/graph_filter_environment_map.js";
|
|
11
|
-
import { shader_sky_irradiance_lut } from "../atmosphere/shader_sky_irradiance_lut.js";
|
|
12
|
-
import { graph_image_pass } from "../shader/graph/graph_image_pass.js";
|
|
13
|
-
import { GPUTextureContext } from "../texture/GPUTextureContext.js";
|
|
14
|
-
import { sampler_linear_to_sRGB } from "../texture/sampler_linear_to_sRGB.js";
|
|
15
|
-
|
|
16
|
-
let debug_counter = 0;
|
|
17
|
-
|
|
18
|
-
export class GPUViewSkyContext {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* @type {GraphicsContext}
|
|
23
|
-
*/
|
|
24
|
-
#graphics;
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* @type {GPUTextureContext}
|
|
28
|
-
*/
|
|
29
|
-
#lut_sky;
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
*
|
|
34
|
-
* @returns {GPUTextureContext}
|
|
35
|
-
*/
|
|
36
|
-
get irradiance_lut() {
|
|
37
|
-
return this.#lut_sky;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
*
|
|
42
|
-
* @param {GraphicsContext} graphics
|
|
43
|
-
*/
|
|
44
|
-
constructor(graphics) {
|
|
45
|
-
this.#graphics = graphics;
|
|
46
|
-
|
|
47
|
-
const device = graphics.device;
|
|
48
|
-
|
|
49
|
-
this.#lut_sky = new GPUTextureContext(device);
|
|
50
|
-
this.#lut_sky.descriptor.label = "Sky / Irradiance LUT";
|
|
51
|
-
this.#lut_sky.descriptor.format = "rgba16float";
|
|
52
|
-
this.#lut_sky.descriptor.mipLevelCount = SPHERE_PROBE_MIPMAP_LEVELS;
|
|
53
|
-
this.#lut_sky.descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT
|
|
54
|
-
| GPUTextureUsage.TEXTURE_BINDING
|
|
55
|
-
| GPUTextureUsage.STORAGE_BINDING
|
|
56
|
-
| GPUTextureUsage.COPY_SRC
|
|
57
|
-
;
|
|
58
|
-
this.#lut_sky.resize(1024, 1024);
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
#needs_update = true;
|
|
63
|
-
|
|
64
|
-
set needs_update(v) {
|
|
65
|
-
if (v) {
|
|
66
|
-
this.#needs_update = true;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
*
|
|
72
|
-
* @param {GPUCameraContext} camera
|
|
73
|
-
* @param {GPUSceneContext} scene
|
|
74
|
-
*/
|
|
75
|
-
update(camera, scene) {
|
|
76
|
-
|
|
77
|
-
if (!this.#needs_update) {
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
this.#needs_update = false;
|
|
82
|
-
|
|
83
|
-
// ensure base sky LUTs are up-to-date
|
|
84
|
-
/**
|
|
85
|
-
*
|
|
86
|
-
* @type {GPUSky}
|
|
87
|
-
*/
|
|
88
|
-
const scene_sky = scene.sky;
|
|
89
|
-
scene_sky.update();
|
|
90
|
-
|
|
91
|
-
const graph = new FrameGraph("sky update");
|
|
92
|
-
|
|
93
|
-
const command_ctx = ShadeGPUCommandContext.create(
|
|
94
|
-
this.#graphics,
|
|
95
|
-
"Renderer/main"
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const gr_transmittance = graph.import_resource("transmittance", TextureResourceDescriptor.fromTexture(scene_sky.lut_transmittance.gpu_texture), scene_sky.lut_transmittance);
|
|
99
|
-
const gr_multiscatter = graph.import_resource("multiscatter", TextureResourceDescriptor.fromTexture(scene_sky.lut_multiple_scattering.gpu_texture), scene_sky.lut_multiple_scattering);
|
|
100
|
-
const gr_sky = graph.import_resource("sky_irradiance", TextureResourceDescriptor.fromTexture(this.#lut_sky.gpu_texture), this.#lut_sky);
|
|
101
|
-
|
|
102
|
-
let [out] = graph_image_pass({
|
|
103
|
-
graph,
|
|
104
|
-
shader: shader_sky_irradiance_lut,
|
|
105
|
-
inputs: {
|
|
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
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
canvas
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
this.
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
destroy() {
|
|
172
|
-
this.#lut_sky.destroy();
|
|
173
|
-
}
|
|
1
|
+
import { m3_cm_compose_transform } from "../../../core/geom/mat3/m3_cm_compose_transform.js";
|
|
2
|
+
import { FrameGraph } from "../../../engine/graphics/render/frame_graph/FrameGraph.js";
|
|
3
|
+
import { Sampler2D } from "../../../engine/graphics/texture/sampler/Sampler2D.js";
|
|
4
|
+
import sampler2d_to_html_canvas
|
|
5
|
+
from "../../../engine/graphics/texture/sampler/sampler2d_to_html_canvas.js";
|
|
6
|
+
import { writeCssTransformMatrix } from "../../../view/writeCssTransformMatrix.js";
|
|
7
|
+
import { TextureResourceDescriptor } from "../../device/graph/TextureResourceDescriptor.js";
|
|
8
|
+
import { ShadeGPUCommandContext } from "../../device/ShadeGPUCommandContext.js";
|
|
9
|
+
import { SPHERE_PROBE_MIPMAP_LEVELS } from "../light/environment/DEFINES.js";
|
|
10
|
+
import { graph_filter_environment_map } from "../light/environment/graph_filter_environment_map.js";
|
|
11
|
+
import { shader_sky_irradiance_lut } from "../atmosphere/shader_sky_irradiance_lut.js";
|
|
12
|
+
import { graph_image_pass } from "../shader/graph/graph_image_pass.js";
|
|
13
|
+
import { GPUTextureContext } from "../texture/GPUTextureContext.js";
|
|
14
|
+
import { sampler_linear_to_sRGB } from "../texture/sampler_linear_to_sRGB.js";
|
|
15
|
+
|
|
16
|
+
let debug_counter = 0;
|
|
17
|
+
|
|
18
|
+
export class GPUViewSkyContext {
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @type {GraphicsContext}
|
|
23
|
+
*/
|
|
24
|
+
#graphics;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @type {GPUTextureContext}
|
|
28
|
+
*/
|
|
29
|
+
#lut_sky;
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
* @returns {GPUTextureContext}
|
|
35
|
+
*/
|
|
36
|
+
get irradiance_lut() {
|
|
37
|
+
return this.#lut_sky;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
*
|
|
42
|
+
* @param {GraphicsContext} graphics
|
|
43
|
+
*/
|
|
44
|
+
constructor(graphics) {
|
|
45
|
+
this.#graphics = graphics;
|
|
46
|
+
|
|
47
|
+
const device = graphics.device;
|
|
48
|
+
|
|
49
|
+
this.#lut_sky = new GPUTextureContext(device);
|
|
50
|
+
this.#lut_sky.descriptor.label = "Sky / Irradiance LUT";
|
|
51
|
+
this.#lut_sky.descriptor.format = "rgba16float";
|
|
52
|
+
this.#lut_sky.descriptor.mipLevelCount = SPHERE_PROBE_MIPMAP_LEVELS;
|
|
53
|
+
this.#lut_sky.descriptor.usage = GPUTextureUsage.RENDER_ATTACHMENT
|
|
54
|
+
| GPUTextureUsage.TEXTURE_BINDING
|
|
55
|
+
| GPUTextureUsage.STORAGE_BINDING
|
|
56
|
+
| GPUTextureUsage.COPY_SRC
|
|
57
|
+
;
|
|
58
|
+
this.#lut_sky.resize(1024, 1024);
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
#needs_update = true;
|
|
63
|
+
|
|
64
|
+
set needs_update(v) {
|
|
65
|
+
if (v) {
|
|
66
|
+
this.#needs_update = true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
*
|
|
72
|
+
* @param {GPUCameraContext} camera
|
|
73
|
+
* @param {GPUSceneContext} scene
|
|
74
|
+
*/
|
|
75
|
+
update(camera, scene) {
|
|
76
|
+
|
|
77
|
+
if (!this.#needs_update) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
this.#needs_update = false;
|
|
82
|
+
|
|
83
|
+
// ensure base sky LUTs are up-to-date
|
|
84
|
+
/**
|
|
85
|
+
*
|
|
86
|
+
* @type {GPUSky}
|
|
87
|
+
*/
|
|
88
|
+
const scene_sky = scene.sky;
|
|
89
|
+
scene_sky.update();
|
|
90
|
+
|
|
91
|
+
const graph = new FrameGraph("sky update");
|
|
92
|
+
|
|
93
|
+
const command_ctx = ShadeGPUCommandContext.create(
|
|
94
|
+
this.#graphics,
|
|
95
|
+
"Renderer/main"
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const gr_transmittance = graph.import_resource("transmittance", TextureResourceDescriptor.fromTexture(scene_sky.lut_transmittance.gpu_texture), scene_sky.lut_transmittance);
|
|
99
|
+
const gr_multiscatter = graph.import_resource("multiscatter", TextureResourceDescriptor.fromTexture(scene_sky.lut_multiple_scattering.gpu_texture), scene_sky.lut_multiple_scattering);
|
|
100
|
+
const gr_sky = graph.import_resource("sky_irradiance", TextureResourceDescriptor.fromTexture(this.#lut_sky.gpu_texture), this.#lut_sky);
|
|
101
|
+
|
|
102
|
+
let [out] = graph_image_pass({
|
|
103
|
+
graph,
|
|
104
|
+
shader: shader_sky_irradiance_lut,
|
|
105
|
+
inputs: {
|
|
106
|
+
camera: camera.buffer,
|
|
107
|
+
|
|
108
|
+
light_data: scene.lights.buffer_data,
|
|
109
|
+
|
|
110
|
+
tSkyLutTransmittance: gr_transmittance,
|
|
111
|
+
tSkyLutMultiscatter: gr_multiscatter
|
|
112
|
+
},
|
|
113
|
+
output: [gr_sky]
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// convolve
|
|
117
|
+
out = graph_filter_environment_map({
|
|
118
|
+
graph,
|
|
119
|
+
map: out
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
command_ctx.encodeGraph(graph);
|
|
123
|
+
|
|
124
|
+
command_ctx.finish();
|
|
125
|
+
|
|
126
|
+
debug_counter++;
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
// this.debug(3);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async debug(mip = 0) {
|
|
133
|
+
if (this.__debug_waiting) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
this.__debug_waiting = true;
|
|
138
|
+
|
|
139
|
+
const texture = this.#lut_sky;
|
|
140
|
+
|
|
141
|
+
const data = await texture.download(mip);
|
|
142
|
+
|
|
143
|
+
const s = new Sampler2D(data, 4, texture.size[0] >> mip, texture.size[1] >> mip);
|
|
144
|
+
sampler_linear_to_sRGB(s);
|
|
145
|
+
|
|
146
|
+
const canvas = sampler2d_to_html_canvas(s, 255, 0, this.__debug_canvas);
|
|
147
|
+
|
|
148
|
+
if (this.__debug_canvas === undefined) {
|
|
149
|
+
|
|
150
|
+
canvas.style.zIndex = "100";
|
|
151
|
+
canvas.style.position = "absolute";
|
|
152
|
+
canvas.style.bottom = `${32}px`;
|
|
153
|
+
canvas.style.right = `${32}px`;
|
|
154
|
+
|
|
155
|
+
const transform = new Float32Array(9);
|
|
156
|
+
m3_cm_compose_transform(transform, 0, 0, 1, -1, 0, 0, 0);
|
|
157
|
+
writeCssTransformMatrix(transform, canvas);
|
|
158
|
+
|
|
159
|
+
document.body.appendChild(canvas);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
this.__debug_canvas = canvas;
|
|
163
|
+
|
|
164
|
+
this.__debug_waiting = false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
destroy() {
|
|
168
|
+
this.#lut_sky.destroy();
|
|
169
|
+
}
|
|
174
170
|
}
|
|
@@ -689,3 +689,241 @@ for the march.
|
|
|
689
689
|
that the metadata wants a real array, whose uniform-space stride is 16 bytes per `f32`.
|
|
690
690
|
- The build is latency-bound, not bandwidth-bound. If it ever has to be cheaper, the lever is the
|
|
691
691
|
serial chain — fewer slices per cascade, or a cooperative scan — and not fewer samples.
|
|
692
|
+
|
|
693
|
+
## The lighting workgroup, and a premise that did not survive contact — measured, 2026-08-30
|
|
694
|
+
|
|
695
|
+
S-01. The pass declared `@workgroup_size(16,16)`, dispatched one workgroup per froxel z slice. The
|
|
696
|
+
study's argument for reshaping it: a froxel is `TILE_SIZE` = 8 pixels, so a 16x16 group covers
|
|
697
|
+
128x128 pixels — sixteen separate 32-pixel light clusters — and every lane of a wave can be walking
|
|
698
|
+
a different light list, which is the worst case for the divergent light-record reads in the point
|
|
699
|
+
and spot loops and for shadow atlas tile locality. 4x4 in xy is exactly one cluster tile.
|
|
700
|
+
|
|
701
|
+
Mean over the build lighting span:
|
|
702
|
+
|
|
703
|
+
| shape | invocations | clusters spanned | mean |
|
|
704
|
+
|---|---|---|---|
|
|
705
|
+
| 16x16x1 | 256 | 16 in xy | **247 us** |
|
|
706
|
+
| 8x8x1 | 64 | 4 in xy | 368 us |
|
|
707
|
+
| 4x4x4 | 64 | 1 in xy, 4 in z | 386 us |
|
|
708
|
+
|
|
709
|
+
**The premise was false.** The shape spanning the most clusters is the fastest by 50%, so
|
|
710
|
+
cross-cluster divergence is not what governs this pass's time — cutting the group's cluster
|
|
711
|
+
footprint from sixteen tiles to one made it *slower*, and the xy alignment the whole finding rested
|
|
712
|
+
on bought nothing. Whatever dominates here, it is group size, not light-list coherence.
|
|
713
|
+
|
|
714
|
+
**Spanning z is separately wrong, and that part holds.** At equal occupancy, 4x4x4 is slower than
|
|
715
|
+
8x8x1. Lights are binned into 3D clusters and the largest distance between neighbouring clusters is
|
|
716
|
+
along depth, so a group that spans z spans the most unrelated light lists — the axis the study
|
|
717
|
+
picked to grow was the one axis to leave alone. The reshape is 2D for the same reason the pass was
|
|
718
|
+
2D to begin with.
|
|
719
|
+
|
|
720
|
+
Shipping **8x8x1**: 64 invocations, one wave64 or two warps, flat in z. That is 121 us of measured
|
|
721
|
+
cost against the original, paid deliberately, and the reason is **portability**.
|
|
722
|
+
|
|
723
|
+
Both numbers above were taken on an RTX 4090. Its register file is unusually large, and a 256-invocation
|
|
724
|
+
group winning there says more about that part than about the shape: the wider the group, the more
|
|
725
|
+
registers it needs live at once, and a 4090 has the headroom to keep 256 of them resident where a
|
|
726
|
+
smaller part would drop occupancy instead. AMD hardware in particular does better with smaller
|
|
727
|
+
groups. So 16x16x1's 247 us is a local result, not a general one, and shipping the shape that wins
|
|
728
|
+
on one machine would be tuning the engine to this desk.
|
|
729
|
+
|
|
730
|
+
Do not read the table as an instruction to put 16x16x1 back. That would need measurements from more
|
|
731
|
+
than one vendor, and it is a different question from the one measured here.
|
|
732
|
+
|
|
733
|
+
The shape is now `VOLUMETRICS_LIGHTING_WORKGROUP_RESOLUTION`, read by both the `@workgroup_size` and
|
|
734
|
+
the dispatch, following the sun shadow build. Dead `VOLUMETRICS_WORKGROUP_RESOLUTION` (`[8, 8, 4]`,
|
|
735
|
+
zero importers, matching one pass's literal by coincidence) is deleted.
|
|
736
|
+
|
|
737
|
+
### Regrouping cannot change the output, and did not
|
|
738
|
+
|
|
739
|
+
Every invocation computes from `global_invocation_id` alone — no shared memory, no subgroup op, no
|
|
740
|
+
cross-invocation state — so the question reduces to which ids get dispatched. Driven through the
|
|
741
|
+
emulator over both id sets, `tLighting` compared equal byte for byte at grids with a partial group
|
|
742
|
+
on one axis, on all three, and on none, with no voxel left unwritten; coverage at 1080p's 240x135x64
|
|
743
|
+
is exactly once each. That harness was throwaway — it compares against shapes that no longer exist —
|
|
744
|
+
and what stayed is a test pinning `@workgroup_size` to the constant the dispatch divides by.
|
|
745
|
+
|
|
746
|
+
Two things worth keeping from building its negative controls:
|
|
747
|
+
|
|
748
|
+
- Under-dispatching fails loudly: a stale divisor drops froxels and the volume comes back holed.
|
|
749
|
+
- **Over-dispatching is invisible.** A `group_count_z` left at the slice count under a 4-deep group
|
|
750
|
+
still visits every voxel exactly once — `gz * 4 + lz` stays a bijection — so the volume is
|
|
751
|
+
bit-identical while four times the groups launch to bounds-check and return. Only an assertion
|
|
752
|
+
about the dispatch being tight catches it.
|
|
753
|
+
|
|
754
|
+
## Hoisting the local-light shadow test above the march — measured and NOT shipped, 2026-08-30
|
|
755
|
+
|
|
756
|
+
F-01 and F-03, together, because separately neither is worth doing. Both local-light loops in
|
|
757
|
+
`shader_volumetrics_build_lighting` ran march -> multiscatter -> shadow:
|
|
758
|
+
|
|
759
|
+
```
|
|
760
|
+
let optical_depth = integrate_optical_depth(shadow_ray); // up to 16 filtered 3D fetches
|
|
761
|
+
let ms = calculate_multiscattering(...); // 6 octaves, pow + exp per octave
|
|
762
|
+
let shadow_term = volumetrics_point_light_shadow(...); // 1 textureLoad, returns 0 or 1
|
|
763
|
+
inScatteringSum += shadow_term * ms * direct_light.color;
|
|
764
|
+
```
|
|
765
|
+
|
|
766
|
+
The shadow term is binary by construction — `chunk_volumetrics_local_light_shadow` takes one nearest
|
|
767
|
+
texel with no filter kernel and ends in a `select` between 0.0 and 1.0 — so a zero throws away
|
|
768
|
+
everything above it. Testing it first and `continue`ing on zero skips that work (F-01). And
|
|
769
|
+
`volumetrics_point_light_shadow(database, index, position)` re-read the record
|
|
770
|
+
`get_point_light_info_by_index` had just unmarshalled, *before* its `CASTS_SHADOW_BIT` early-out
|
|
771
|
+
could fire, so the reorder alone would have added a storage read for every non-shadowing light;
|
|
772
|
+
passing the struct instead removes the duplicate and makes the flag test free (F-03).
|
|
773
|
+
|
|
774
|
+
**The reorder is exact, and that was verified rather than argued.** Both orderings were driven
|
|
775
|
+
through the WGSL emulator over thirteen fixtures — every exit of both loops, plus a case with real
|
|
776
|
+
per-froxel extinction where the old order computed a genuine optical depth and multiscatter and then
|
|
777
|
+
multiplied the pair by zero — and `tLighting` compared **byte for byte**, on the raw bits so that
|
|
778
|
+
`+0`/`-0` and any NaN payload would count as a difference. It is identical everywhere. NaN is the one
|
|
779
|
+
input for which the two are not equivalent (`0 * NaN` propagates, a skipped term does not), so the
|
|
780
|
+
spec fails on any non-finite value in its own right instead of folding it into the comparison.
|
|
781
|
+
|
|
782
|
+
**It is not faster.** Median of the `Volumetrics / build lighting` span over 18 interleaved rounds at
|
|
783
|
+
3840x2160, ABBA-ordered so neither arm systematically takes the warm half of a round, 48 recorded
|
|
784
|
+
frames per capture after a 40-frame ramp:
|
|
785
|
+
|
|
786
|
+
| arm | median (ms) |
|
|
787
|
+
|---|---|
|
|
788
|
+
| shadow test first (F-01/F-03) | 1.3573 |
|
|
789
|
+
| march first (shipping) | 1.3471 |
|
|
790
|
+
| **median paired difference** | **+0.0108** |
|
|
791
|
+
|
|
792
|
+
and the same protocol run with the *same build on both arms*, which is what that number has to beat:
|
|
793
|
+
|
|
794
|
+
| | median paired difference | median abs difference |
|
|
795
|
+
|---|---|---|
|
|
796
|
+
| A/B, 18 rounds | +0.0108 | 0.0998 |
|
|
797
|
+
| **A/A floor, 10 rounds** | **+0.0092** | 0.0456 |
|
|
798
|
+
|
|
799
|
+
The measured effect and the noise floor agree to 1.6 us. There is no signal, and the sign is the
|
|
800
|
+
wrong one anyway.
|
|
801
|
+
|
|
802
|
+
**Why there was nothing to win, which is the part worth keeping.** The local-light loops are not a
|
|
803
|
+
measurable part of this pass in this scene at all. Removing all 130 shadow-casting point lights from
|
|
804
|
+
the playground scene and re-measuring:
|
|
805
|
+
|
|
806
|
+
| | median (ms) |
|
|
807
|
+
|---|---|
|
|
808
|
+
| 130 shadow-casting point lights | 1.2585 |
|
|
809
|
+
| the same scene with all of them removed | 1.3343 |
|
|
810
|
+
|
|
811
|
+
Deleting the entire workload the finding proposed to optimize does not make the pass faster — it
|
|
812
|
+
reads a shade slower, which is to say the difference is noise. Whatever the 1.3 ms is spent on, it is
|
|
813
|
+
the per-froxel work outside those loops: the STBN fetch, the two participating-media samples, the
|
|
814
|
+
cluster lookup, and the directional loop's cascade fetch. That also retires the premise the finding
|
|
815
|
+
rested on, the same way S-01's did: 130 shadow-casting lights in the scene does not mean 130 lights
|
|
816
|
+
in the cluster any froxel actually reads.
|
|
817
|
+
|
|
818
|
+
So this joins Q-01's "the cost is nearly nothing" as a cost claim the profiler refused. Not shipped;
|
|
819
|
+
the tree keeps the march-first order.
|
|
820
|
+
|
|
821
|
+
What would have to change before it is worth revisiting: a scene where the froxel volume genuinely
|
|
822
|
+
overlaps many clustered lights, shown by that removal test moving the number. Reach for the removal
|
|
823
|
+
test *first* next time — it is three captures and it would have settled this before any code was
|
|
824
|
+
written.
|
|
825
|
+
|
|
826
|
+
Two things did survive, both about the shipping code rather than the change:
|
|
827
|
+
|
|
828
|
+
- `shader_volumetrics_build_lighting_local_shadow.spec.js` now covers the exits it did not: a light
|
|
829
|
+
over an atlas region that was never rendered (`stored == 0`, must not occlude), a froxel behind a
|
|
830
|
+
spot's apex (`clip_light.w <= 0`), that the optical-depth march still runs and still attenuates
|
|
831
|
+
when the shadow test passes, and that no NaN or infinity reaches `tLighting` in any fixture. The
|
|
832
|
+
apex case needs a deliberately inconsistent fixture and says so: in the shipping arrangement a
|
|
833
|
+
froxel behind the apex is also outside the cone, so the range cull `continue`s before the shadow
|
|
834
|
+
function is reached, and the branch has to be built rather than found.
|
|
835
|
+
|
|
836
|
+
- The dev server no longer hot-reloads (`server.hmr: false`). A reload drops the WebGPU device and
|
|
837
|
+
resets the temporal accumulation, and an edit anywhere in the module graph — including one made by
|
|
838
|
+
someone else working in the tree at the same time, which is exactly what happened here — destroys
|
|
839
|
+
an interleaved A/B mid-run.
|
|
840
|
+
|
|
841
|
+
Measurement conditions, because one of them is unusual: RTX 4090, canvas 1280x720 at pixel ratio 3.
|
|
842
|
+
The pass was confirmed resolution-bound first (0.4618 ms at 1280x720 rising to 1.878 ms at 3840x2160)
|
|
843
|
+
rather than presentation-throttled. Another session was building and running GPU work on the same
|
|
844
|
+
machine throughout, which put occasional 2x outliers in both arms; medians absorb them, and the A/A
|
|
845
|
+
floor was taken under the same conditions so it prices them in.
|
|
846
|
+
|
|
847
|
+
## The participating-media coverage skip — counted and NOT shipped, 2026-08-30
|
|
848
|
+
|
|
849
|
+
`shader_volumetrics_build_participating_media` carried a TODO: if a froxel is entirely inside a
|
|
850
|
+
volume, coverage is 1.0 and `frustum_unit_box_coverage` need not run. The TODO proposed getting there
|
|
851
|
+
by replacing `aabb3_intersects_frustum` with something that returns an intersection *degree*.
|
|
852
|
+
|
|
853
|
+
### The degree answers the other question
|
|
854
|
+
|
|
855
|
+
`aabb3_intersects_frustum(unit_bounds, local_frustum)` classifies the **volume box** against the
|
|
856
|
+
**froxel's planes**, and `aabb3_intersects_frustum_degree` already returns 0/1/2 for exactly that.
|
|
857
|
+
Its `2` means the box is inside the froxel — the arrangement where the froxel is *larger* than the
|
|
858
|
+
volume, where coverage is a small fraction. Reading it as "coverage is 1.0" would flood the far field
|
|
859
|
+
with fog, which `volumetrics_froxel_containment.spec.js` pins as a test.
|
|
860
|
+
|
|
861
|
+
What coverage of 1.0 needs is the dual: **froxel ⊆ box**. Both bodies are convex, so it holds exactly
|
|
862
|
+
when the froxel's 8 vertices are inside the box, and `frustum_to_corners` produces the same points
|
|
863
|
+
`frustum_sample_point` reaches at `uvw ∈ {0,1}³` — the sign flips in `plane_convex_intersection`
|
|
864
|
+
cancel, so a plane and its negation give the same corner. The test is exact, not conservative, and
|
|
865
|
+
the same bound gives a second exact skip: if the froxel clears `fade_distance` from every face in
|
|
866
|
+
world units, all three of `compute_volume_fade_factor`'s smoothsteps saturate and the fade is 1.0.
|
|
867
|
+
|
|
868
|
+
### It is exact and it still loses
|
|
869
|
+
|
|
870
|
+
Corners cost 8 `plane_convex_intersection` — 3 crosses, a dot and a divide each — which is what
|
|
871
|
+
`frustum_unit_box_coverage` spends on its 4 rays. Deriving them per volume pays for precisely the
|
|
872
|
+
work it skips, so the only arrangement that can win builds them once per froxel in world space and
|
|
873
|
+
maps them into each volume with a matrix multiply.
|
|
874
|
+
|
|
875
|
+
Hit rates over the playground's six volumes at 240x135x64 (every 4th column in X and Y, every slice
|
|
876
|
+
in Z, froxel extents kept at the true 1/240 and 1/135):
|
|
877
|
+
|
|
878
|
+
| pose | froxels touching a volume | survivors per froxel | contained | fade saturated |
|
|
879
|
+
|---|---|---|---|---|
|
|
880
|
+
| overview (default) | 100.0% | 1.223 | 7.0% | 3.3% |
|
|
881
|
+
| fog bank, wide | 91.2% | 0.912 | 8.1% | 4.1% |
|
|
882
|
+
| inside the fog | 97.6% | 1.565 | 33.2% | 27.2% |
|
|
883
|
+
|
|
884
|
+
Per froxel, in three-plane intersections:
|
|
885
|
+
|
|
886
|
+
| pose | paid | saved | |
|
|
887
|
+
|---|---|---|---|
|
|
888
|
+
| overview | 8.00 | 0.77 | **10.4x loss** |
|
|
889
|
+
| fog bank | 8.00 | 0.67 | **12.0x loss** |
|
|
890
|
+
| inside the fog | 8.00 | 5.00 | **1.6x loss** |
|
|
891
|
+
|
|
892
|
+
Two refinements do not rescue it. Building the corners **lazily**, on the first volume that survives
|
|
893
|
+
the plane test, saves nothing: the 400x20x400 cloud deck is a huge thin slab that defeats the
|
|
894
|
+
conservative frustum/AABB test almost everywhere, so 91-100% of froxels touch a volume anyway.
|
|
895
|
+
Gating on a **1-intersection necessary condition** — the froxel's own centre must be inside the box,
|
|
896
|
+
or containment is impossible — is very tight (centre-inside is 8.7% against contained's 7.0%) and
|
|
897
|
+
still loses: 2.7x, 2.5x and 1.3x on the three poses.
|
|
898
|
+
|
|
899
|
+
So the geometry is right and the arithmetic is against it, at every pose measured, including the one
|
|
900
|
+
where a third of froxels qualify. Not shipped. `volumetrics_froxel_containment.spec.js` keeps the
|
|
901
|
+
proof — containment implies coverage of exactly 1.0, and the fade core implies a fade of exactly
|
|
902
|
+
1.0 — alongside `chunk_aabb3_contains_aabb3` and `chunk_aabb3_from_transformed_corners`, so if the
|
|
903
|
+
grid, the volume mix or the estimator's cost moves, restoring the early-out is a few lines rather
|
|
904
|
+
than a rediscovery.
|
|
905
|
+
|
|
906
|
+
This one was settled by counting rather than by the profiler, which is unusual here. The hit rate is
|
|
907
|
+
a property of the scene's geometry and not of the device, and the cost ratio it implies is 2.5x to
|
|
908
|
+
12x on the two poses that matter — far outside anything a capture would overturn. A GPU A/B is the
|
|
909
|
+
confirmation, not the decision.
|
|
910
|
+
|
|
911
|
+
### What did ship
|
|
912
|
+
|
|
913
|
+
The line above the TODO was doing real redundant work. `frustum_transform_m4(froxel_frustum,
|
|
914
|
+
volume.transform_inverse)` derives its plane matrix as `transpose(mat4_inverse(transform))`, so
|
|
915
|
+
handing it `transform_inverse` made it compute `transpose(mat4_inverse(transform_inverse))` — which
|
|
916
|
+
is `transpose(volume.transform)`, a matrix already resident in the struct. Every froxel/volume pair
|
|
917
|
+
was running the 85-line generic cofactor `mat4_inverse` to recover a matrix it had been given: at
|
|
918
|
+
1080p with six volumes, 12.4M of them per frame.
|
|
919
|
+
|
|
920
|
+
`chunk_frustum_transform_plane_matrix` is that function with the plane matrix passed in, and
|
|
921
|
+
`frustum_transform_m4` is now the wrapper that derives one. The shader calls the former with
|
|
922
|
+
`transpose(volume.transform)`. Strictly less work, and marginally more accurate for dropping an
|
|
923
|
+
inverse round-trip. It is a full 4x4, not a 3x3 normal basis: the last row carries the translation,
|
|
924
|
+
which is what supplies each transformed plane's distance term, and
|
|
925
|
+
`chunk_frustum_transform_plane_matrix.spec.js` asserts both that and the equality with the old route.
|
|
926
|
+
|
|
927
|
+
`transpose` was missing from the WGSL emulator's language core, which is why `frustum_transform_m4`
|
|
928
|
+
had never been exercised there; it is implemented now for the square matrix sizes, on the same
|
|
929
|
+
footing as `determinant`.
|