topazcube 0.1.31 → 0.1.35

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 (103) hide show
  1. package/LICENSE.txt +0 -0
  2. package/README.md +0 -0
  3. package/dist/Renderer.cjs +20844 -0
  4. package/dist/Renderer.cjs.map +1 -0
  5. package/dist/Renderer.js +20827 -0
  6. package/dist/Renderer.js.map +1 -0
  7. package/dist/client.cjs +91 -260
  8. package/dist/client.cjs.map +1 -1
  9. package/dist/client.js +68 -215
  10. package/dist/client.js.map +1 -1
  11. package/dist/server.cjs +165 -432
  12. package/dist/server.cjs.map +1 -1
  13. package/dist/server.js +117 -370
  14. package/dist/server.js.map +1 -1
  15. package/dist/terminal.cjs +113 -200
  16. package/dist/terminal.cjs.map +1 -1
  17. package/dist/terminal.js +50 -51
  18. package/dist/terminal.js.map +1 -1
  19. package/dist/utils-CRhi1BDa.cjs +259 -0
  20. package/dist/utils-CRhi1BDa.cjs.map +1 -0
  21. package/dist/utils-D7tXt6-2.js +260 -0
  22. package/dist/utils-D7tXt6-2.js.map +1 -0
  23. package/package.json +19 -15
  24. package/src/{client.ts → network/client.js} +170 -403
  25. package/src/{compress-browser.ts → network/compress-browser.js} +2 -4
  26. package/src/{compress-node.ts → network/compress-node.js} +8 -14
  27. package/src/{server.ts → network/server.js} +229 -317
  28. package/src/{terminal.js → network/terminal.js} +0 -0
  29. package/src/{topazcube.ts → network/topazcube.js} +2 -2
  30. package/src/network/utils.js +375 -0
  31. package/src/renderer/Camera.js +191 -0
  32. package/src/renderer/DebugUI.js +703 -0
  33. package/src/renderer/Geometry.js +1049 -0
  34. package/src/renderer/Material.js +64 -0
  35. package/src/renderer/Mesh.js +211 -0
  36. package/src/renderer/Node.js +112 -0
  37. package/src/renderer/Pipeline.js +645 -0
  38. package/src/renderer/Renderer.js +1496 -0
  39. package/src/renderer/Skin.js +792 -0
  40. package/src/renderer/Texture.js +584 -0
  41. package/src/renderer/core/AssetManager.js +394 -0
  42. package/src/renderer/core/CullingSystem.js +308 -0
  43. package/src/renderer/core/EntityManager.js +541 -0
  44. package/src/renderer/core/InstanceManager.js +343 -0
  45. package/src/renderer/core/ParticleEmitter.js +358 -0
  46. package/src/renderer/core/ParticleSystem.js +564 -0
  47. package/src/renderer/core/SpriteSystem.js +349 -0
  48. package/src/renderer/gltf.js +563 -0
  49. package/src/renderer/math.js +161 -0
  50. package/src/renderer/rendering/HistoryBufferManager.js +333 -0
  51. package/src/renderer/rendering/ProbeCapture.js +1495 -0
  52. package/src/renderer/rendering/ReflectionProbeManager.js +352 -0
  53. package/src/renderer/rendering/RenderGraph.js +2258 -0
  54. package/src/renderer/rendering/passes/AOPass.js +308 -0
  55. package/src/renderer/rendering/passes/AmbientCapturePass.js +593 -0
  56. package/src/renderer/rendering/passes/BasePass.js +101 -0
  57. package/src/renderer/rendering/passes/BloomPass.js +420 -0
  58. package/src/renderer/rendering/passes/CRTPass.js +724 -0
  59. package/src/renderer/rendering/passes/FogPass.js +445 -0
  60. package/src/renderer/rendering/passes/GBufferPass.js +730 -0
  61. package/src/renderer/rendering/passes/HiZPass.js +744 -0
  62. package/src/renderer/rendering/passes/LightingPass.js +753 -0
  63. package/src/renderer/rendering/passes/ParticlePass.js +841 -0
  64. package/src/renderer/rendering/passes/PlanarReflectionPass.js +456 -0
  65. package/src/renderer/rendering/passes/PostProcessPass.js +405 -0
  66. package/src/renderer/rendering/passes/ReflectionPass.js +157 -0
  67. package/src/renderer/rendering/passes/RenderPostPass.js +364 -0
  68. package/src/renderer/rendering/passes/SSGIPass.js +266 -0
  69. package/src/renderer/rendering/passes/SSGITilePass.js +305 -0
  70. package/src/renderer/rendering/passes/ShadowPass.js +2072 -0
  71. package/src/renderer/rendering/passes/TransparentPass.js +831 -0
  72. package/src/renderer/rendering/passes/VolumetricFogPass.js +715 -0
  73. package/src/renderer/rendering/shaders/ao.wgsl +182 -0
  74. package/src/renderer/rendering/shaders/bloom.wgsl +97 -0
  75. package/src/renderer/rendering/shaders/bloom_blur.wgsl +80 -0
  76. package/src/renderer/rendering/shaders/crt.wgsl +455 -0
  77. package/src/renderer/rendering/shaders/depth_copy.wgsl +17 -0
  78. package/src/renderer/rendering/shaders/geometry.wgsl +580 -0
  79. package/src/renderer/rendering/shaders/hiz_reduce.wgsl +114 -0
  80. package/src/renderer/rendering/shaders/light_culling.wgsl +204 -0
  81. package/src/renderer/rendering/shaders/lighting.wgsl +932 -0
  82. package/src/renderer/rendering/shaders/lighting_common.wgsl +143 -0
  83. package/src/renderer/rendering/shaders/particle_render.wgsl +672 -0
  84. package/src/renderer/rendering/shaders/particle_simulate.wgsl +440 -0
  85. package/src/renderer/rendering/shaders/postproc.wgsl +293 -0
  86. package/src/renderer/rendering/shaders/render_post.wgsl +289 -0
  87. package/src/renderer/rendering/shaders/shadow.wgsl +117 -0
  88. package/src/renderer/rendering/shaders/ssgi.wgsl +266 -0
  89. package/src/renderer/rendering/shaders/ssgi_accumulate.wgsl +114 -0
  90. package/src/renderer/rendering/shaders/ssgi_propagate.wgsl +132 -0
  91. package/src/renderer/rendering/shaders/volumetric_blur.wgsl +80 -0
  92. package/src/renderer/rendering/shaders/volumetric_composite.wgsl +80 -0
  93. package/src/renderer/rendering/shaders/volumetric_raymarch.wgsl +634 -0
  94. package/src/renderer/utils/BoundingSphere.js +439 -0
  95. package/src/renderer/utils/Frustum.js +281 -0
  96. package/src/renderer/utils/Raycaster.js +761 -0
  97. package/dist/client.d.cts +0 -211
  98. package/dist/client.d.ts +0 -211
  99. package/dist/server.d.cts +0 -120
  100. package/dist/server.d.ts +0 -120
  101. package/dist/terminal.d.cts +0 -64
  102. package/dist/terminal.d.ts +0 -64
  103. package/src/utils.ts +0 -403
@@ -0,0 +1,445 @@
1
+ import { BasePass } from "./BasePass.js"
2
+
3
+ /**
4
+ * FogPass - Distance-based fog with height fade
5
+ *
6
+ * Features:
7
+ * - Distance fog based on camera distance (not Z-depth)
8
+ * - Two-gradient system: distance[0]->distance[1] and distance[1]->distance[2]
9
+ * - Height fade: maximum fog at bottomY, zero fog at topY
10
+ * - Emissive/bright colors show through fog more (HDR resistance)
11
+ * - Applied before bloom so bright objects still bloom through fog
12
+ *
13
+ * Input: HDR lighting output, GBuffer (for depth texture)
14
+ * Output: Fog-applied HDR texture
15
+ */
16
+ class FogPass extends BasePass {
17
+ constructor(engine = null) {
18
+ super('Fog', engine)
19
+
20
+ this.renderPipeline = null
21
+ this.outputTexture = null
22
+ this.inputTexture = null
23
+ this.gbuffer = null
24
+ this.uniformBuffer = null
25
+ this.bindGroup = null
26
+ this.sampler = null
27
+
28
+ // Render dimensions
29
+ this.width = 0
30
+ this.height = 0
31
+ }
32
+
33
+ // Fog settings getters
34
+ get fogEnabled() { return this.settings?.environment?.fog?.enabled ?? false }
35
+ get fogColor() { return this.settings?.environment?.fog?.color ?? [0.8, 0.85, 0.9] }
36
+ get fogDistances() { return this.settings?.environment?.fog?.distances ?? [0, 50, 200] }
37
+ get fogAlpha() { return this.settings?.environment?.fog?.alpha ?? [0.0, 0.3, 0.8] }
38
+ get fogHeightFade() { return this.settings?.environment?.fog?.heightFade ?? [-10, 100] }
39
+ get fogBrightResist() { return this.settings?.environment?.fog?.brightResist ?? 0.8 }
40
+ get fogDebug() { return this.settings?.environment?.fog?.debug ?? 0 } // 0=off, 1=show fogAlpha, 2=show distance, 3=show heightFactor
41
+
42
+ /**
43
+ * Set the input texture (HDR lighting output)
44
+ */
45
+ setInputTexture(texture) {
46
+ if (this.inputTexture !== texture) {
47
+ this.inputTexture = texture
48
+ this._needsRebuild = true
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Set the GBuffer for depth/normal reconstruction
54
+ */
55
+ setGBuffer(gbuffer) {
56
+ if (this.gbuffer !== gbuffer) {
57
+ this.gbuffer = gbuffer
58
+ this._needsRebuild = true
59
+ }
60
+ }
61
+
62
+ async _init() {
63
+ const { device } = this.engine
64
+
65
+ // Create sampler
66
+ this.sampler = device.createSampler({
67
+ label: 'Fog Sampler',
68
+ minFilter: 'linear',
69
+ magFilter: 'linear',
70
+ addressModeU: 'clamp-to-edge',
71
+ addressModeV: 'clamp-to-edge',
72
+ })
73
+
74
+ // Create uniform buffer
75
+ // inverseProj (64) + inverseView (64) + cameraPosition (12) + near (4) +
76
+ // far (4) + fogEnabled (4) + fogColor (12) + brightResist (4) +
77
+ // distances (12) + pad (4) + alphas (12) + pad (4) + heightFade (8) + screenSize (8) = 224 bytes
78
+ // Round to 256 for alignment
79
+ this.uniformBuffer = device.createBuffer({
80
+ label: 'Fog Uniforms',
81
+ size: 256,
82
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
83
+ })
84
+ }
85
+
86
+ async _buildPipeline() {
87
+ if (!this.inputTexture || !this.gbuffer) {
88
+ return
89
+ }
90
+
91
+ const { device } = this.engine
92
+
93
+ // Destroy old output texture before creating new one
94
+ if (this.outputTexture) {
95
+ this.outputTexture.destroy()
96
+ this.outputTexture = null
97
+ this.outputTextureView = null
98
+ }
99
+
100
+ // Create output texture (same format as input)
101
+ // Include COPY_SRC for planar reflection pass which copies fog output
102
+ this.outputTexture = device.createTexture({
103
+ label: 'Fog Output',
104
+ size: { width: this.width || 1, height: this.height || 1, depthOrArrayLayers: 1 },
105
+ format: 'rgba16float',
106
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_SRC,
107
+ })
108
+ this.outputTextureView = this.outputTexture.createView({ label: 'Fog Output View' })
109
+
110
+ // Create bind group layout
111
+ const bindGroupLayout = device.createBindGroupLayout({
112
+ label: 'Fog BGL',
113
+ entries: [
114
+ { binding: 0, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },
115
+ { binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
116
+ { binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
117
+ { binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },
118
+ ],
119
+ })
120
+
121
+ const shaderModule = device.createShaderModule({
122
+ label: 'Fog Shader',
123
+ code: `
124
+ // Uniforms packed for proper WGSL alignment (vec3f needs 16-byte alignment)
125
+ struct Uniforms {
126
+ inverseProjection: mat4x4f, // floats 0-15
127
+ inverseView: mat4x4f, // floats 16-31
128
+ cameraPosition: vec3f, // floats 32-34
129
+ near: f32, // float 35
130
+ fogColor: vec3f, // floats 36-38
131
+ far: f32, // float 39
132
+ distances: vec3f, // floats 40-42
133
+ fogEnabled: f32, // float 43
134
+ alphas: vec3f, // floats 44-46
135
+ brightResist: f32, // float 47
136
+ heightFade: vec2f, // floats 48-49
137
+ screenSize: vec2f, // floats 50-51
138
+ debug: f32, // float 52: 0=off, 1=fogAlpha, 2=distance, 3=heightFactor
139
+ _pad: vec3f, // floats 53-55
140
+ }
141
+
142
+ @group(0) @binding(0) var<uniform> uniforms: Uniforms;
143
+ @group(0) @binding(1) var inputTexture: texture_2d<f32>;
144
+ @group(0) @binding(2) var inputSampler: sampler;
145
+ @group(0) @binding(3) var depthTexture: texture_depth_2d;
146
+
147
+ struct VertexOutput {
148
+ @builtin(position) position: vec4f,
149
+ @location(0) uv: vec2f,
150
+ }
151
+
152
+ @vertex
153
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
154
+ var output: VertexOutput;
155
+ // Full-screen triangle
156
+ let x = f32((vertexIndex << 1u) & 2u);
157
+ let y = f32(vertexIndex & 2u);
158
+ output.position = vec4f(x * 2.0 - 1.0, y * 2.0 - 1.0, 0.0, 1.0);
159
+ output.uv = vec2f(x, 1.0 - y);
160
+ return output;
161
+ }
162
+
163
+ @fragment
164
+ fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
165
+ let color = textureSample(inputTexture, inputSampler, input.uv);
166
+
167
+ // If fog disabled, pass through
168
+ if (uniforms.fogEnabled < 0.5) {
169
+ return color;
170
+ }
171
+
172
+ // Sample depth from GBuffer depth texture (same as lighting shader)
173
+ let pixelCoords = vec2i(input.uv * uniforms.screenSize);
174
+ let depth = textureLoad(depthTexture, pixelCoords, 0);
175
+
176
+ // Check if this is sky (depth = 1 means far plane / cleared depth buffer)
177
+ let isSky = depth >= 0.9999;
178
+
179
+ // Get view ray direction (needed for both sky and geometry)
180
+ var iuv = input.uv;
181
+ iuv.y = 1.0 - iuv.y;
182
+ let ndc = vec4f(iuv * 2.0 - 1.0, 0.0, 1.0);
183
+ let viewRay = uniforms.inverseProjection * ndc;
184
+ let rayDir = normalize(viewRay.xyz / viewRay.w);
185
+
186
+ // For sky, treat as far distance; otherwise reconstruct linear depth
187
+ var cameraDistance: f32;
188
+ var worldPosY: f32;
189
+
190
+ if (isSky) {
191
+ // Sky is at far distance
192
+ cameraDistance = uniforms.far;
193
+
194
+ // Map view ray elevation to effective Y for height fade
195
+ // The fog clear zone in sky depends on topY:
196
+ // - Low topY (near camera): small clear zone, fog only at horizon
197
+ // - Medium topY (25m): clear zone extends to ~60° above horizon
198
+ // - High topY (100m+): almost no clear zone, fog fills sky
199
+ let worldRayDir = normalize((uniforms.inverseView * vec4f(rayDir, 0.0)).xyz);
200
+ let camY = uniforms.cameraPosition.y;
201
+ let topY = uniforms.heightFade.y;
202
+ let bottomY = uniforms.heightFade.x;
203
+
204
+ if (worldRayDir.y > 0.0) {
205
+ // Looking up: map elevation to effective Y
206
+ // Use a reference height to control how much sky can be clear
207
+ // At referenceHeight (50m), zenith reaches topY (fully clear)
208
+ // Above that, zenith stays fogged
209
+ let referenceHeight = 50.0;
210
+ let fogHeight = topY - camY;
211
+ let clearRange = min(fogHeight, referenceHeight);
212
+ worldPosY = camY + worldRayDir.y * clearRange;
213
+ } else {
214
+ // Looking down or at horizon: below camera, full fog
215
+ worldPosY = camY + worldRayDir.y * (camY - bottomY);
216
+ }
217
+ } else {
218
+ // Reconstruct linear depth: depth = (z - near) / (far - near), so z = near + depth * (far - near)
219
+ let linearDepth = uniforms.near + depth * (uniforms.far - uniforms.near);
220
+
221
+ // Use linear depth directly as camera distance
222
+ cameraDistance = linearDepth;
223
+
224
+ // Reconstruct world position for height fade
225
+ let viewPos = rayDir * (linearDepth / -rayDir.z);
226
+ let worldPos4 = uniforms.inverseView * vec4f(viewPos, 1.0);
227
+ worldPosY = worldPos4.y;
228
+ }
229
+
230
+ // Distance fog - two gradients
231
+ var distanceFog: f32;
232
+ let d0 = uniforms.distances.x;
233
+ let d1 = uniforms.distances.y;
234
+ let d2 = uniforms.distances.z;
235
+ let a0 = uniforms.alphas.x;
236
+ let a1 = uniforms.alphas.y;
237
+ let a2 = uniforms.alphas.z;
238
+
239
+ if (cameraDistance <= d0) {
240
+ distanceFog = a0;
241
+ } else if (cameraDistance <= d1) {
242
+ let t = (cameraDistance - d0) / max(d1 - d0, 0.001);
243
+ distanceFog = mix(a0, a1, t);
244
+ } else if (cameraDistance <= d2) {
245
+ let t = (cameraDistance - d1) / max(d2 - d1, 0.001);
246
+ distanceFog = mix(a1, a2, t);
247
+ } else {
248
+ distanceFog = a2;
249
+ }
250
+
251
+ // Height fade - full fog at bottomY, zero fog at topY
252
+ let bottomY = uniforms.heightFade.x;
253
+ let topY = uniforms.heightFade.y;
254
+ var heightFactor = clamp((worldPosY - bottomY) / max(topY - bottomY, 0.001), 0.0, 1.0);
255
+ // Below bottomY = maximum fog
256
+ if (worldPosY < bottomY) {
257
+ heightFactor = 0.0;
258
+ }
259
+ var fogAlpha = distanceFog * (1.0 - heightFactor);
260
+
261
+ // Emissive/bright resistance - HDR values show through fog
262
+ // Calculate luminance
263
+ let luminance = dot(color.rgb, vec3f(0.299, 0.587, 0.114));
264
+ // For HDR values > 1.0, reduce fog effect
265
+ let brightnessResist = clamp((luminance - 1.0) / 2.0, 0.0, 1.0);
266
+ fogAlpha *= (1.0 - brightnessResist * uniforms.brightResist);
267
+
268
+ // Debug output
269
+ let debugMode = i32(uniforms.debug);
270
+ if (debugMode == 1) {
271
+ // Show fog alpha as grayscale
272
+ return vec4f(vec3f(fogAlpha), 1.0);
273
+ } else if (debugMode == 2) {
274
+ // Show distance (normalized to 0-100m range)
275
+ let normDist = clamp(cameraDistance / 100.0, 0.0, 1.0);
276
+ return vec4f(vec3f(normDist), 1.0);
277
+ } else if (debugMode == 3) {
278
+ // Show height factor
279
+ return vec4f(vec3f(heightFactor), 1.0);
280
+ } else if (debugMode == 4) {
281
+ // Show distance fog (before height fade)
282
+ return vec4f(vec3f(distanceFog), 1.0);
283
+ } else if (debugMode == 5) {
284
+ // Show the actual fog color being used (to verify it matches particles)
285
+ return vec4f(uniforms.fogColor, 1.0);
286
+ }
287
+
288
+ // Apply fog
289
+ let foggedColor = mix(color.rgb, uniforms.fogColor, fogAlpha);
290
+
291
+ return vec4f(foggedColor, color.a);
292
+ }
293
+ `,
294
+ })
295
+
296
+ this.renderPipeline = await device.createRenderPipelineAsync({
297
+ label: 'Fog Pipeline',
298
+ layout: device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] }),
299
+ vertex: { module: shaderModule, entryPoint: 'vertexMain' },
300
+ fragment: {
301
+ module: shaderModule,
302
+ entryPoint: 'fragmentMain',
303
+ targets: [{ format: 'rgba16float' }],
304
+ },
305
+ primitive: { topology: 'triangle-list' },
306
+ })
307
+
308
+ this.bindGroup = device.createBindGroup({
309
+ label: 'Fog Bind Group',
310
+ layout: bindGroupLayout,
311
+ entries: [
312
+ { binding: 0, resource: { buffer: this.uniformBuffer } },
313
+ { binding: 1, resource: this.inputTexture.view },
314
+ { binding: 2, resource: this.sampler },
315
+ { binding: 3, resource: this.gbuffer.depth.view },
316
+ ],
317
+ })
318
+
319
+ this._needsRebuild = false
320
+ }
321
+
322
+ async _execute(context) {
323
+ const { device } = this.engine
324
+ const { camera } = context
325
+
326
+ // Skip if fog disabled
327
+ if (!this.fogEnabled) {
328
+ // Just copy input to output or return input directly
329
+ this.outputTexture = null
330
+ return
331
+ }
332
+
333
+ // Rebuild pipeline if needed
334
+ if (this._needsRebuild) {
335
+ await this._buildPipeline()
336
+ }
337
+
338
+ if (!this.renderPipeline || !this.inputTexture || !this.gbuffer || this._needsRebuild) {
339
+ return
340
+ }
341
+
342
+ // Update uniforms - 256 bytes = 64 floats (matches shader struct layout)
343
+ const uniformData = new Float32Array(64)
344
+
345
+ // inverseProjection (floats 0-15)
346
+ if (camera.iProj) {
347
+ uniformData.set(camera.iProj, 0)
348
+ }
349
+
350
+ // inverseView (floats 16-31)
351
+ if (camera.iView) {
352
+ uniformData.set(camera.iView, 16)
353
+ }
354
+
355
+ // cameraPosition (floats 32-34) + near (float 35)
356
+ uniformData[32] = camera.position[0]
357
+ uniformData[33] = camera.position[1]
358
+ uniformData[34] = camera.position[2]
359
+ uniformData[35] = camera.near || 0.1
360
+
361
+ // fogColor (floats 36-38) + far (float 39)
362
+ const fogColor = this.fogColor
363
+ uniformData[36] = fogColor[0]
364
+ uniformData[37] = fogColor[1]
365
+ uniformData[38] = fogColor[2]
366
+ uniformData[39] = camera.far || 1000
367
+
368
+ // distances (floats 40-42) + fogEnabled (float 43)
369
+ const distances = this.fogDistances
370
+ uniformData[40] = distances[0]
371
+ uniformData[41] = distances[1]
372
+ uniformData[42] = distances[2]
373
+ uniformData[43] = this.fogEnabled ? 1.0 : 0.0
374
+
375
+ // alphas (floats 44-46) + brightResist (float 47)
376
+ const alphas = this.fogAlpha
377
+ uniformData[44] = alphas[0]
378
+ uniformData[45] = alphas[1]
379
+ uniformData[46] = alphas[2]
380
+ uniformData[47] = this.fogBrightResist
381
+
382
+ // heightFade (floats 48-49) + screenSize (floats 50-51)
383
+ const heightFade = this.fogHeightFade
384
+ uniformData[48] = heightFade[0] // bottomY
385
+ uniformData[49] = heightFade[1] // topY
386
+ uniformData[50] = this.width
387
+ uniformData[51] = this.height
388
+
389
+ // debug (float 52) + padding (floats 53-55)
390
+ uniformData[52] = this.fogDebug
391
+
392
+ device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)
393
+
394
+ // Render fog pass
395
+ const commandEncoder = device.createCommandEncoder({ label: 'Fog Pass' })
396
+
397
+ const renderPass = commandEncoder.beginRenderPass({
398
+ label: 'Fog Render Pass',
399
+ colorAttachments: [{
400
+ view: this.outputTextureView,
401
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
402
+ loadOp: 'clear',
403
+ storeOp: 'store',
404
+ }],
405
+ })
406
+
407
+ renderPass.setPipeline(this.renderPipeline)
408
+ renderPass.setBindGroup(0, this.bindGroup)
409
+ renderPass.draw(3)
410
+ renderPass.end()
411
+
412
+ device.queue.submit([commandEncoder.finish()])
413
+ }
414
+
415
+ async _resize(width, height) {
416
+ this.width = width
417
+ this.height = height
418
+ this._needsRebuild = true
419
+ }
420
+
421
+ _destroy() {
422
+ if (this.outputTexture) {
423
+ this.outputTexture.destroy()
424
+ this.outputTexture = null
425
+ }
426
+ this.renderPipeline = null
427
+ }
428
+
429
+ /**
430
+ * Get the output texture (fog-applied HDR)
431
+ */
432
+ getOutputTexture() {
433
+ // If fog is disabled, return input texture
434
+ if (!this.fogEnabled || !this.outputTexture) {
435
+ return this.inputTexture
436
+ }
437
+ return {
438
+ texture: this.outputTexture,
439
+ view: this.outputTextureView,
440
+ sampler: this.sampler,
441
+ }
442
+ }
443
+ }
444
+
445
+ export { FogPass }