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.
- package/LICENSE.txt +0 -0
- package/README.md +0 -0
- package/dist/Renderer.cjs +20844 -0
- package/dist/Renderer.cjs.map +1 -0
- package/dist/Renderer.js +20827 -0
- package/dist/Renderer.js.map +1 -0
- package/dist/client.cjs +91 -260
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +68 -215
- package/dist/client.js.map +1 -1
- package/dist/server.cjs +165 -432
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +117 -370
- package/dist/server.js.map +1 -1
- package/dist/terminal.cjs +113 -200
- package/dist/terminal.cjs.map +1 -1
- package/dist/terminal.js +50 -51
- package/dist/terminal.js.map +1 -1
- package/dist/utils-CRhi1BDa.cjs +259 -0
- package/dist/utils-CRhi1BDa.cjs.map +1 -0
- package/dist/utils-D7tXt6-2.js +260 -0
- package/dist/utils-D7tXt6-2.js.map +1 -0
- package/package.json +19 -15
- package/src/{client.ts → network/client.js} +170 -403
- package/src/{compress-browser.ts → network/compress-browser.js} +2 -4
- package/src/{compress-node.ts → network/compress-node.js} +8 -14
- package/src/{server.ts → network/server.js} +229 -317
- package/src/{terminal.js → network/terminal.js} +0 -0
- package/src/{topazcube.ts → network/topazcube.js} +2 -2
- package/src/network/utils.js +375 -0
- package/src/renderer/Camera.js +191 -0
- package/src/renderer/DebugUI.js +703 -0
- package/src/renderer/Geometry.js +1049 -0
- package/src/renderer/Material.js +64 -0
- package/src/renderer/Mesh.js +211 -0
- package/src/renderer/Node.js +112 -0
- package/src/renderer/Pipeline.js +645 -0
- package/src/renderer/Renderer.js +1496 -0
- package/src/renderer/Skin.js +792 -0
- package/src/renderer/Texture.js +584 -0
- package/src/renderer/core/AssetManager.js +394 -0
- package/src/renderer/core/CullingSystem.js +308 -0
- package/src/renderer/core/EntityManager.js +541 -0
- package/src/renderer/core/InstanceManager.js +343 -0
- package/src/renderer/core/ParticleEmitter.js +358 -0
- package/src/renderer/core/ParticleSystem.js +564 -0
- package/src/renderer/core/SpriteSystem.js +349 -0
- package/src/renderer/gltf.js +563 -0
- package/src/renderer/math.js +161 -0
- package/src/renderer/rendering/HistoryBufferManager.js +333 -0
- package/src/renderer/rendering/ProbeCapture.js +1495 -0
- package/src/renderer/rendering/ReflectionProbeManager.js +352 -0
- package/src/renderer/rendering/RenderGraph.js +2258 -0
- package/src/renderer/rendering/passes/AOPass.js +308 -0
- package/src/renderer/rendering/passes/AmbientCapturePass.js +593 -0
- package/src/renderer/rendering/passes/BasePass.js +101 -0
- package/src/renderer/rendering/passes/BloomPass.js +420 -0
- package/src/renderer/rendering/passes/CRTPass.js +724 -0
- package/src/renderer/rendering/passes/FogPass.js +445 -0
- package/src/renderer/rendering/passes/GBufferPass.js +730 -0
- package/src/renderer/rendering/passes/HiZPass.js +744 -0
- package/src/renderer/rendering/passes/LightingPass.js +753 -0
- package/src/renderer/rendering/passes/ParticlePass.js +841 -0
- package/src/renderer/rendering/passes/PlanarReflectionPass.js +456 -0
- package/src/renderer/rendering/passes/PostProcessPass.js +405 -0
- package/src/renderer/rendering/passes/ReflectionPass.js +157 -0
- package/src/renderer/rendering/passes/RenderPostPass.js +364 -0
- package/src/renderer/rendering/passes/SSGIPass.js +266 -0
- package/src/renderer/rendering/passes/SSGITilePass.js +305 -0
- package/src/renderer/rendering/passes/ShadowPass.js +2072 -0
- package/src/renderer/rendering/passes/TransparentPass.js +831 -0
- package/src/renderer/rendering/passes/VolumetricFogPass.js +715 -0
- package/src/renderer/rendering/shaders/ao.wgsl +182 -0
- package/src/renderer/rendering/shaders/bloom.wgsl +97 -0
- package/src/renderer/rendering/shaders/bloom_blur.wgsl +80 -0
- package/src/renderer/rendering/shaders/crt.wgsl +455 -0
- package/src/renderer/rendering/shaders/depth_copy.wgsl +17 -0
- package/src/renderer/rendering/shaders/geometry.wgsl +580 -0
- package/src/renderer/rendering/shaders/hiz_reduce.wgsl +114 -0
- package/src/renderer/rendering/shaders/light_culling.wgsl +204 -0
- package/src/renderer/rendering/shaders/lighting.wgsl +932 -0
- package/src/renderer/rendering/shaders/lighting_common.wgsl +143 -0
- package/src/renderer/rendering/shaders/particle_render.wgsl +672 -0
- package/src/renderer/rendering/shaders/particle_simulate.wgsl +440 -0
- package/src/renderer/rendering/shaders/postproc.wgsl +293 -0
- package/src/renderer/rendering/shaders/render_post.wgsl +289 -0
- package/src/renderer/rendering/shaders/shadow.wgsl +117 -0
- package/src/renderer/rendering/shaders/ssgi.wgsl +266 -0
- package/src/renderer/rendering/shaders/ssgi_accumulate.wgsl +114 -0
- package/src/renderer/rendering/shaders/ssgi_propagate.wgsl +132 -0
- package/src/renderer/rendering/shaders/volumetric_blur.wgsl +80 -0
- package/src/renderer/rendering/shaders/volumetric_composite.wgsl +80 -0
- package/src/renderer/rendering/shaders/volumetric_raymarch.wgsl +634 -0
- package/src/renderer/utils/BoundingSphere.js +439 -0
- package/src/renderer/utils/Frustum.js +281 -0
- package/src/renderer/utils/Raycaster.js +761 -0
- package/dist/client.d.cts +0 -211
- package/dist/client.d.ts +0 -211
- package/dist/server.d.cts +0 -120
- package/dist/server.d.ts +0 -120
- package/dist/terminal.d.cts +0 -64
- package/dist/terminal.d.ts +0 -64
- package/src/utils.ts +0 -403
|
@@ -0,0 +1,715 @@
|
|
|
1
|
+
import { BasePass } from "./BasePass.js"
|
|
2
|
+
import { Texture } from "../../Texture.js"
|
|
3
|
+
|
|
4
|
+
import volumetricRaymarchWGSL from "../shaders/volumetric_raymarch.wgsl"
|
|
5
|
+
import volumetricBlurWGSL from "../shaders/volumetric_blur.wgsl"
|
|
6
|
+
import volumetricCompositeWGSL from "../shaders/volumetric_composite.wgsl"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* VolumetricFogPass - Simple ray marching volumetric fog
|
|
10
|
+
*
|
|
11
|
+
* Pipeline:
|
|
12
|
+
* 1. Ray Marching (Fragment) - March rays, sample shadows directly at each step
|
|
13
|
+
* 2. Blur (Fragment) - Gaussian blur to soften edges
|
|
14
|
+
* 3. Composite (Fragment) - Blend fog into scene
|
|
15
|
+
*/
|
|
16
|
+
class VolumetricFogPass extends BasePass {
|
|
17
|
+
constructor(engine = null) {
|
|
18
|
+
super('VolumetricFog', engine)
|
|
19
|
+
|
|
20
|
+
// Pipelines
|
|
21
|
+
this.raymarchPipeline = null
|
|
22
|
+
this.blurHPipeline = null
|
|
23
|
+
this.blurVPipeline = null
|
|
24
|
+
this.compositePipeline = null
|
|
25
|
+
|
|
26
|
+
// Bind group layouts
|
|
27
|
+
this.raymarchBGL = null
|
|
28
|
+
this.blurBGL = null
|
|
29
|
+
this.compositeBGL = null
|
|
30
|
+
|
|
31
|
+
// 2D textures
|
|
32
|
+
this.raymarchTexture = null
|
|
33
|
+
this.blurTempTexture = null
|
|
34
|
+
this.blurredTexture = null
|
|
35
|
+
this.outputTexture = null
|
|
36
|
+
|
|
37
|
+
// Uniform buffers
|
|
38
|
+
this.raymarchUniformBuffer = null
|
|
39
|
+
this.blurHUniformBuffer = null
|
|
40
|
+
this.blurVUniformBuffer = null
|
|
41
|
+
this.compositeUniformBuffer = null
|
|
42
|
+
|
|
43
|
+
// Samplers
|
|
44
|
+
this.linearSampler = null
|
|
45
|
+
|
|
46
|
+
// External dependencies
|
|
47
|
+
this.inputTexture = null
|
|
48
|
+
this.gbuffer = null
|
|
49
|
+
this.shadowPass = null
|
|
50
|
+
this.lightingPass = null
|
|
51
|
+
|
|
52
|
+
// Dimensions
|
|
53
|
+
this.canvasWidth = 0
|
|
54
|
+
this.canvasHeight = 0
|
|
55
|
+
this.renderWidth = 0
|
|
56
|
+
this.renderHeight = 0
|
|
57
|
+
|
|
58
|
+
// Adaptive scatter state
|
|
59
|
+
this._currentMainLightScatter = null // Will be initialized on first execute
|
|
60
|
+
this._lastUpdateTime = 0
|
|
61
|
+
this._cameraInShadowSmooth = 0.0 // 0 = in light, 1 = in shadow
|
|
62
|
+
|
|
63
|
+
// Sky detection state (for adaptive scatter)
|
|
64
|
+
this._skyVisible = true // Assume sky visible until proven otherwise
|
|
65
|
+
this._skyCheckPending = false
|
|
66
|
+
this._lastSkyCheckTime = 0
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Settings getters
|
|
70
|
+
get volumetricSettings() { return this.settings?.volumetricFog ?? {} }
|
|
71
|
+
get fogSettings() { return this.settings?.fog ?? {} }
|
|
72
|
+
get isVolumetricEnabled() { return this.volumetricSettings.enabled ?? false }
|
|
73
|
+
get resolution() { return this.volumetricSettings.resolution ?? 0.25 }
|
|
74
|
+
get maxSamples() { return this.volumetricSettings.maxSamples ?? 32 }
|
|
75
|
+
get blurRadius() { return this.volumetricSettings.blurRadius ?? 4.0 }
|
|
76
|
+
get fogDensity() { return this.volumetricSettings.density ?? this.volumetricSettings.densityMultiplier ?? 0.5 }
|
|
77
|
+
get scatterStrength() { return this.volumetricSettings.scatterStrength ?? 1.0 }
|
|
78
|
+
get maxDistance() { return this.volumetricSettings.maxDistance ?? 20.0 }
|
|
79
|
+
get heightRange() { return this.volumetricSettings.heightRange ?? [-5, 20] }
|
|
80
|
+
get shadowsEnabled() { return this.volumetricSettings.shadowsEnabled ?? true }
|
|
81
|
+
get noiseStrength() { return this.volumetricSettings.noiseStrength ?? 1.0 } // 0 = uniform fog, 1 = full noise
|
|
82
|
+
get noiseAnimated() { return this.volumetricSettings.noiseAnimated ?? true }
|
|
83
|
+
get noiseScale() { return this.volumetricSettings.noiseScale ?? 0.25 } // Noise frequency (higher = finer detail)
|
|
84
|
+
get mainLightScatter() { return this.volumetricSettings.mainLightScatter ?? 1.0 } // Scatter when camera in light
|
|
85
|
+
get mainLightScatterDark() { return this.volumetricSettings.mainLightScatterDark ?? 3.0 } // Scatter when camera in shadow
|
|
86
|
+
get mainLightSaturation() { return this.volumetricSettings.mainLightSaturation ?? 1.0 } // Max brightness cap
|
|
87
|
+
// Brightness-based attenuation (fog less visible over bright surfaces)
|
|
88
|
+
get brightnessThreshold() { return this.volumetricSettings.brightnessThreshold ?? 1.0 } // Scene luminance where fog starts fading
|
|
89
|
+
get minVisibility() { return this.volumetricSettings.minVisibility ?? 0.15 } // Minimum fog visibility over very bright surfaces
|
|
90
|
+
get skyBrightness() { return this.volumetricSettings.skyBrightness ?? 5.0 } // Virtual brightness for sky (far depth)
|
|
91
|
+
// Debug mode: 0=normal, 1=depth, 2=ray dir, 3=noise, 4=viewDir.z, 5=worldPos, 6=accum, 7=light dist, 8=light pos
|
|
92
|
+
get debugMode() { return this.volumetricSettings.debug ?? 0 }
|
|
93
|
+
|
|
94
|
+
setInputTexture(texture) { this.inputTexture = texture }
|
|
95
|
+
setGBuffer(gbuffer) { this.gbuffer = gbuffer }
|
|
96
|
+
setShadowPass(shadowPass) { this.shadowPass = shadowPass }
|
|
97
|
+
setLightingPass(lightingPass) { this.lightingPass = lightingPass }
|
|
98
|
+
getOutputTexture() { return this.outputTexture }
|
|
99
|
+
|
|
100
|
+
// Unused setters (kept for API compatibility)
|
|
101
|
+
setHiZPass() {}
|
|
102
|
+
|
|
103
|
+
async _init() {
|
|
104
|
+
const { device } = this.engine
|
|
105
|
+
|
|
106
|
+
this.linearSampler = device.createSampler({
|
|
107
|
+
label: 'Volumetric Linear Sampler',
|
|
108
|
+
minFilter: 'linear',
|
|
109
|
+
magFilter: 'linear',
|
|
110
|
+
addressModeU: 'clamp-to-edge',
|
|
111
|
+
addressModeV: 'clamp-to-edge',
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
// Fallback shadow resources
|
|
115
|
+
this.fallbackShadowSampler = device.createSampler({
|
|
116
|
+
label: 'Volumetric Fallback Shadow Sampler',
|
|
117
|
+
compare: 'less',
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
this.fallbackCascadeShadowMap = device.createTexture({
|
|
121
|
+
label: 'Volumetric Fallback Cascade Shadow',
|
|
122
|
+
size: [1, 1, 3],
|
|
123
|
+
format: 'depth32float',
|
|
124
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
const identityMatrix = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1])
|
|
128
|
+
const matrixData = new Float32Array(16 * 3)
|
|
129
|
+
for (let i = 0; i < 3; i++) matrixData.set(identityMatrix, i * 16)
|
|
130
|
+
|
|
131
|
+
this.fallbackCascadeMatrices = device.createBuffer({
|
|
132
|
+
label: 'Volumetric Fallback Cascade Matrices',
|
|
133
|
+
size: 16 * 4 * 3,
|
|
134
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
135
|
+
})
|
|
136
|
+
device.queue.writeBuffer(this.fallbackCascadeMatrices, 0, matrixData)
|
|
137
|
+
|
|
138
|
+
// Fallback lights buffer (empty) - 96 bytes per light to match WGSL alignment
|
|
139
|
+
this.fallbackLightsBuffer = device.createBuffer({
|
|
140
|
+
label: 'Volumetric Fallback Lights',
|
|
141
|
+
size: 768 * 96, // MAX_LIGHTS * 96 bytes per light
|
|
142
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
// Fallback spot shadow atlas (1x1 depth texture)
|
|
146
|
+
this.fallbackSpotShadowAtlas = device.createTexture({
|
|
147
|
+
label: 'Volumetric Fallback Spot Shadow Atlas',
|
|
148
|
+
size: [1, 1, 1],
|
|
149
|
+
format: 'depth32float',
|
|
150
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.RENDER_ATTACHMENT,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
// Fallback spot matrices buffer
|
|
154
|
+
const spotMatrixData = new Float32Array(16 * 16) // 16 spot shadows max
|
|
155
|
+
for (let i = 0; i < 16; i++) spotMatrixData.set(identityMatrix, i * 16)
|
|
156
|
+
this.fallbackSpotMatrices = device.createBuffer({
|
|
157
|
+
label: 'Volumetric Fallback Spot Matrices',
|
|
158
|
+
size: 16 * 4 * 16,
|
|
159
|
+
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
|
|
160
|
+
})
|
|
161
|
+
device.queue.writeBuffer(this.fallbackSpotMatrices, 0, spotMatrixData)
|
|
162
|
+
|
|
163
|
+
await this._createResources(this.engine.canvas.width, this.engine.canvas.height)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async _createResources(width, height) {
|
|
167
|
+
const { device } = this.engine
|
|
168
|
+
|
|
169
|
+
this.canvasWidth = width
|
|
170
|
+
this.canvasHeight = height
|
|
171
|
+
this.renderWidth = Math.max(1, Math.floor(width * this.resolution))
|
|
172
|
+
this.renderHeight = Math.max(1, Math.floor(height * this.resolution))
|
|
173
|
+
|
|
174
|
+
this._destroyTextures()
|
|
175
|
+
|
|
176
|
+
// Create 2D textures at reduced resolution
|
|
177
|
+
this.raymarchTexture = this._create2DTexture('Raymarch Output', this.renderWidth, this.renderHeight)
|
|
178
|
+
this.blurTempTexture = this._create2DTexture('Blur Temp', this.renderWidth, this.renderHeight)
|
|
179
|
+
this.blurredTexture = this._create2DTexture('Blurred Fog', this.renderWidth, this.renderHeight)
|
|
180
|
+
this.outputTexture = await Texture.renderTarget(this.engine, 'rgba16float', width, height)
|
|
181
|
+
|
|
182
|
+
// Uniform buffers
|
|
183
|
+
this.raymarchUniformBuffer = this._createUniformBuffer('Raymarch Uniforms', 256)
|
|
184
|
+
this.blurHUniformBuffer = this._createUniformBuffer('Blur H Uniforms', 32)
|
|
185
|
+
this.blurVUniformBuffer = this._createUniformBuffer('Blur V Uniforms', 32)
|
|
186
|
+
this.compositeUniformBuffer = this._createUniformBuffer('Composite Uniforms', 48)
|
|
187
|
+
|
|
188
|
+
await this._createPipelines()
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
_create2DTexture(label, width, height) {
|
|
192
|
+
const texture = this.engine.device.createTexture({
|
|
193
|
+
label,
|
|
194
|
+
size: [width, height, 1],
|
|
195
|
+
format: 'rgba16float',
|
|
196
|
+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
|
|
197
|
+
})
|
|
198
|
+
return { texture, view: texture.createView(), width, height }
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
_createUniformBuffer(label, size) {
|
|
202
|
+
return this.engine.device.createBuffer({
|
|
203
|
+
label,
|
|
204
|
+
size,
|
|
205
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async _createPipelines() {
|
|
210
|
+
const { device } = this.engine
|
|
211
|
+
|
|
212
|
+
// === Raymarch Pipeline ===
|
|
213
|
+
const raymarchModule = device.createShaderModule({
|
|
214
|
+
label: 'Volumetric Raymarch Shader',
|
|
215
|
+
code: volumetricRaymarchWGSL,
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
this.raymarchBGL = device.createBindGroupLayout({
|
|
219
|
+
label: 'Volumetric Raymarch BGL',
|
|
220
|
+
entries: [
|
|
221
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
222
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },
|
|
223
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth', viewDimension: '2d-array' } },
|
|
224
|
+
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },
|
|
225
|
+
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // cascade matrices
|
|
226
|
+
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // lights
|
|
227
|
+
{ binding: 6, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } }, // spot shadow atlas
|
|
228
|
+
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } }, // spot matrices
|
|
229
|
+
],
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
this.raymarchPipeline = await device.createRenderPipelineAsync({
|
|
233
|
+
label: 'Volumetric Raymarch Pipeline',
|
|
234
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [this.raymarchBGL] }),
|
|
235
|
+
vertex: { module: raymarchModule, entryPoint: 'vertexMain' },
|
|
236
|
+
fragment: {
|
|
237
|
+
module: raymarchModule,
|
|
238
|
+
entryPoint: 'fragmentMain',
|
|
239
|
+
targets: [{ format: 'rgba16float' }],
|
|
240
|
+
},
|
|
241
|
+
primitive: { topology: 'triangle-list' },
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
// === Blur Pipeline ===
|
|
245
|
+
const blurModule = device.createShaderModule({
|
|
246
|
+
label: 'Volumetric Blur Shader',
|
|
247
|
+
code: volumetricBlurWGSL,
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
this.blurBGL = device.createBindGroupLayout({
|
|
251
|
+
label: 'Volumetric Blur BGL',
|
|
252
|
+
entries: [
|
|
253
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
254
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
255
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
256
|
+
],
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
const blurPipelineDesc = {
|
|
260
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [this.blurBGL] }),
|
|
261
|
+
vertex: { module: blurModule, entryPoint: 'vertexMain' },
|
|
262
|
+
fragment: {
|
|
263
|
+
module: blurModule,
|
|
264
|
+
entryPoint: 'fragmentMain',
|
|
265
|
+
targets: [{ format: 'rgba16float' }],
|
|
266
|
+
},
|
|
267
|
+
primitive: { topology: 'triangle-list' },
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
this.blurHPipeline = await device.createRenderPipelineAsync({ ...blurPipelineDesc, label: 'Volumetric Blur H' })
|
|
271
|
+
this.blurVPipeline = await device.createRenderPipelineAsync({ ...blurPipelineDesc, label: 'Volumetric Blur V' })
|
|
272
|
+
|
|
273
|
+
// === Composite Pipeline ===
|
|
274
|
+
const compositeModule = device.createShaderModule({
|
|
275
|
+
label: 'Volumetric Composite Shader',
|
|
276
|
+
code: volumetricCompositeWGSL,
|
|
277
|
+
})
|
|
278
|
+
|
|
279
|
+
this.compositeBGL = device.createBindGroupLayout({
|
|
280
|
+
label: 'Volumetric Composite BGL',
|
|
281
|
+
entries: [
|
|
282
|
+
{ binding: 0, visibility: GPUShaderStage.FRAGMENT | GPUShaderStage.VERTEX, buffer: { type: 'uniform' } },
|
|
283
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
284
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
285
|
+
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
286
|
+
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth' } },
|
|
287
|
+
],
|
|
288
|
+
})
|
|
289
|
+
|
|
290
|
+
this.compositePipeline = await device.createRenderPipelineAsync({
|
|
291
|
+
label: 'Volumetric Composite Pipeline',
|
|
292
|
+
layout: device.createPipelineLayout({ bindGroupLayouts: [this.compositeBGL] }),
|
|
293
|
+
vertex: { module: compositeModule, entryPoint: 'vertexMain' },
|
|
294
|
+
fragment: {
|
|
295
|
+
module: compositeModule,
|
|
296
|
+
entryPoint: 'fragmentMain',
|
|
297
|
+
targets: [{ format: 'rgba16float' }],
|
|
298
|
+
},
|
|
299
|
+
primitive: { topology: 'triangle-list' },
|
|
300
|
+
})
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async _execute(context) {
|
|
304
|
+
if (!this.isVolumetricEnabled) return
|
|
305
|
+
if (!this.inputTexture || !this.gbuffer) return
|
|
306
|
+
|
|
307
|
+
const { device } = this.engine
|
|
308
|
+
const { camera, mainLight, lights } = context
|
|
309
|
+
const time = performance.now() / 1000
|
|
310
|
+
|
|
311
|
+
// Update adaptive main light scatter based on camera shadow state
|
|
312
|
+
this._updateAdaptiveScatter(camera, mainLight, time)
|
|
313
|
+
|
|
314
|
+
// Get light count from context or lighting pass
|
|
315
|
+
const lightCount = lights?.length ?? this.lightingPass?.lightCount ?? 0
|
|
316
|
+
|
|
317
|
+
const commandEncoder = device.createCommandEncoder({ label: 'Volumetric Fog Pass' })
|
|
318
|
+
|
|
319
|
+
// === Stage 1: Ray Marching ===
|
|
320
|
+
this._updateRaymarchUniforms(camera, mainLight, time, lightCount)
|
|
321
|
+
const raymarchBindGroup = this._createRaymarchBindGroup()
|
|
322
|
+
if (raymarchBindGroup) {
|
|
323
|
+
const raymarchPass = commandEncoder.beginRenderPass({
|
|
324
|
+
label: 'Volumetric Raymarch',
|
|
325
|
+
colorAttachments: [{
|
|
326
|
+
view: this.raymarchTexture.view,
|
|
327
|
+
loadOp: 'clear',
|
|
328
|
+
storeOp: 'store',
|
|
329
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
330
|
+
}],
|
|
331
|
+
})
|
|
332
|
+
raymarchPass.setPipeline(this.raymarchPipeline)
|
|
333
|
+
raymarchPass.setBindGroup(0, raymarchBindGroup)
|
|
334
|
+
raymarchPass.draw(3)
|
|
335
|
+
raymarchPass.end()
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// === Stage 2: Blur ===
|
|
339
|
+
this._updateBlurUniforms(this.blurHUniformBuffer, 1, 0)
|
|
340
|
+
this._updateBlurUniforms(this.blurVUniformBuffer, 0, 1)
|
|
341
|
+
|
|
342
|
+
const blurHBindGroup = this._createBlurBindGroup(this.raymarchTexture, this.blurHUniformBuffer)
|
|
343
|
+
const blurHPass = commandEncoder.beginRenderPass({
|
|
344
|
+
label: 'Volumetric Blur H',
|
|
345
|
+
colorAttachments: [{
|
|
346
|
+
view: this.blurTempTexture.view,
|
|
347
|
+
loadOp: 'clear',
|
|
348
|
+
storeOp: 'store',
|
|
349
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
350
|
+
}],
|
|
351
|
+
})
|
|
352
|
+
blurHPass.setPipeline(this.blurHPipeline)
|
|
353
|
+
blurHPass.setBindGroup(0, blurHBindGroup)
|
|
354
|
+
blurHPass.draw(3)
|
|
355
|
+
blurHPass.end()
|
|
356
|
+
|
|
357
|
+
const blurVBindGroup = this._createBlurBindGroup(this.blurTempTexture, this.blurVUniformBuffer)
|
|
358
|
+
const blurVPass = commandEncoder.beginRenderPass({
|
|
359
|
+
label: 'Volumetric Blur V',
|
|
360
|
+
colorAttachments: [{
|
|
361
|
+
view: this.blurredTexture.view,
|
|
362
|
+
loadOp: 'clear',
|
|
363
|
+
storeOp: 'store',
|
|
364
|
+
clearValue: { r: 0, g: 0, b: 0, a: 0 },
|
|
365
|
+
}],
|
|
366
|
+
})
|
|
367
|
+
blurVPass.setPipeline(this.blurVPipeline)
|
|
368
|
+
blurVPass.setBindGroup(0, blurVBindGroup)
|
|
369
|
+
blurVPass.draw(3)
|
|
370
|
+
blurVPass.end()
|
|
371
|
+
|
|
372
|
+
// === Stage 3: Composite ===
|
|
373
|
+
this._updateCompositeUniforms()
|
|
374
|
+
const compositeBindGroup = this._createCompositeBindGroup()
|
|
375
|
+
if (compositeBindGroup) {
|
|
376
|
+
const compositePass = commandEncoder.beginRenderPass({
|
|
377
|
+
label: 'Volumetric Composite',
|
|
378
|
+
colorAttachments: [{
|
|
379
|
+
view: this.outputTexture.view,
|
|
380
|
+
loadOp: 'clear',
|
|
381
|
+
storeOp: 'store',
|
|
382
|
+
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
383
|
+
}],
|
|
384
|
+
})
|
|
385
|
+
compositePass.setPipeline(this.compositePipeline)
|
|
386
|
+
compositePass.setBindGroup(0, compositeBindGroup)
|
|
387
|
+
compositePass.draw(3)
|
|
388
|
+
compositePass.end()
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
device.queue.submit([commandEncoder.finish()])
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Update adaptive main light scatter based on camera shadow state and sky visibility
|
|
396
|
+
* Smoothly transitions between light/dark scatter values
|
|
397
|
+
* Only uses dark scatter if camera is in shadow AND there's something overhead (no sky)
|
|
398
|
+
*/
|
|
399
|
+
_updateAdaptiveScatter(camera, mainLight, currentTime) {
|
|
400
|
+
// Initialize on first call
|
|
401
|
+
if (this._currentMainLightScatter === null) {
|
|
402
|
+
this._currentMainLightScatter = this.mainLightScatter
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
const deltaTime = this._lastUpdateTime > 0 ? (currentTime - this._lastUpdateTime) : 0.016
|
|
406
|
+
this._lastUpdateTime = currentTime
|
|
407
|
+
|
|
408
|
+
// Check if camera is in shadow using cascade shadow matrices
|
|
409
|
+
let cameraInShadow = false
|
|
410
|
+
|
|
411
|
+
if (this.shadowPass && this.shadowsEnabled && mainLight?.enabled !== false) {
|
|
412
|
+
const cameraPos = camera.position || [0, 0, 0]
|
|
413
|
+
cameraInShadow = this._isCameraInShadow(cameraPos)
|
|
414
|
+
|
|
415
|
+
// Periodically check sky visibility using raycaster (every 0.5s)
|
|
416
|
+
if (!this._skyCheckPending && currentTime - this._lastSkyCheckTime > 0.5) {
|
|
417
|
+
this._checkSkyVisibility(cameraPos)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Only consider "in dark area" if in shadow AND sky is NOT visible
|
|
422
|
+
// If sky is visible (outdoors), don't boost scatter even in shadow
|
|
423
|
+
const inDarkArea = cameraInShadow && !this._skyVisible
|
|
424
|
+
|
|
425
|
+
// Smooth transition toward target state
|
|
426
|
+
// Going into dark area: 5 seconds (slow), exiting: 1 second (fast)
|
|
427
|
+
const targetShadowState = inDarkArea ? 1.0 : 0.0
|
|
428
|
+
const transitionSpeed = inDarkArea ? (1.0 / 5.0) : (1.0 / 1.0) // per second
|
|
429
|
+
|
|
430
|
+
// Exponential smoothing toward target
|
|
431
|
+
const t = 1.0 - Math.exp(-transitionSpeed * deltaTime * 3.0)
|
|
432
|
+
this._cameraInShadowSmooth += (targetShadowState - this._cameraInShadowSmooth) * t
|
|
433
|
+
|
|
434
|
+
// Clamp to valid range
|
|
435
|
+
this._cameraInShadowSmooth = Math.max(0, Math.min(1, this._cameraInShadowSmooth))
|
|
436
|
+
|
|
437
|
+
// Interpolate scatter value
|
|
438
|
+
const lightScatter = this.mainLightScatter
|
|
439
|
+
const darkScatter = this.mainLightScatterDark
|
|
440
|
+
this._currentMainLightScatter = lightScatter + (darkScatter - lightScatter) * this._cameraInShadowSmooth
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Check if sky is visible above the camera using raycaster
|
|
445
|
+
* This is async and updates _skyVisible when complete
|
|
446
|
+
*/
|
|
447
|
+
_checkSkyVisibility(cameraPos) {
|
|
448
|
+
const raycaster = this.engine?.raycaster
|
|
449
|
+
if (!raycaster) return
|
|
450
|
+
|
|
451
|
+
this._skyCheckPending = true
|
|
452
|
+
this._lastSkyCheckTime = this._lastUpdateTime
|
|
453
|
+
|
|
454
|
+
// Cast ray upward from camera position
|
|
455
|
+
const skyCheckDistance = this.volumetricSettings.skyCheckDistance ?? 100
|
|
456
|
+
const debugSkyCheck = this.volumetricSettings.debugSkyCheck ?? false
|
|
457
|
+
|
|
458
|
+
raycaster.cast(
|
|
459
|
+
cameraPos,
|
|
460
|
+
[0, 1, 0], // Straight up
|
|
461
|
+
skyCheckDistance,
|
|
462
|
+
(result) => {
|
|
463
|
+
const wasVisible = this._skyVisible
|
|
464
|
+
this._skyVisible = !result.hit
|
|
465
|
+
this._skyCheckPending = false
|
|
466
|
+
|
|
467
|
+
if (debugSkyCheck) {
|
|
468
|
+
const pos = cameraPos.map(v => v.toFixed(1)).join(', ')
|
|
469
|
+
if (result.hit) {
|
|
470
|
+
console.log(`Sky check from [${pos}]: HIT ${result.meshName || result.candidateId} at dist=${result.distance?.toFixed(1)}`)
|
|
471
|
+
} else {
|
|
472
|
+
console.log(`Sky check from [${pos}]: NO HIT (sky visible)`)
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
},
|
|
476
|
+
{ backfaces: true, debug: debugSkyCheck } // Need backfaces to hit ceilings from below
|
|
477
|
+
)
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Check if camera position is in shadow
|
|
482
|
+
* Uses the shadow pass's isCameraInShadow method if available,
|
|
483
|
+
* otherwise falls back to checking fog height bounds
|
|
484
|
+
*/
|
|
485
|
+
_isCameraInShadow(cameraPos) {
|
|
486
|
+
// Try shadow pass method first (if it has GPU shadow readback)
|
|
487
|
+
if (typeof this.shadowPass?.isCameraInShadow === 'function') {
|
|
488
|
+
return this.shadowPass.isCameraInShadow(cameraPos)
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Fallback: check if camera is below the fog height range
|
|
492
|
+
// This is a simple heuristic - if camera is at the bottom of fog volume,
|
|
493
|
+
// it's more likely to be in a shadowed area (cave, corridor, etc.)
|
|
494
|
+
const heightRange = this.heightRange
|
|
495
|
+
const fogBottom = heightRange[0]
|
|
496
|
+
const fogTop = heightRange[1]
|
|
497
|
+
const fogMiddle = (fogBottom + fogTop) / 2
|
|
498
|
+
|
|
499
|
+
// Consider "in shadow" if camera is in the lower third of fog volume
|
|
500
|
+
// This is a rough approximation - works for indoor/cave scenarios
|
|
501
|
+
const lowerThird = fogBottom + (fogTop - fogBottom) * 0.33
|
|
502
|
+
if (cameraPos[1] < lowerThird) {
|
|
503
|
+
return true
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Also check if main light direction is mostly blocked (sun very low)
|
|
507
|
+
// This helps with sunset/sunrise scenarios
|
|
508
|
+
const mainLightDir = this.engine?.settings?.mainLight?.direction
|
|
509
|
+
if (mainLightDir) {
|
|
510
|
+
// If sun direction Y component is positive (sun below horizon in our convention)
|
|
511
|
+
// or very low angle, consider shadowed
|
|
512
|
+
const sunAngle = mainLightDir[1] // Y component of direction
|
|
513
|
+
if (sunAngle > 0.7) { // Sun mostly pointing up = below horizon
|
|
514
|
+
return true
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return false
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
_updateRaymarchUniforms(camera, mainLight, time, lightCount) {
|
|
522
|
+
const { device } = this.engine
|
|
523
|
+
|
|
524
|
+
// Verify camera matrices exist
|
|
525
|
+
if (!camera.iProj || !camera.iView) {
|
|
526
|
+
console.warn('VolumetricFogPass: Camera missing iProj or iView matrices')
|
|
527
|
+
return
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
const invProj = camera.iProj
|
|
531
|
+
const invView = camera.iView
|
|
532
|
+
const cameraPos = camera.position || [0, 0, 0]
|
|
533
|
+
|
|
534
|
+
const mainLightEnabled = mainLight?.enabled !== false
|
|
535
|
+
const mainLightDir = mainLight?.direction ?? [-1.0, 1.0, -0.5]
|
|
536
|
+
const mainLightColor = mainLight?.color ?? [1, 0.95, 0.9]
|
|
537
|
+
const mainLightIntensity = mainLightEnabled ? (mainLight?.intensity ?? 1.0) : 0.0
|
|
538
|
+
|
|
539
|
+
const fogColor = this.fogSettings.color ?? [0.8, 0.85, 0.9]
|
|
540
|
+
const heightFade = this.heightRange
|
|
541
|
+
|
|
542
|
+
const shadowsEnabled = this.shadowsEnabled && this.shadowPass != null
|
|
543
|
+
|
|
544
|
+
const data = new Float32Array(64)
|
|
545
|
+
let offset = 0
|
|
546
|
+
|
|
547
|
+
// mat4 inverseProjection
|
|
548
|
+
data.set(invProj, offset); offset += 16
|
|
549
|
+
|
|
550
|
+
// mat4 inverseView
|
|
551
|
+
data.set(invView, offset); offset += 16
|
|
552
|
+
|
|
553
|
+
// vec3 cameraPosition + nearPlane
|
|
554
|
+
data[offset++] = cameraPos[0]
|
|
555
|
+
data[offset++] = cameraPos[1]
|
|
556
|
+
data[offset++] = cameraPos[2]
|
|
557
|
+
data[offset++] = camera.near ?? 0.1
|
|
558
|
+
|
|
559
|
+
// farPlane + maxSamples + time + fogDensity
|
|
560
|
+
data[offset++] = camera.far ?? 1000
|
|
561
|
+
data[offset++] = this.maxSamples
|
|
562
|
+
data[offset++] = time
|
|
563
|
+
data[offset++] = this.fogDensity
|
|
564
|
+
|
|
565
|
+
// vec3 fogColor + shadowsEnabled
|
|
566
|
+
data[offset++] = fogColor[0]
|
|
567
|
+
data[offset++] = fogColor[1]
|
|
568
|
+
data[offset++] = fogColor[2]
|
|
569
|
+
data[offset++] = shadowsEnabled ? 1.0 : 0.0
|
|
570
|
+
|
|
571
|
+
// vec3 mainLightDir + mainLightIntensity
|
|
572
|
+
data[offset++] = mainLightDir[0]
|
|
573
|
+
data[offset++] = mainLightDir[1]
|
|
574
|
+
data[offset++] = mainLightDir[2]
|
|
575
|
+
data[offset++] = mainLightIntensity
|
|
576
|
+
|
|
577
|
+
// vec3 mainLightColor + scatterStrength
|
|
578
|
+
data[offset++] = mainLightColor[0]
|
|
579
|
+
data[offset++] = mainLightColor[1]
|
|
580
|
+
data[offset++] = mainLightColor[2]
|
|
581
|
+
data[offset++] = this.scatterStrength
|
|
582
|
+
|
|
583
|
+
// vec2 fogHeightFade + maxDistance + lightCount
|
|
584
|
+
data[offset++] = heightFade[0]
|
|
585
|
+
data[offset++] = heightFade[1]
|
|
586
|
+
data[offset++] = this.maxDistance
|
|
587
|
+
data[offset++] = lightCount
|
|
588
|
+
|
|
589
|
+
// debugMode + noiseStrength + noiseAnimated + mainLightScatter (adaptive)
|
|
590
|
+
data[offset++] = this.debugMode
|
|
591
|
+
data[offset++] = this.noiseStrength
|
|
592
|
+
data[offset++] = this.noiseAnimated ? 1.0 : 0.0
|
|
593
|
+
data[offset++] = this._currentMainLightScatter // Uses adaptive value
|
|
594
|
+
|
|
595
|
+
// noiseScale + mainLightSaturation (ends the struct)
|
|
596
|
+
data[offset++] = this.noiseScale
|
|
597
|
+
data[offset++] = this.mainLightSaturation
|
|
598
|
+
|
|
599
|
+
device.queue.writeBuffer(this.raymarchUniformBuffer, 0, data)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
_updateBlurUniforms(buffer, dirX, dirY) {
|
|
603
|
+
const data = new Float32Array([
|
|
604
|
+
dirX, dirY,
|
|
605
|
+
1.0 / this.renderWidth, 1.0 / this.renderHeight,
|
|
606
|
+
this.blurRadius, 0, 0, 0,
|
|
607
|
+
])
|
|
608
|
+
this.engine.device.queue.writeBuffer(buffer, 0, data)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
_updateCompositeUniforms() {
|
|
612
|
+
const data = new Float32Array([
|
|
613
|
+
this.canvasWidth, this.canvasHeight,
|
|
614
|
+
this.renderWidth, this.renderHeight,
|
|
615
|
+
1.0 / this.canvasWidth, 1.0 / this.canvasHeight,
|
|
616
|
+
this.brightnessThreshold, this.minVisibility,
|
|
617
|
+
this.skyBrightness, 0, // skyBrightness + padding
|
|
618
|
+
0, 0, // extra padding to 48 bytes
|
|
619
|
+
])
|
|
620
|
+
this.engine.device.queue.writeBuffer(this.compositeUniformBuffer, 0, data)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
_createRaymarchBindGroup() {
|
|
624
|
+
const { device } = this.engine
|
|
625
|
+
|
|
626
|
+
const depthTexture = this.gbuffer?.depth
|
|
627
|
+
if (!depthTexture) return null
|
|
628
|
+
|
|
629
|
+
const cascadeShadows = this.shadowPass?.getShadowMap?.() ?? this.fallbackCascadeShadowMap
|
|
630
|
+
const cascadeMatrices = this.shadowPass?.getCascadeMatricesBuffer?.() ?? this.fallbackCascadeMatrices
|
|
631
|
+
const shadowSampler = this.shadowPass?.getShadowSampler?.() ?? this.fallbackShadowSampler
|
|
632
|
+
|
|
633
|
+
// Light resources
|
|
634
|
+
const lightsBuffer = this.lightingPass?.getLightBuffer?.() ?? this.fallbackLightsBuffer
|
|
635
|
+
const spotShadowAtlas = this.shadowPass?.getSpotShadowAtlasView?.() ?? this.fallbackSpotShadowAtlas.createView()
|
|
636
|
+
const spotMatrices = this.shadowPass?.getSpotMatricesBuffer?.() ?? this.fallbackSpotMatrices
|
|
637
|
+
|
|
638
|
+
return device.createBindGroup({
|
|
639
|
+
label: 'Volumetric Raymarch Bind Group',
|
|
640
|
+
layout: this.raymarchBGL,
|
|
641
|
+
entries: [
|
|
642
|
+
{ binding: 0, resource: { buffer: this.raymarchUniformBuffer } },
|
|
643
|
+
{ binding: 1, resource: depthTexture.texture.createView({ aspect: 'depth-only' }) },
|
|
644
|
+
{ binding: 2, resource: cascadeShadows.createView({ dimension: '2d-array', aspect: 'depth-only' }) },
|
|
645
|
+
{ binding: 3, resource: shadowSampler },
|
|
646
|
+
{ binding: 4, resource: { buffer: cascadeMatrices } },
|
|
647
|
+
{ binding: 5, resource: { buffer: lightsBuffer } },
|
|
648
|
+
{ binding: 6, resource: spotShadowAtlas },
|
|
649
|
+
{ binding: 7, resource: { buffer: spotMatrices } },
|
|
650
|
+
],
|
|
651
|
+
})
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
_createBlurBindGroup(inputTexture, uniformBuffer) {
|
|
655
|
+
return this.engine.device.createBindGroup({
|
|
656
|
+
label: 'Volumetric Blur Bind Group',
|
|
657
|
+
layout: this.blurBGL,
|
|
658
|
+
entries: [
|
|
659
|
+
{ binding: 0, resource: { buffer: uniformBuffer } },
|
|
660
|
+
{ binding: 1, resource: inputTexture.view },
|
|
661
|
+
{ binding: 2, resource: this.linearSampler },
|
|
662
|
+
],
|
|
663
|
+
})
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
_createCompositeBindGroup() {
|
|
667
|
+
if (!this.inputTexture) return null
|
|
668
|
+
|
|
669
|
+
const depthTexture = this.gbuffer?.depth
|
|
670
|
+
if (!depthTexture) return null
|
|
671
|
+
|
|
672
|
+
return this.engine.device.createBindGroup({
|
|
673
|
+
label: 'Volumetric Composite Bind Group',
|
|
674
|
+
layout: this.compositeBGL,
|
|
675
|
+
entries: [
|
|
676
|
+
{ binding: 0, resource: { buffer: this.compositeUniformBuffer } },
|
|
677
|
+
{ binding: 1, resource: this.inputTexture.view },
|
|
678
|
+
{ binding: 2, resource: this.blurredTexture.view },
|
|
679
|
+
{ binding: 3, resource: this.linearSampler },
|
|
680
|
+
{ binding: 4, resource: depthTexture.texture.createView({ aspect: 'depth-only' }) },
|
|
681
|
+
],
|
|
682
|
+
})
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
_destroyTextures() {
|
|
686
|
+
const textures = [this.raymarchTexture, this.blurTempTexture, this.blurredTexture, this.outputTexture]
|
|
687
|
+
for (const tex of textures) {
|
|
688
|
+
if (tex?.texture) tex.texture.destroy()
|
|
689
|
+
}
|
|
690
|
+
this.raymarchTexture = null
|
|
691
|
+
this.blurTempTexture = null
|
|
692
|
+
this.blurredTexture = null
|
|
693
|
+
this.outputTexture = null
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
async _resize(width, height) {
|
|
697
|
+
await this._createResources(width, height)
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
_destroy() {
|
|
701
|
+
this._destroyTextures()
|
|
702
|
+
|
|
703
|
+
const buffers = [this.raymarchUniformBuffer, this.blurHUniformBuffer, this.blurVUniformBuffer, this.compositeUniformBuffer]
|
|
704
|
+
for (const buf of buffers) {
|
|
705
|
+
if (buf) buf.destroy()
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
this.raymarchPipeline = null
|
|
709
|
+
this.blurHPipeline = null
|
|
710
|
+
this.blurVPipeline = null
|
|
711
|
+
this.compositePipeline = null
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
export { VolumetricFogPass }
|