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,831 @@
|
|
|
1
|
+
import { BasePass } from "./BasePass.js"
|
|
2
|
+
import { Texture } from "../../Texture.js"
|
|
3
|
+
import { Frustum } from "../../utils/Frustum.js"
|
|
4
|
+
|
|
5
|
+
import lightingCommonWGSL from "../shaders/lighting_common.wgsl"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* TransparentPass - Forward rendering pass for transparent objects
|
|
9
|
+
*
|
|
10
|
+
* Renders transparent meshes with full PBR lighting on top of the HDR buffer.
|
|
11
|
+
* Uses back-to-front sorting for correct alpha blending.
|
|
12
|
+
* Reads depth from GBuffer for occlusion testing but does NOT write depth,
|
|
13
|
+
* so fog pass uses scene depth (what's behind transparent objects).
|
|
14
|
+
*/
|
|
15
|
+
class TransparentPass extends BasePass {
|
|
16
|
+
constructor(engine = null) {
|
|
17
|
+
super('Transparent', engine)
|
|
18
|
+
|
|
19
|
+
this.pipeline = null
|
|
20
|
+
this.bindGroupLayout = null
|
|
21
|
+
this.uniformBuffer = null
|
|
22
|
+
this.pipelineCache = new Map()
|
|
23
|
+
|
|
24
|
+
// References to shared resources
|
|
25
|
+
this.gbuffer = null
|
|
26
|
+
this.lightingUniforms = null
|
|
27
|
+
this.shadowPass = null
|
|
28
|
+
this.environmentMap = null
|
|
29
|
+
this.noiseTexture = null
|
|
30
|
+
this.noiseSize = 64
|
|
31
|
+
this.noiseAnimated = true
|
|
32
|
+
|
|
33
|
+
// Light buffers
|
|
34
|
+
this.tileLightBuffer = null
|
|
35
|
+
this.lightBuffer = null
|
|
36
|
+
|
|
37
|
+
// HiZ pass reference for occlusion culling
|
|
38
|
+
this.hizPass = null
|
|
39
|
+
|
|
40
|
+
// Frustum for transparent mesh culling
|
|
41
|
+
this.frustum = new Frustum()
|
|
42
|
+
|
|
43
|
+
// Distance fade for preventing object popping at culling distance
|
|
44
|
+
this.distanceFadeStart = 0 // Distance where fade begins
|
|
45
|
+
this.distanceFadeEnd = 0 // Distance where fade completes (0 = disabled)
|
|
46
|
+
|
|
47
|
+
// Culling stats for transparent meshes
|
|
48
|
+
this.cullingStats = {
|
|
49
|
+
total: 0,
|
|
50
|
+
rendered: 0,
|
|
51
|
+
culledByFrustum: 0,
|
|
52
|
+
culledByDistance: 0,
|
|
53
|
+
culledByOcclusion: 0
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Set the HiZ pass for occlusion culling
|
|
59
|
+
* @param {HiZPass} hizPass - The HiZ pass instance
|
|
60
|
+
*/
|
|
61
|
+
setHiZPass(hizPass) {
|
|
62
|
+
this.hizPass = hizPass
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Test if a transparent mesh should be culled
|
|
67
|
+
* @param {Mesh} mesh - The mesh to test
|
|
68
|
+
* @param {Camera} camera - Current camera
|
|
69
|
+
* @param {boolean} canCull - Whether frustum/occlusion culling is available
|
|
70
|
+
* @returns {string|null} - Reason for culling or null if visible
|
|
71
|
+
*/
|
|
72
|
+
_shouldCullMesh(mesh, camera, canCull) {
|
|
73
|
+
// Transparent mesh culling is disabled - same issues as legacy mesh culling
|
|
74
|
+
// where bounding spheres may be in local space or represent root nodes
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async _init() {
|
|
79
|
+
const { device } = this.engine
|
|
80
|
+
|
|
81
|
+
// Create uniform buffer (same layout as lighting pass + material params)
|
|
82
|
+
this.uniformBuffer = device.createBuffer({
|
|
83
|
+
size: 512, // Plenty of space for uniforms
|
|
84
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
85
|
+
label: 'Transparent Uniforms'
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
// Create placeholder resources for missing bindings
|
|
89
|
+
await this._createPlaceholders()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Set the GBuffer for depth testing
|
|
94
|
+
*/
|
|
95
|
+
setGBuffer(gbuffer) {
|
|
96
|
+
this.gbuffer = gbuffer
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set the HDR output texture to render onto
|
|
101
|
+
*/
|
|
102
|
+
setOutputTexture(texture) {
|
|
103
|
+
this.outputTexture = texture
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Set shadow pass reference
|
|
108
|
+
*/
|
|
109
|
+
setShadowPass(shadowPass) {
|
|
110
|
+
this.shadowPass = shadowPass
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Set environment map
|
|
115
|
+
*/
|
|
116
|
+
setEnvironmentMap(envMap, encoding = 'equirect') {
|
|
117
|
+
this.environmentMap = envMap
|
|
118
|
+
this.environmentEncoding = encoding
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Set noise texture for effects
|
|
123
|
+
*/
|
|
124
|
+
setNoise(noise, size = 64, animated = true) {
|
|
125
|
+
this.noiseTexture = noise
|
|
126
|
+
this.noiseSize = size
|
|
127
|
+
this.noiseAnimated = animated
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Set light buffers for tiled lighting
|
|
132
|
+
*/
|
|
133
|
+
setLightBuffers(tileLightBuffer, lightBuffer) {
|
|
134
|
+
this.tileLightBuffer = tileLightBuffer
|
|
135
|
+
this.lightBuffer = lightBuffer
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create or get pipeline for a mesh
|
|
140
|
+
*/
|
|
141
|
+
async _getOrCreatePipeline(mesh) {
|
|
142
|
+
const { device } = this.engine
|
|
143
|
+
const key = `transparent_${mesh.material.uid}`
|
|
144
|
+
|
|
145
|
+
if (this.pipelineCache.has(key)) {
|
|
146
|
+
return this.pipelineCache.get(key)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Build the shader
|
|
150
|
+
const shaderCode = this._buildShaderCode()
|
|
151
|
+
|
|
152
|
+
const shaderModule = device.createShaderModule({
|
|
153
|
+
label: 'Transparent Shader',
|
|
154
|
+
code: shaderCode
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Bind group layout
|
|
158
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
159
|
+
label: 'Transparent BindGroup Layout',
|
|
160
|
+
entries: [
|
|
161
|
+
// Uniforms
|
|
162
|
+
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'uniform' } },
|
|
163
|
+
// Albedo texture
|
|
164
|
+
{ binding: 1, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
165
|
+
{ binding: 2, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
166
|
+
// Normal texture
|
|
167
|
+
{ binding: 3, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
168
|
+
{ binding: 4, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
169
|
+
// ARM texture
|
|
170
|
+
{ binding: 5, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
171
|
+
{ binding: 6, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
172
|
+
// Environment map
|
|
173
|
+
{ binding: 7, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
174
|
+
{ binding: 8, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'filtering' } },
|
|
175
|
+
// Shadow map array (cascades)
|
|
176
|
+
{ binding: 9, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'depth', viewDimension: '2d-array' } },
|
|
177
|
+
{ binding: 10, visibility: GPUShaderStage.FRAGMENT, sampler: { type: 'comparison' } },
|
|
178
|
+
// Cascade matrices
|
|
179
|
+
{ binding: 11, visibility: GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage' } },
|
|
180
|
+
// Noise texture
|
|
181
|
+
{ binding: 12, visibility: GPUShaderStage.FRAGMENT, texture: { sampleType: 'float' } },
|
|
182
|
+
]
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
186
|
+
bindGroupLayouts: [bindGroupLayout]
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
// Vertex buffer layout (same as geometry pass)
|
|
190
|
+
const vertexBufferLayout = {
|
|
191
|
+
arrayStride: 80,
|
|
192
|
+
attributes: [
|
|
193
|
+
{ format: "float32x3", offset: 0, shaderLocation: 0 }, // position
|
|
194
|
+
{ format: "float32x2", offset: 12, shaderLocation: 1 }, // uv
|
|
195
|
+
{ format: "float32x3", offset: 20, shaderLocation: 2 }, // normal
|
|
196
|
+
{ format: "float32x4", offset: 32, shaderLocation: 3 }, // color
|
|
197
|
+
{ format: "float32x4", offset: 48, shaderLocation: 4 }, // weights
|
|
198
|
+
{ format: "uint32x4", offset: 64, shaderLocation: 5 }, // joints
|
|
199
|
+
],
|
|
200
|
+
stepMode: 'vertex'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
const instanceBufferLayout = {
|
|
204
|
+
arrayStride: 112, // 28 floats: matrix(16) + posRadius(4) + uvTransform(4) + color(4)
|
|
205
|
+
stepMode: 'instance',
|
|
206
|
+
attributes: [
|
|
207
|
+
{ format: "float32x4", offset: 0, shaderLocation: 6 },
|
|
208
|
+
{ format: "float32x4", offset: 16, shaderLocation: 7 },
|
|
209
|
+
{ format: "float32x4", offset: 32, shaderLocation: 8 },
|
|
210
|
+
{ format: "float32x4", offset: 48, shaderLocation: 9 },
|
|
211
|
+
{ format: "float32x4", offset: 64, shaderLocation: 10 },
|
|
212
|
+
{ format: "float32x4", offset: 80, shaderLocation: 11 }, // uvTransform
|
|
213
|
+
{ format: "float32x4", offset: 96, shaderLocation: 12 }, // color
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const pipeline = await device.createRenderPipelineAsync({
|
|
218
|
+
label: 'Transparent Pipeline',
|
|
219
|
+
layout: pipelineLayout,
|
|
220
|
+
vertex: {
|
|
221
|
+
module: shaderModule,
|
|
222
|
+
entryPoint: 'vertexMain',
|
|
223
|
+
buffers: [vertexBufferLayout, instanceBufferLayout]
|
|
224
|
+
},
|
|
225
|
+
fragment: {
|
|
226
|
+
module: shaderModule,
|
|
227
|
+
entryPoint: 'fragmentMain',
|
|
228
|
+
targets: [{
|
|
229
|
+
format: 'rgba16float',
|
|
230
|
+
blend: {
|
|
231
|
+
color: {
|
|
232
|
+
srcFactor: 'src-alpha',
|
|
233
|
+
dstFactor: 'one-minus-src-alpha',
|
|
234
|
+
operation: 'add'
|
|
235
|
+
},
|
|
236
|
+
alpha: {
|
|
237
|
+
srcFactor: 'one',
|
|
238
|
+
dstFactor: 'one-minus-src-alpha',
|
|
239
|
+
operation: 'add'
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}]
|
|
243
|
+
},
|
|
244
|
+
depthStencil: {
|
|
245
|
+
format: 'depth32float',
|
|
246
|
+
depthWriteEnabled: false, // Don't write depth - fog needs scene depth behind transparent objects
|
|
247
|
+
depthCompare: 'less',
|
|
248
|
+
},
|
|
249
|
+
primitive: {
|
|
250
|
+
topology: 'triangle-list',
|
|
251
|
+
cullMode: 'none', // No culling for transparent objects
|
|
252
|
+
}
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
this.pipelineCache.set(key, { pipeline, bindGroupLayout })
|
|
256
|
+
return { pipeline, bindGroupLayout }
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Build the forward transparent shader
|
|
261
|
+
*/
|
|
262
|
+
_buildShaderCode() {
|
|
263
|
+
return `
|
|
264
|
+
${lightingCommonWGSL}
|
|
265
|
+
|
|
266
|
+
struct TransparentUniforms {
|
|
267
|
+
viewMatrix: mat4x4f,
|
|
268
|
+
projectionMatrix: mat4x4f,
|
|
269
|
+
cameraPosition: vec3f,
|
|
270
|
+
opacity: f32,
|
|
271
|
+
lightDir: vec3f,
|
|
272
|
+
lightIntensity: f32,
|
|
273
|
+
lightColor: vec3f,
|
|
274
|
+
ambientIntensity: f32,
|
|
275
|
+
ambientColor: vec3f,
|
|
276
|
+
envMipCount: f32,
|
|
277
|
+
envDiffuse: f32,
|
|
278
|
+
envSpecular: f32,
|
|
279
|
+
exposure: f32,
|
|
280
|
+
envEncoding: f32, // 0=equirect, 1=octahedral
|
|
281
|
+
shadowBias: f32,
|
|
282
|
+
shadowNormalBias: f32,
|
|
283
|
+
shadowStrength: f32,
|
|
284
|
+
cascadeSize0: f32,
|
|
285
|
+
cascadeSize1: f32,
|
|
286
|
+
cascadeSize2: f32,
|
|
287
|
+
noiseSize: f32,
|
|
288
|
+
noiseOffsetX: f32,
|
|
289
|
+
noiseOffsetY: f32,
|
|
290
|
+
distanceFadeStart: f32, // Distance where fade begins
|
|
291
|
+
distanceFadeEnd: f32, // Distance where fade completes (0 = disabled)
|
|
292
|
+
fogEnabled: f32, // Whether fog is enabled
|
|
293
|
+
fogPad1: f32, // Padding for vec3 alignment
|
|
294
|
+
fogPad2: f32,
|
|
295
|
+
fogPad3: f32,
|
|
296
|
+
fogColor: vec3f, // Fog color
|
|
297
|
+
fogBrightResist: f32, // HDR brightness resistance
|
|
298
|
+
fogDistances: vec3f, // Distance thresholds [near, mid, far]
|
|
299
|
+
fogPad4: f32,
|
|
300
|
+
fogAlphas: vec3f, // Alpha values at distances
|
|
301
|
+
fogPad5: f32,
|
|
302
|
+
fogHeightFade: vec2f, // [bottomY, topY]
|
|
303
|
+
cameraNear: f32,
|
|
304
|
+
cameraFar: f32,
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
struct VertexInput {
|
|
308
|
+
@location(0) position: vec3f,
|
|
309
|
+
@location(1) uv: vec2f,
|
|
310
|
+
@location(2) normal: vec3f,
|
|
311
|
+
@location(3) color: vec4f,
|
|
312
|
+
@location(4) weights: vec4f,
|
|
313
|
+
@location(5) joints: vec4u,
|
|
314
|
+
@location(6) model0: vec4f,
|
|
315
|
+
@location(7) model1: vec4f,
|
|
316
|
+
@location(8) model2: vec4f,
|
|
317
|
+
@location(9) model3: vec4f,
|
|
318
|
+
@location(10) posRadius: vec4f,
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
struct VertexOutput {
|
|
322
|
+
@builtin(position) position: vec4f,
|
|
323
|
+
@location(0) worldPos: vec3f,
|
|
324
|
+
@location(1) uv: vec2f,
|
|
325
|
+
@location(2) normal: vec3f,
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
@group(0) @binding(0) var<uniform> uniforms: TransparentUniforms;
|
|
329
|
+
@group(0) @binding(1) var albedoTexture: texture_2d<f32>;
|
|
330
|
+
@group(0) @binding(2) var albedoSampler: sampler;
|
|
331
|
+
@group(0) @binding(3) var normalTexture: texture_2d<f32>;
|
|
332
|
+
@group(0) @binding(4) var normalSampler: sampler;
|
|
333
|
+
@group(0) @binding(5) var armTexture: texture_2d<f32>;
|
|
334
|
+
@group(0) @binding(6) var armSampler: sampler;
|
|
335
|
+
@group(0) @binding(7) var envTexture: texture_2d<f32>;
|
|
336
|
+
@group(0) @binding(8) var envSampler: sampler;
|
|
337
|
+
@group(0) @binding(9) var shadowMapArray: texture_depth_2d_array;
|
|
338
|
+
@group(0) @binding(10) var shadowSampler: sampler_comparison;
|
|
339
|
+
@group(0) @binding(11) var<storage, read> cascadeMatrices: CascadeMatrices;
|
|
340
|
+
@group(0) @binding(12) var noiseTexture: texture_2d<f32>;
|
|
341
|
+
|
|
342
|
+
fn getEnvUV(dir: vec3f) -> vec2f {
|
|
343
|
+
if (uniforms.envEncoding > 0.5) {
|
|
344
|
+
return octEncode(dir);
|
|
345
|
+
}
|
|
346
|
+
return SphToUV(dir);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
fn sampleNoise(screenPos: vec2f) -> f32 {
|
|
350
|
+
let noiseSize = i32(uniforms.noiseSize);
|
|
351
|
+
let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));
|
|
352
|
+
let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));
|
|
353
|
+
let texCoord = vec2i(
|
|
354
|
+
(i32(screenPos.x) + noiseOffsetX) % noiseSize,
|
|
355
|
+
(i32(screenPos.y) + noiseOffsetY) % noiseSize
|
|
356
|
+
);
|
|
357
|
+
return textureLoad(noiseTexture, texCoord, 0).r;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
fn getNoiseJitter(screenPos: vec2f) -> vec2f {
|
|
361
|
+
let noiseSize = i32(uniforms.noiseSize);
|
|
362
|
+
let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));
|
|
363
|
+
let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));
|
|
364
|
+
let texCoord1 = vec2i(
|
|
365
|
+
(i32(screenPos.x) + noiseOffsetX) % noiseSize,
|
|
366
|
+
(i32(screenPos.y) + noiseOffsetY) % noiseSize
|
|
367
|
+
);
|
|
368
|
+
let texCoord2 = vec2i(
|
|
369
|
+
(i32(screenPos.x) + 37 + noiseOffsetX) % noiseSize,
|
|
370
|
+
(i32(screenPos.y) + 17 + noiseOffsetY) % noiseSize
|
|
371
|
+
);
|
|
372
|
+
let n1 = textureLoad(noiseTexture, texCoord1, 0).r;
|
|
373
|
+
let n2 = textureLoad(noiseTexture, texCoord2, 0).r;
|
|
374
|
+
return vec2f(n1, n2) * 2.0 - 1.0;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
fn getIBLSample(reflection: vec3f, lod: f32) -> vec3f {
|
|
378
|
+
let envRGBE = textureSampleLevel(envTexture, envSampler, getEnvUV(reflection), lod);
|
|
379
|
+
let envColor = envRGBE.rgb * pow(2.0, envRGBE.a * 255.0 - 128.0);
|
|
380
|
+
return envColor;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Sample cascade shadow
|
|
384
|
+
fn sampleCascadeShadow(worldPos: vec3f, normal: vec3f, cascadeIndex: i32, screenPos: vec2f) -> f32 {
|
|
385
|
+
let bias = uniforms.shadowBias;
|
|
386
|
+
let normalBias = uniforms.shadowNormalBias;
|
|
387
|
+
let biasedPos = worldPos + normal * normalBias;
|
|
388
|
+
let lightMatrix = cascadeMatrices.matrices[cascadeIndex];
|
|
389
|
+
let lightSpacePos = lightMatrix * vec4f(biasedPos, 1.0);
|
|
390
|
+
let projCoords = lightSpacePos.xyz / lightSpacePos.w;
|
|
391
|
+
let shadowUV = vec2f(projCoords.x * 0.5 + 0.5, 0.5 - projCoords.y * 0.5);
|
|
392
|
+
let currentDepth = projCoords.z - bias;
|
|
393
|
+
|
|
394
|
+
if (shadowUV.x < 0.0 || shadowUV.x > 1.0 || shadowUV.y < 0.0 || shadowUV.y > 1.0) {
|
|
395
|
+
return 1.0;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Simple shadow sampling with jitter
|
|
399
|
+
let jitter = getNoiseJitter(screenPos);
|
|
400
|
+
let texelSize = 1.0 / 2048.0;
|
|
401
|
+
var shadow = 0.0;
|
|
402
|
+
for (var i = 0; i < 4; i++) {
|
|
403
|
+
let offset = vogelDiskSample(i, 4, jitter.x * PI) * texelSize * 2.0;
|
|
404
|
+
shadow += textureSampleCompareLevel(shadowMapArray, shadowSampler, shadowUV + offset, cascadeIndex, currentDepth);
|
|
405
|
+
}
|
|
406
|
+
return shadow / 4.0;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
fn calculateShadow(worldPos: vec3f, normal: vec3f, screenPos: vec2f) -> f32 {
|
|
410
|
+
let camXZ = vec2f(uniforms.cameraPosition.x, uniforms.cameraPosition.z);
|
|
411
|
+
let posXZ = vec2f(worldPos.x, worldPos.z);
|
|
412
|
+
let offsetXZ = posXZ - camXZ;
|
|
413
|
+
|
|
414
|
+
let dist0 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize0);
|
|
415
|
+
let dist1 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize1);
|
|
416
|
+
let dist2 = squircleDistanceXZ(offsetXZ, uniforms.cascadeSize2);
|
|
417
|
+
|
|
418
|
+
var shadow = 1.0;
|
|
419
|
+
if (dist0 < 1.0) {
|
|
420
|
+
shadow = sampleCascadeShadow(worldPos, normal, 0, screenPos);
|
|
421
|
+
} else if (dist1 < 1.0) {
|
|
422
|
+
shadow = sampleCascadeShadow(worldPos, normal, 1, screenPos);
|
|
423
|
+
} else if (dist2 < 1.0) {
|
|
424
|
+
shadow = sampleCascadeShadow(worldPos, normal, 2, screenPos);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
return mix(1.0 - uniforms.shadowStrength, 1.0, shadow);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
@vertex
|
|
431
|
+
fn vertexMain(input: VertexInput) -> VertexOutput {
|
|
432
|
+
var output: VertexOutput;
|
|
433
|
+
|
|
434
|
+
let modelMatrix = mat4x4f(
|
|
435
|
+
input.model0,
|
|
436
|
+
input.model1,
|
|
437
|
+
input.model2,
|
|
438
|
+
input.model3
|
|
439
|
+
);
|
|
440
|
+
|
|
441
|
+
let worldPos = (modelMatrix * vec4f(input.position, 1.0)).xyz;
|
|
442
|
+
let viewPos = uniforms.viewMatrix * vec4f(worldPos, 1.0);
|
|
443
|
+
output.position = uniforms.projectionMatrix * viewPos;
|
|
444
|
+
output.worldPos = worldPos;
|
|
445
|
+
output.uv = input.uv;
|
|
446
|
+
|
|
447
|
+
let normalMatrix = mat3x3f(
|
|
448
|
+
modelMatrix[0].xyz,
|
|
449
|
+
modelMatrix[1].xyz,
|
|
450
|
+
modelMatrix[2].xyz
|
|
451
|
+
);
|
|
452
|
+
output.normal = normalize(normalMatrix * input.normal);
|
|
453
|
+
|
|
454
|
+
return output;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
@fragment
|
|
458
|
+
fn fragmentMain(input: VertexOutput) -> @location(0) vec4f {
|
|
459
|
+
// Sample textures
|
|
460
|
+
let albedoSample = textureSample(albedoTexture, albedoSampler, input.uv);
|
|
461
|
+
let armSample = textureSample(armTexture, armSampler, input.uv);
|
|
462
|
+
|
|
463
|
+
// Calculate alpha (albedo alpha * material opacity)
|
|
464
|
+
var alpha = albedoSample.a * uniforms.opacity;
|
|
465
|
+
|
|
466
|
+
// Distance fade: modulate alpha to prevent popping at culling distance
|
|
467
|
+
if (uniforms.distanceFadeEnd > 0.0) {
|
|
468
|
+
let distToCamera = length(input.worldPos - uniforms.cameraPosition);
|
|
469
|
+
if (distToCamera >= uniforms.distanceFadeEnd) {
|
|
470
|
+
discard; // Beyond fade end - fully invisible
|
|
471
|
+
}
|
|
472
|
+
if (distToCamera > uniforms.distanceFadeStart) {
|
|
473
|
+
// Calculate fade factor: 1.0 at fadeStart, 0.0 at fadeEnd
|
|
474
|
+
let fadeRange = uniforms.distanceFadeEnd - uniforms.distanceFadeStart;
|
|
475
|
+
let fadeFactor = 1.0 - (distToCamera - uniforms.distanceFadeStart) / fadeRange;
|
|
476
|
+
alpha *= fadeFactor;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Discard very transparent fragments
|
|
481
|
+
if (alpha < 0.01) {
|
|
482
|
+
discard;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
let baseColor = albedoSample.rgb;
|
|
486
|
+
let ao = armSample.r;
|
|
487
|
+
let roughness = max(armSample.g, 0.04);
|
|
488
|
+
let metallic = armSample.b;
|
|
489
|
+
let alphaRoughness = roughness * roughness;
|
|
490
|
+
|
|
491
|
+
// Normal mapping
|
|
492
|
+
let normalSample = textureSample(normalTexture, normalSampler, input.uv).rgb;
|
|
493
|
+
let tangentNormal = normalize(normalSample * 2.0 - 1.0);
|
|
494
|
+
let N = normalize(input.normal);
|
|
495
|
+
let refVec = select(vec3f(0.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), abs(N.y) > 0.9);
|
|
496
|
+
let T = normalize(cross(N, refVec));
|
|
497
|
+
let B = cross(N, T);
|
|
498
|
+
let TBN = mat3x3f(T, B, N);
|
|
499
|
+
let n = normalize(TBN * tangentNormal);
|
|
500
|
+
|
|
501
|
+
// View direction
|
|
502
|
+
let v = normalize(uniforms.cameraPosition - input.worldPos);
|
|
503
|
+
let NdotV = clampedDot(n, v);
|
|
504
|
+
|
|
505
|
+
// PBR setup
|
|
506
|
+
var f0 = vec3f(0.04);
|
|
507
|
+
f0 = mix(f0, baseColor, metallic);
|
|
508
|
+
let f90 = vec3f(1.0);
|
|
509
|
+
let c_diff = mix(baseColor, vec3f(0.0), metallic);
|
|
510
|
+
let specularWeight = 1.0;
|
|
511
|
+
|
|
512
|
+
var diffuse = vec3f(0.0);
|
|
513
|
+
var specular = vec3f(0.0);
|
|
514
|
+
|
|
515
|
+
// Ambient lighting
|
|
516
|
+
diffuse += ao * uniforms.ambientColor * uniforms.ambientIntensity * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);
|
|
517
|
+
|
|
518
|
+
// Environment diffuse
|
|
519
|
+
let envDiffuseSample = getIBLSample(n, uniforms.envMipCount - 2.0);
|
|
520
|
+
diffuse += ao * uniforms.envDiffuse * envDiffuseSample * BRDF_lambertian(f0, f90, c_diff, specularWeight, 1.0);
|
|
521
|
+
|
|
522
|
+
// Environment specular
|
|
523
|
+
let reflection = normalize(reflect(-v, n));
|
|
524
|
+
let lod = roughness * (uniforms.envMipCount - 1.0);
|
|
525
|
+
let Fr = max(vec3f(1.0 - roughness), f0) - f0;
|
|
526
|
+
let k_S = f0 + Fr * pow(1.0 - NdotV, 5.0);
|
|
527
|
+
let envSpecSample = getIBLSample(reflection, lod);
|
|
528
|
+
specular += ao * uniforms.envSpecular * envSpecSample * k_S;
|
|
529
|
+
|
|
530
|
+
// Directional light
|
|
531
|
+
let l = normalize(uniforms.lightDir);
|
|
532
|
+
let h = normalize(l + v);
|
|
533
|
+
let NdotL = clampedDot(n, l);
|
|
534
|
+
let NdotH = clampedDot(n, h);
|
|
535
|
+
let VdotH = clampedDot(v, h);
|
|
536
|
+
|
|
537
|
+
let shadow = calculateShadow(input.worldPos, n, input.position.xy);
|
|
538
|
+
let lightContrib = uniforms.lightIntensity * uniforms.lightColor * NdotL * shadow;
|
|
539
|
+
|
|
540
|
+
diffuse += lightContrib * BRDF_lambertian(f0, f90, c_diff, specularWeight, VdotH);
|
|
541
|
+
specular += lightContrib * BRDF_specularGGX(f0, f90, alphaRoughness, specularWeight, VdotH, NdotL, NdotV, NdotH);
|
|
542
|
+
|
|
543
|
+
// Final color
|
|
544
|
+
var color = (diffuse + specular) * uniforms.exposure;
|
|
545
|
+
|
|
546
|
+
// Fog is applied as post-process after all transparent/particle rendering
|
|
547
|
+
|
|
548
|
+
// For glass-like materials, reduce color intensity based on transparency
|
|
549
|
+
// More transparent = more of background shows through
|
|
550
|
+
color *= alpha;
|
|
551
|
+
|
|
552
|
+
return vec4f(color, alpha);
|
|
553
|
+
}
|
|
554
|
+
`
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Execute the transparent pass
|
|
559
|
+
*/
|
|
560
|
+
async _execute(context) {
|
|
561
|
+
const { device, canvas, stats } = this.engine
|
|
562
|
+
const { camera, meshes, mainLight } = context
|
|
563
|
+
|
|
564
|
+
// Initialize transparent stats
|
|
565
|
+
stats.transparentDrawCalls = 0
|
|
566
|
+
stats.transparentTriangles = 0
|
|
567
|
+
|
|
568
|
+
if (!this.outputTexture || !this.gbuffer) {
|
|
569
|
+
return
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// Update frustum for transparent mesh culling (only if camera has required properties)
|
|
573
|
+
const canCull = camera.view && camera.proj && camera.position && camera.direction
|
|
574
|
+
if (canCull) {
|
|
575
|
+
const fovRadians = (camera.fov || 60) * (Math.PI / 180)
|
|
576
|
+
this.frustum.update(
|
|
577
|
+
camera.view,
|
|
578
|
+
camera.proj,
|
|
579
|
+
camera.position,
|
|
580
|
+
camera.direction,
|
|
581
|
+
fovRadians,
|
|
582
|
+
camera.aspect || (canvas.width / canvas.height),
|
|
583
|
+
camera.near || 0.05,
|
|
584
|
+
camera.far || 1000,
|
|
585
|
+
canvas.width,
|
|
586
|
+
canvas.height
|
|
587
|
+
)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Reset culling stats
|
|
591
|
+
this.cullingStats.total = 0
|
|
592
|
+
this.cullingStats.rendered = 0
|
|
593
|
+
this.cullingStats.culledByFrustum = 0
|
|
594
|
+
this.cullingStats.culledByDistance = 0
|
|
595
|
+
this.cullingStats.culledByOcclusion = 0
|
|
596
|
+
|
|
597
|
+
// Collect transparent meshes with culling
|
|
598
|
+
const transparentMeshes = []
|
|
599
|
+
for (const name in meshes) {
|
|
600
|
+
const mesh = meshes[name]
|
|
601
|
+
if (mesh.material?.transparent && mesh.geometry?.instanceCount > 0) {
|
|
602
|
+
this.cullingStats.total++
|
|
603
|
+
|
|
604
|
+
// Apply culling (frustum, distance, occlusion)
|
|
605
|
+
const cullReason = this._shouldCullMesh(mesh, camera, canCull)
|
|
606
|
+
if (cullReason) {
|
|
607
|
+
if (cullReason === 'frustum') this.cullingStats.culledByFrustum++
|
|
608
|
+
else if (cullReason === 'distance') this.cullingStats.culledByDistance++
|
|
609
|
+
else if (cullReason === 'occlusion') this.cullingStats.culledByOcclusion++
|
|
610
|
+
continue
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
this.cullingStats.rendered++
|
|
614
|
+
transparentMeshes.push({ name, mesh })
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
if (transparentMeshes.length === 0) {
|
|
619
|
+
return
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Sort back-to-front by distance to camera
|
|
623
|
+
transparentMeshes.sort((a, b) => {
|
|
624
|
+
// Get center position from first instance (simplified)
|
|
625
|
+
const aPos = a.mesh.geometry.instanceData ?
|
|
626
|
+
[a.mesh.geometry.instanceData[12], a.mesh.geometry.instanceData[13], a.mesh.geometry.instanceData[14]] :
|
|
627
|
+
[0, 0, 0]
|
|
628
|
+
const bPos = b.mesh.geometry.instanceData ?
|
|
629
|
+
[b.mesh.geometry.instanceData[12], b.mesh.geometry.instanceData[13], b.mesh.geometry.instanceData[14]] :
|
|
630
|
+
[0, 0, 0]
|
|
631
|
+
|
|
632
|
+
const aDist = (aPos[0] - camera.position[0]) ** 2 +
|
|
633
|
+
(aPos[1] - camera.position[1]) ** 2 +
|
|
634
|
+
(aPos[2] - camera.position[2]) ** 2
|
|
635
|
+
const bDist = (bPos[0] - camera.position[0]) ** 2 +
|
|
636
|
+
(bPos[1] - camera.position[1]) ** 2 +
|
|
637
|
+
(bPos[2] - camera.position[2]) ** 2
|
|
638
|
+
|
|
639
|
+
return bDist - aDist // Back to front
|
|
640
|
+
})
|
|
641
|
+
|
|
642
|
+
// Render each transparent mesh
|
|
643
|
+
const commandEncoder = device.createCommandEncoder({ label: 'Transparent Pass' })
|
|
644
|
+
|
|
645
|
+
const passEncoder = commandEncoder.beginRenderPass({
|
|
646
|
+
colorAttachments: [{
|
|
647
|
+
view: this.outputTexture.view,
|
|
648
|
+
loadOp: 'load', // Preserve existing content
|
|
649
|
+
storeOp: 'store',
|
|
650
|
+
}],
|
|
651
|
+
depthStencilAttachment: {
|
|
652
|
+
view: this.gbuffer.depth.view,
|
|
653
|
+
depthLoadOp: 'load',
|
|
654
|
+
depthStoreOp: 'store',
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
|
|
658
|
+
for (const { mesh } of transparentMeshes) {
|
|
659
|
+
const { pipeline, bindGroupLayout } = await this._getOrCreatePipeline(mesh)
|
|
660
|
+
|
|
661
|
+
// Update uniforms
|
|
662
|
+
const uniformData = new Float32Array(96)
|
|
663
|
+
uniformData.set(camera.view, 0)
|
|
664
|
+
uniformData.set(camera.proj, 16)
|
|
665
|
+
uniformData.set(camera.position, 32)
|
|
666
|
+
uniformData[35] = mesh.material.opacity ?? 1.0
|
|
667
|
+
|
|
668
|
+
// Light direction
|
|
669
|
+
const lightDir = mainLight?.direction || [-1, 1, -0.5]
|
|
670
|
+
uniformData.set(lightDir, 36)
|
|
671
|
+
uniformData[39] = mainLight?.color?.[3] ?? 1.0 // intensity
|
|
672
|
+
|
|
673
|
+
// Light color
|
|
674
|
+
const lightColor = mainLight?.color || [1, 1, 1]
|
|
675
|
+
uniformData.set(lightColor, 40)
|
|
676
|
+
uniformData[43] = this.settings?.environment?.ambientIntensity ?? 0.3
|
|
677
|
+
|
|
678
|
+
// Ambient color
|
|
679
|
+
const ambientColor = this.settings?.environment?.ambientColor || [0.5, 0.5, 0.6]
|
|
680
|
+
uniformData.set(ambientColor, 44)
|
|
681
|
+
uniformData[47] = this.settings?.environment?.envMipCount ?? 8 // envMipCount
|
|
682
|
+
|
|
683
|
+
// Environment params
|
|
684
|
+
uniformData[48] = this.settings?.environment?.diffuseLevel ?? 0.5
|
|
685
|
+
uniformData[49] = this.settings?.environment?.specularLevel ?? 1.0
|
|
686
|
+
uniformData[50] = this.settings?.environment?.exposure ?? 1.0
|
|
687
|
+
uniformData[51] = this.environmentEncoding === 'octahedral' ? 1.0 : 0.0
|
|
688
|
+
|
|
689
|
+
// Shadow params
|
|
690
|
+
uniformData[52] = this.settings?.shadow?.bias ?? 0.001
|
|
691
|
+
uniformData[53] = this.settings?.shadow?.normalBias ?? 0.02
|
|
692
|
+
uniformData[54] = this.settings?.shadow?.strength ?? 0.7
|
|
693
|
+
|
|
694
|
+
// Cascade sizes
|
|
695
|
+
const cascadeSizes = this.shadowPass?.getCascadeSizes() || [20, 60, 300]
|
|
696
|
+
uniformData[55] = cascadeSizes[0]
|
|
697
|
+
uniformData[56] = cascadeSizes[1]
|
|
698
|
+
uniformData[57] = cascadeSizes[2]
|
|
699
|
+
|
|
700
|
+
// Noise params
|
|
701
|
+
uniformData[58] = this.noiseSize
|
|
702
|
+
uniformData[59] = this.noiseAnimated ? Math.random() : 0
|
|
703
|
+
uniformData[60] = this.noiseAnimated ? Math.random() : 0
|
|
704
|
+
|
|
705
|
+
// Distance fade params
|
|
706
|
+
uniformData[61] = this.distanceFadeStart
|
|
707
|
+
uniformData[62] = this.distanceFadeEnd
|
|
708
|
+
|
|
709
|
+
// Fog params
|
|
710
|
+
const fogSettings = this.settings?.environment?.fog
|
|
711
|
+
uniformData[63] = fogSettings?.enabled ? 1.0 : 0.0
|
|
712
|
+
// Padding at 64-66 for vec3 alignment (indices 64, 65, 66 are pad)
|
|
713
|
+
// fogColor at indices 68-70 (aligned to 16 bytes at byte 272)
|
|
714
|
+
const fogColor = fogSettings?.color ?? [0.8, 0.85, 0.9]
|
|
715
|
+
uniformData[68] = fogColor[0]
|
|
716
|
+
uniformData[69] = fogColor[1]
|
|
717
|
+
uniformData[70] = fogColor[2]
|
|
718
|
+
uniformData[71] = fogSettings?.brightResist ?? 0.8
|
|
719
|
+
// fogDistances at indices 72-74
|
|
720
|
+
const fogDistances = fogSettings?.distances ?? [0, 50, 200]
|
|
721
|
+
uniformData[72] = fogDistances[0]
|
|
722
|
+
uniformData[73] = fogDistances[1]
|
|
723
|
+
uniformData[74] = fogDistances[2]
|
|
724
|
+
// fogAlphas at indices 76-78
|
|
725
|
+
const fogAlphas = fogSettings?.alpha ?? [0, 0.3, 0.8]
|
|
726
|
+
uniformData[76] = fogAlphas[0]
|
|
727
|
+
uniformData[77] = fogAlphas[1]
|
|
728
|
+
uniformData[78] = fogAlphas[2]
|
|
729
|
+
// fogHeightFade at indices 80-81
|
|
730
|
+
const fogHeightFade = fogSettings?.heightFade ?? [-10, 100]
|
|
731
|
+
uniformData[80] = fogHeightFade[0]
|
|
732
|
+
uniformData[81] = fogHeightFade[1]
|
|
733
|
+
// cameraNear/Far at indices 82-83
|
|
734
|
+
uniformData[82] = camera.near || 0.1
|
|
735
|
+
uniformData[83] = camera.far || 1000
|
|
736
|
+
|
|
737
|
+
device.queue.writeBuffer(this.uniformBuffer, 0, uniformData)
|
|
738
|
+
|
|
739
|
+
// Create bind group
|
|
740
|
+
const bindGroup = device.createBindGroup({
|
|
741
|
+
layout: bindGroupLayout,
|
|
742
|
+
entries: [
|
|
743
|
+
{ binding: 0, resource: { buffer: this.uniformBuffer } },
|
|
744
|
+
{ binding: 1, resource: mesh.material.textures[0]?.view || this._placeholderTexture.view },
|
|
745
|
+
{ binding: 2, resource: mesh.material.textures[0]?.sampler || this._placeholderSampler },
|
|
746
|
+
{ binding: 3, resource: mesh.material.textures[1]?.view || this._placeholderNormal.view },
|
|
747
|
+
{ binding: 4, resource: mesh.material.textures[1]?.sampler || this._placeholderSampler },
|
|
748
|
+
{ binding: 5, resource: mesh.material.textures[3]?.view || this._placeholderTexture.view },
|
|
749
|
+
{ binding: 6, resource: mesh.material.textures[3]?.sampler || this._placeholderSampler },
|
|
750
|
+
{ binding: 7, resource: this.environmentMap?.view || this._placeholderTexture.view },
|
|
751
|
+
{ binding: 8, resource: this.environmentMap?.sampler || this._placeholderSampler },
|
|
752
|
+
{ binding: 9, resource: this.shadowPass?.getShadowMapView() || this._placeholderDepth.view },
|
|
753
|
+
{ binding: 10, resource: this.shadowPass?.getShadowSampler() || this._placeholderComparisonSampler },
|
|
754
|
+
{ binding: 11, resource: { buffer: this.shadowPass?.getCascadeMatricesBuffer() || this._placeholderBuffer } },
|
|
755
|
+
{ binding: 12, resource: this.noiseTexture?.view || this._placeholderTexture.view },
|
|
756
|
+
]
|
|
757
|
+
})
|
|
758
|
+
|
|
759
|
+
// Update geometry
|
|
760
|
+
mesh.geometry.update()
|
|
761
|
+
|
|
762
|
+
// Render
|
|
763
|
+
passEncoder.setPipeline(pipeline)
|
|
764
|
+
passEncoder.setBindGroup(0, bindGroup)
|
|
765
|
+
passEncoder.setVertexBuffer(0, mesh.geometry.vertexBuffer)
|
|
766
|
+
passEncoder.setVertexBuffer(1, mesh.geometry.instanceBuffer)
|
|
767
|
+
passEncoder.setIndexBuffer(mesh.geometry.indexBuffer, 'uint32')
|
|
768
|
+
passEncoder.drawIndexed(mesh.geometry.indexArray.length, mesh.geometry.instanceCount)
|
|
769
|
+
|
|
770
|
+
// Track stats
|
|
771
|
+
stats.transparentDrawCalls++
|
|
772
|
+
stats.transparentTriangles += (mesh.geometry.indexArray.length / 3) * mesh.geometry.instanceCount
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
passEncoder.end()
|
|
776
|
+
device.queue.submit([commandEncoder.finish()])
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Create placeholder resources for missing bindings
|
|
781
|
+
*/
|
|
782
|
+
async _createPlaceholders() {
|
|
783
|
+
const { device } = this.engine
|
|
784
|
+
|
|
785
|
+
// 1x1 white texture
|
|
786
|
+
this._placeholderTexture = await Texture.fromRGBA(this.engine, 1, 1, 1, 1)
|
|
787
|
+
|
|
788
|
+
// 1x1 normal texture (pointing up)
|
|
789
|
+
this._placeholderNormal = await Texture.fromColor(this.engine, "#8080FF")
|
|
790
|
+
|
|
791
|
+
// Sampler
|
|
792
|
+
this._placeholderSampler = device.createSampler({
|
|
793
|
+
magFilter: 'linear',
|
|
794
|
+
minFilter: 'linear',
|
|
795
|
+
})
|
|
796
|
+
|
|
797
|
+
// Comparison sampler
|
|
798
|
+
this._placeholderComparisonSampler = device.createSampler({
|
|
799
|
+
compare: 'less',
|
|
800
|
+
magFilter: 'linear',
|
|
801
|
+
minFilter: 'linear',
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
// 1x1 depth texture
|
|
805
|
+
this._placeholderDepthTexture = device.createTexture({
|
|
806
|
+
size: [1, 1, 1],
|
|
807
|
+
format: 'depth32float',
|
|
808
|
+
usage: GPUTextureUsage.TEXTURE_BINDING,
|
|
809
|
+
dimension: '2d',
|
|
810
|
+
})
|
|
811
|
+
this._placeholderDepth = {
|
|
812
|
+
view: this._placeholderDepthTexture.createView({ dimension: '2d-array', arrayLayerCount: 1 })
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Placeholder buffer
|
|
816
|
+
this._placeholderBuffer = device.createBuffer({
|
|
817
|
+
size: 256,
|
|
818
|
+
usage: GPUBufferUsage.STORAGE,
|
|
819
|
+
})
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
async _resize(width, height) {
|
|
823
|
+
// Nothing to resize
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
_destroy() {
|
|
827
|
+
this.pipelineCache.clear()
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
export { TransparentPass }
|