topazcube 0.1.30 → 0.1.33
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 +18200 -0
- package/dist/Renderer.cjs.map +1 -0
- package/dist/Renderer.js +18183 -0
- package/dist/Renderer.js.map +1 -0
- package/dist/client.cjs +94 -260
- package/dist/client.cjs.map +1 -1
- package/dist/client.js +71 -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} +173 -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 +572 -0
- package/src/renderer/Geometry.js +1049 -0
- package/src/renderer/Material.js +61 -0
- package/src/renderer/Mesh.js +211 -0
- package/src/renderer/Node.js +112 -0
- package/src/renderer/Pipeline.js +643 -0
- package/src/renderer/Renderer.js +1324 -0
- package/src/renderer/Skin.js +792 -0
- package/src/renderer/Texture.js +584 -0
- package/src/renderer/core/AssetManager.js +359 -0
- package/src/renderer/core/CullingSystem.js +307 -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 +546 -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 +2064 -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 +417 -0
- package/src/renderer/rendering/passes/FogPass.js +419 -0
- package/src/renderer/rendering/passes/GBufferPass.js +706 -0
- package/src/renderer/rendering/passes/HiZPass.js +714 -0
- package/src/renderer/rendering/passes/LightingPass.js +739 -0
- package/src/renderer/rendering/passes/ParticlePass.js +835 -0
- package/src/renderer/rendering/passes/PlanarReflectionPass.js +456 -0
- package/src/renderer/rendering/passes/PostProcessPass.js +282 -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 +265 -0
- package/src/renderer/rendering/passes/SSGITilePass.js +296 -0
- package/src/renderer/rendering/passes/ShadowPass.js +1822 -0
- package/src/renderer/rendering/passes/TransparentPass.js +831 -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/depth_copy.wgsl +17 -0
- package/src/renderer/rendering/shaders/geometry.wgsl +550 -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 +525 -0
- package/src/renderer/rendering/shaders/particle_simulate.wgsl +440 -0
- package/src/renderer/rendering/shaders/postproc.wgsl +272 -0
- package/src/renderer/rendering/shaders/render_post.wgsl +289 -0
- package/src/renderer/rendering/shaders/shadow.wgsl +76 -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/utils/BoundingSphere.js +439 -0
- package/src/renderer/utils/Frustum.js +281 -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,550 @@
|
|
|
1
|
+
struct VertexInput {
|
|
2
|
+
@location(0) position: vec3f,
|
|
3
|
+
@location(1) uv: vec2f,
|
|
4
|
+
@location(2) normal: vec3f,
|
|
5
|
+
@location(3) color: vec4f,
|
|
6
|
+
@location(4) weights: vec4f,
|
|
7
|
+
@location(5) joints: vec4u,
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
struct VertexOutput {
|
|
11
|
+
@invariant @builtin(position) position: vec4f, // @invariant ensures shared edges compute identical positions
|
|
12
|
+
@location(0) worldPos: vec3f,
|
|
13
|
+
@location(1) uv: vec2f,
|
|
14
|
+
@location(2) normal: vec3f,
|
|
15
|
+
@location(3) viewZ: f32, // Linear view-space depth for logarithmic encoding
|
|
16
|
+
@location(4) currClipPos: vec4f, // Current frame clip position (for motion vectors)
|
|
17
|
+
@location(5) prevClipPos: vec4f, // Previous frame clip position (for motion vectors)
|
|
18
|
+
@location(6) instanceColor: vec4f, // Per-instance color tint for sprites
|
|
19
|
+
@location(7) anchorY: f32, // Entity anchor Y position (for billboard clip plane testing)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
struct Uniforms {
|
|
23
|
+
viewMatrix: mat4x4f,
|
|
24
|
+
projectionMatrix: mat4x4f,
|
|
25
|
+
prevViewProjMatrix: mat4x4f, // Previous frame view-projection for motion vectors
|
|
26
|
+
mipBias: f32,
|
|
27
|
+
skinEnabled: f32, // 1.0 if skinning enabled, 0.0 otherwise
|
|
28
|
+
numJoints: f32, // Number of joints in the skin
|
|
29
|
+
near: f32, // Camera near plane
|
|
30
|
+
far: f32, // Camera far plane
|
|
31
|
+
jitterFadeDistance: f32, // Distance at which jitter fades to 0 (meters)
|
|
32
|
+
jitterOffset: vec2f, // TAA jitter offset in pixels
|
|
33
|
+
screenSize: vec2f, // Screen dimensions for pixel-to-clip conversion
|
|
34
|
+
emissionFactor: vec4f,
|
|
35
|
+
clipPlaneY: f32, // Y level for clip plane
|
|
36
|
+
clipPlaneEnabled: f32, // 1.0 to enable clip plane, 0.0 to disable
|
|
37
|
+
clipPlaneDirection: f32, // 1.0 = discard below clipPlaneY, -1.0 = discard above clipPlaneY
|
|
38
|
+
pixelRounding: f32, // Pixel grid size (0=off, 1=every pixel, 2=every 2px, etc.)
|
|
39
|
+
pixelExpansion: f32, // Sub-pixel expansion to convert gaps to overlaps (0=off, 0.05=default)
|
|
40
|
+
positionRounding: f32, // If > 0, round view-space position to this precision (simulates fixed-point)
|
|
41
|
+
alphaHashEnabled: f32, // 1.0 to enable alpha hashing/dithering for cutout transparency
|
|
42
|
+
alphaHashScale: f32, // Scale factor for alpha hash (default 1.0, higher = more opaque)
|
|
43
|
+
luminanceToAlpha: f32, // 1.0 to derive alpha from base color luminance (black=transparent)
|
|
44
|
+
noiseSize: f32, // Size of noise texture (8 for bayer, 64/128 for blue noise)
|
|
45
|
+
noiseOffsetX: f32, // Animated noise offset X
|
|
46
|
+
noiseOffsetY: f32, // Animated noise offset Y
|
|
47
|
+
cameraPosition: vec3f, // World-space camera position for distance fade
|
|
48
|
+
distanceFadeStart: f32, // Distance where fade begins (A)
|
|
49
|
+
distanceFadeEnd: f32, // Distance where fade completes (B) - object invisible
|
|
50
|
+
// Billboard/sprite uniforms
|
|
51
|
+
billboardMode: f32, // 0=none, 1=center (spherical), 2=bottom (cylindrical), 3=horizontal
|
|
52
|
+
billboardCameraRight: vec3f, // Camera right vector for billboarding
|
|
53
|
+
_pad1: f32,
|
|
54
|
+
billboardCameraUp: vec3f, // Camera up vector for billboarding
|
|
55
|
+
_pad2: f32,
|
|
56
|
+
billboardCameraForward: vec3f, // Camera forward vector for billboarding
|
|
57
|
+
specularBoost: f32, // Per-material specular boost (0-1), scales the 3-light specular effect
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
struct InstanceData {
|
|
61
|
+
modelMatrix: mat4x4f,
|
|
62
|
+
posRadius: vec4f,
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
66
|
+
@group(0) @binding(1) var albedoTexture: texture_2d<f32>;
|
|
67
|
+
@group(0) @binding(2) var albedoSampler: sampler;
|
|
68
|
+
@group(0) @binding(3) var normalTexture: texture_2d<f32>;
|
|
69
|
+
@group(0) @binding(4) var normalSampler: sampler;
|
|
70
|
+
@group(0) @binding(5) var ambientTexture: texture_2d<f32>;
|
|
71
|
+
@group(0) @binding(6) var ambientSampler: sampler;
|
|
72
|
+
@group(0) @binding(7) var rmTexture: texture_2d<f32>;
|
|
73
|
+
@group(0) @binding(8) var rmSampler: sampler;
|
|
74
|
+
@group(0) @binding(9) var emissionTexture: texture_2d<f32>;
|
|
75
|
+
@group(0) @binding(10) var emissionSampler: sampler;
|
|
76
|
+
@group(0) @binding(11) var jointTexture: texture_2d<f32>;
|
|
77
|
+
@group(0) @binding(12) var jointSampler: sampler;
|
|
78
|
+
@group(0) @binding(13) var prevJointTexture: texture_2d<f32>; // Previous frame joint matrices for skinned motion vectors
|
|
79
|
+
@group(0) @binding(14) var noiseTexture: texture_2d<f32>; // Noise texture for alpha hashing (blue noise or bayer)
|
|
80
|
+
|
|
81
|
+
// Sample noise at screen position (tiled with animated offset)
|
|
82
|
+
fn sampleNoise(screenPos: vec2f) -> f32 {
|
|
83
|
+
let noiseSize = i32(uniforms.noiseSize);
|
|
84
|
+
let noiseOffsetX = i32(uniforms.noiseOffsetX * f32(noiseSize));
|
|
85
|
+
let noiseOffsetY = i32(uniforms.noiseOffsetY * f32(noiseSize));
|
|
86
|
+
|
|
87
|
+
let texCoord = vec2i(
|
|
88
|
+
(i32(screenPos.x) + noiseOffsetX) % noiseSize,
|
|
89
|
+
(i32(screenPos.y) + noiseOffsetY) % noiseSize
|
|
90
|
+
);
|
|
91
|
+
return textureLoad(noiseTexture, texCoord, 0).r;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get a 4x4 matrix from the joint texture
|
|
95
|
+
// Each row of the texture stores one joint's matrix (4 RGBA pixels = 16 floats)
|
|
96
|
+
fn getJointMatrix(jointIndex: u32) -> mat4x4f {
|
|
97
|
+
let row = i32(jointIndex);
|
|
98
|
+
let col0 = textureLoad(jointTexture, vec2i(0, row), 0);
|
|
99
|
+
let col1 = textureLoad(jointTexture, vec2i(1, row), 0);
|
|
100
|
+
let col2 = textureLoad(jointTexture, vec2i(2, row), 0);
|
|
101
|
+
let col3 = textureLoad(jointTexture, vec2i(3, row), 0);
|
|
102
|
+
return mat4x4f(col0, col1, col2, col3);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Get a 4x4 matrix from the previous frame joint texture (for motion vectors)
|
|
106
|
+
fn getPrevJointMatrix(jointIndex: u32) -> mat4x4f {
|
|
107
|
+
let row = i32(jointIndex);
|
|
108
|
+
let col0 = textureLoad(prevJointTexture, vec2i(0, row), 0);
|
|
109
|
+
let col1 = textureLoad(prevJointTexture, vec2i(1, row), 0);
|
|
110
|
+
let col2 = textureLoad(prevJointTexture, vec2i(2, row), 0);
|
|
111
|
+
let col3 = textureLoad(prevJointTexture, vec2i(3, row), 0);
|
|
112
|
+
return mat4x4f(col0, col1, col2, col3);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Apply skinning to a position
|
|
116
|
+
fn applySkinning(position: vec3f, joints: vec4u, weights: vec4f) -> vec3f {
|
|
117
|
+
var skinnedPos = vec3f(0.0);
|
|
118
|
+
|
|
119
|
+
let m0 = getJointMatrix(joints.x);
|
|
120
|
+
let m1 = getJointMatrix(joints.y);
|
|
121
|
+
let m2 = getJointMatrix(joints.z);
|
|
122
|
+
let m3 = getJointMatrix(joints.w);
|
|
123
|
+
|
|
124
|
+
skinnedPos += (m0 * vec4f(position, 1.0)).xyz * weights.x;
|
|
125
|
+
skinnedPos += (m1 * vec4f(position, 1.0)).xyz * weights.y;
|
|
126
|
+
skinnedPos += (m2 * vec4f(position, 1.0)).xyz * weights.z;
|
|
127
|
+
skinnedPos += (m3 * vec4f(position, 1.0)).xyz * weights.w;
|
|
128
|
+
|
|
129
|
+
return skinnedPos;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Apply skinning with previous frame joint matrices (for motion vectors)
|
|
133
|
+
fn applySkinningPrev(position: vec3f, joints: vec4u, weights: vec4f) -> vec3f {
|
|
134
|
+
var skinnedPos = vec3f(0.0);
|
|
135
|
+
|
|
136
|
+
let m0 = getPrevJointMatrix(joints.x);
|
|
137
|
+
let m1 = getPrevJointMatrix(joints.y);
|
|
138
|
+
let m2 = getPrevJointMatrix(joints.z);
|
|
139
|
+
let m3 = getPrevJointMatrix(joints.w);
|
|
140
|
+
|
|
141
|
+
skinnedPos += (m0 * vec4f(position, 1.0)).xyz * weights.x;
|
|
142
|
+
skinnedPos += (m1 * vec4f(position, 1.0)).xyz * weights.y;
|
|
143
|
+
skinnedPos += (m2 * vec4f(position, 1.0)).xyz * weights.z;
|
|
144
|
+
skinnedPos += (m3 * vec4f(position, 1.0)).xyz * weights.w;
|
|
145
|
+
|
|
146
|
+
return skinnedPos;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Apply skinning to a normal (using 3x3 rotation part of matrices)
|
|
150
|
+
fn applySkinningNormal(normal: vec3f, joints: vec4u, weights: vec4f) -> vec3f {
|
|
151
|
+
var skinnedNormal = vec3f(0.0);
|
|
152
|
+
|
|
153
|
+
let m0 = getJointMatrix(joints.x);
|
|
154
|
+
let m1 = getJointMatrix(joints.y);
|
|
155
|
+
let m2 = getJointMatrix(joints.z);
|
|
156
|
+
let m3 = getJointMatrix(joints.w);
|
|
157
|
+
|
|
158
|
+
// Extract 3x3 rotation matrices
|
|
159
|
+
let r0 = mat3x3f(m0[0].xyz, m0[1].xyz, m0[2].xyz);
|
|
160
|
+
let r1 = mat3x3f(m1[0].xyz, m1[1].xyz, m1[2].xyz);
|
|
161
|
+
let r2 = mat3x3f(m2[0].xyz, m2[1].xyz, m2[2].xyz);
|
|
162
|
+
let r3 = mat3x3f(m3[0].xyz, m3[1].xyz, m3[2].xyz);
|
|
163
|
+
|
|
164
|
+
skinnedNormal += (r0 * normal) * weights.x;
|
|
165
|
+
skinnedNormal += (r1 * normal) * weights.y;
|
|
166
|
+
skinnedNormal += (r2 * normal) * weights.z;
|
|
167
|
+
skinnedNormal += (r3 * normal) * weights.w;
|
|
168
|
+
|
|
169
|
+
return normalize(skinnedNormal);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
@vertex
|
|
173
|
+
fn vertexMain(
|
|
174
|
+
input: VertexInput,
|
|
175
|
+
@builtin(instance_index) instanceIdx : u32,
|
|
176
|
+
@location(6) instanceModelMatrix0: vec4f,
|
|
177
|
+
@location(7) instanceModelMatrix1: vec4f,
|
|
178
|
+
@location(8) instanceModelMatrix2: vec4f,
|
|
179
|
+
@location(9) instanceModelMatrix3: vec4f,
|
|
180
|
+
@location(10) posRadius: vec4f,
|
|
181
|
+
@location(11) uvTransform: vec4f, // UV offset (xy) and scale (zw) for sprite sheets
|
|
182
|
+
@location(12) instanceColor: vec4f, // Per-instance color tint
|
|
183
|
+
) -> VertexOutput {
|
|
184
|
+
var output: VertexOutput;
|
|
185
|
+
|
|
186
|
+
// Construct instance model matrix
|
|
187
|
+
let instanceModelMatrix = mat4x4f(
|
|
188
|
+
instanceModelMatrix0,
|
|
189
|
+
instanceModelMatrix1,
|
|
190
|
+
instanceModelMatrix2,
|
|
191
|
+
instanceModelMatrix3
|
|
192
|
+
);
|
|
193
|
+
|
|
194
|
+
var localPos = input.position;
|
|
195
|
+
var prevLocalPos = input.position; // For motion vectors
|
|
196
|
+
var localNormal = input.normal;
|
|
197
|
+
var billboardWorldPos: vec3f;
|
|
198
|
+
var useBillboardWorldPos = false;
|
|
199
|
+
|
|
200
|
+
// Apply billboard transform if enabled
|
|
201
|
+
if (uniforms.billboardMode > 0.5) {
|
|
202
|
+
// Extract entity position (translation) from instance matrix
|
|
203
|
+
let entityPos = instanceModelMatrix[3].xyz;
|
|
204
|
+
|
|
205
|
+
// Extract scale from matrix (length of each column's xyz)
|
|
206
|
+
let scaleX = length(instanceModelMatrix[0].xyz);
|
|
207
|
+
let scaleY = length(instanceModelMatrix[1].xyz);
|
|
208
|
+
|
|
209
|
+
if (uniforms.billboardMode < 1.5) {
|
|
210
|
+
// Mode 1: Center (spherical billboard) - faces camera, centered on entity
|
|
211
|
+
// Calculate from camera position and world-up to avoid view matrix flip issues
|
|
212
|
+
let toCamera = uniforms.cameraPosition - entityPos;
|
|
213
|
+
let toCameraDist = length(toCamera);
|
|
214
|
+
|
|
215
|
+
var right: vec3f;
|
|
216
|
+
var up: vec3f;
|
|
217
|
+
|
|
218
|
+
if (toCameraDist > 0.001) {
|
|
219
|
+
let forward = toCamera / toCameraDist;
|
|
220
|
+
// Right = cross(worldUp, forward)
|
|
221
|
+
right = cross(vec3f(0.0, 1.0, 0.0), forward);
|
|
222
|
+
let rightLen = length(right);
|
|
223
|
+
if (rightLen < 0.001) {
|
|
224
|
+
// Camera directly above/below - use camera right as fallback
|
|
225
|
+
right = uniforms.billboardCameraRight;
|
|
226
|
+
up = vec3f(0.0, 0.0, 1.0); // Use Z as up when looking straight down
|
|
227
|
+
} else {
|
|
228
|
+
right = right / rightLen;
|
|
229
|
+
// Up is perpendicular to both forward and right
|
|
230
|
+
up = cross(forward, right);
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
right = uniforms.billboardCameraRight;
|
|
234
|
+
up = vec3f(0.0, 1.0, 0.0);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let offset = right * input.position.x * scaleX + up * input.position.y * scaleY;
|
|
238
|
+
billboardWorldPos = entityPos + offset;
|
|
239
|
+
useBillboardWorldPos = true;
|
|
240
|
+
localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up
|
|
241
|
+
|
|
242
|
+
} else if (uniforms.billboardMode < 2.5) {
|
|
243
|
+
// Mode 2: Bottom (face camera position) - tilts toward camera, pivots at bottom
|
|
244
|
+
// Like a sunflower facing the sun - when viewed from above, nearly horizontal
|
|
245
|
+
let toCamera = uniforms.cameraPosition - entityPos;
|
|
246
|
+
let toCameraDist = length(toCamera);
|
|
247
|
+
|
|
248
|
+
var right: vec3f;
|
|
249
|
+
var billUp: vec3f;
|
|
250
|
+
|
|
251
|
+
if (toCameraDist > 0.001) {
|
|
252
|
+
let forward = toCamera / toCameraDist; // Direction toward camera
|
|
253
|
+
// Right = cross(worldUp, forward)
|
|
254
|
+
right = cross(vec3f(0.0, 1.0, 0.0), forward);
|
|
255
|
+
let rightLen = length(right);
|
|
256
|
+
if (rightLen < 0.001) {
|
|
257
|
+
// Camera directly above/below - use camera right as fallback
|
|
258
|
+
right = uniforms.billboardCameraRight;
|
|
259
|
+
billUp = uniforms.billboardCameraUp;
|
|
260
|
+
} else {
|
|
261
|
+
right = right / rightLen;
|
|
262
|
+
// Billboard "up" is perpendicular to both forward and right
|
|
263
|
+
billUp = cross(forward, right);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// Camera at entity position - use camera vectors as fallback
|
|
267
|
+
right = uniforms.billboardCameraRight;
|
|
268
|
+
billUp = uniforms.billboardCameraUp;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
let offset = right * input.position.x * scaleX + billUp * input.position.y * scaleY;
|
|
272
|
+
billboardWorldPos = entityPos + offset;
|
|
273
|
+
useBillboardWorldPos = true;
|
|
274
|
+
localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up
|
|
275
|
+
|
|
276
|
+
} else {
|
|
277
|
+
// Mode 3: Horizontal - fixed in world space on XZ plane
|
|
278
|
+
// Uses entity's model matrix rotation (yaw only in practice)
|
|
279
|
+
// Don't modify localPos - let model matrix apply the transform
|
|
280
|
+
// Geometry is already on XZ plane from Geometry.billboardQuad('horizontal')
|
|
281
|
+
localNormal = vec3f(0.0, 1.0, 0.0); // Normal points up
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Apply skinning if enabled
|
|
286
|
+
if (uniforms.skinEnabled > 0.5) {
|
|
287
|
+
// Check if weights sum to something meaningful
|
|
288
|
+
let weightSum = input.weights.x + input.weights.y + input.weights.z + input.weights.w;
|
|
289
|
+
if (weightSum > 0.001) {
|
|
290
|
+
localPos = applySkinning(input.position, input.joints, input.weights);
|
|
291
|
+
prevLocalPos = applySkinningPrev(input.position, input.joints, input.weights);
|
|
292
|
+
localNormal = applySkinningNormal(input.normal, input.joints, input.weights);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Apply instance transform
|
|
297
|
+
// For billboard modes 1 and 2, we computed worldPos directly (bypassing matrix rotation)
|
|
298
|
+
// For billboard mode 3 and non-billboard, use standard matrix transform
|
|
299
|
+
var worldPos: vec3f;
|
|
300
|
+
if (useBillboardWorldPos) {
|
|
301
|
+
worldPos = billboardWorldPos;
|
|
302
|
+
} else {
|
|
303
|
+
worldPos = (instanceModelMatrix * vec4f(localPos, 1.0)).xyz;
|
|
304
|
+
}
|
|
305
|
+
var viewPos = uniforms.viewMatrix * vec4f(worldPos, 1.0);
|
|
306
|
+
|
|
307
|
+
// Check if this instance allows rounding (negative radius = no rounding)
|
|
308
|
+
let allowRounding = posRadius.w >= 0.0;
|
|
309
|
+
|
|
310
|
+
// Position rounding: simulate fixed-point by rounding view-space position
|
|
311
|
+
// Only round X and Y - rounding Z can cause clipping issues when vertices are near camera
|
|
312
|
+
if (allowRounding && uniforms.positionRounding > 0.0) {
|
|
313
|
+
let snap = uniforms.positionRounding;
|
|
314
|
+
viewPos = vec4f(
|
|
315
|
+
floor(viewPos.x / snap) * snap,
|
|
316
|
+
floor(viewPos.y / snap) * snap,
|
|
317
|
+
viewPos.z,
|
|
318
|
+
viewPos.w
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
var clipPos = uniforms.projectionMatrix * viewPos;
|
|
323
|
+
|
|
324
|
+
// Compute previous world position (using same instance matrix - static objects)
|
|
325
|
+
// For moving objects, we'd need prevInstanceModelMatrix in the instance buffer
|
|
326
|
+
// For billboards, use same worldPos (billboard faces camera each frame)
|
|
327
|
+
var prevWorldPos: vec3f;
|
|
328
|
+
if (useBillboardWorldPos) {
|
|
329
|
+
prevWorldPos = billboardWorldPos; // Billboard - same as current (TODO: proper motion vectors)
|
|
330
|
+
} else {
|
|
331
|
+
prevWorldPos = (instanceModelMatrix * vec4f(prevLocalPos, 1.0)).xyz;
|
|
332
|
+
}
|
|
333
|
+
let prevClipPos = uniforms.prevViewProjMatrix * vec4f(prevWorldPos, 1.0);
|
|
334
|
+
|
|
335
|
+
// Apply TAA jitter with distance-based fade
|
|
336
|
+
// Full jitter near camera, fading to 0 at jitterFadeDistance
|
|
337
|
+
let viewDist = -viewPos.z; // Positive distance from camera
|
|
338
|
+
let jitterFade = saturate(1.0 - viewDist / uniforms.jitterFadeDistance);
|
|
339
|
+
|
|
340
|
+
// Convert pixel offset to clip space and apply with fade
|
|
341
|
+
// In clip space, 1 pixel = 2/screenSize (since clip space is -1 to 1)
|
|
342
|
+
let jitterClip = uniforms.jitterOffset * 2.0 / uniforms.screenSize * jitterFade;
|
|
343
|
+
clipPos.x += jitterClip.x * clipPos.w;
|
|
344
|
+
clipPos.y += jitterClip.y * clipPos.w;
|
|
345
|
+
|
|
346
|
+
// Pixel rounding: snap vertices to pixel grid for retro/PSX aesthetic
|
|
347
|
+
// pixelRounding value = grid size in pixels (1.0 = every pixel, 2.0 = every 2 pixels, etc.)
|
|
348
|
+
if (allowRounding && uniforms.pixelRounding > 0.5) {
|
|
349
|
+
let gridSize = uniforms.pixelRounding; // Grid size in pixels
|
|
350
|
+
let ndc = clipPos.xy / clipPos.w;
|
|
351
|
+
|
|
352
|
+
// Convert NDC (-1..1) to pixel coordinates (0..screenSize)
|
|
353
|
+
let pixelCoords = (ndc + 1.0) * 0.5 * uniforms.screenSize;
|
|
354
|
+
|
|
355
|
+
// Snap to coarse grid (every gridSize pixels)
|
|
356
|
+
var snappedPixel = floor(pixelCoords / gridSize) * gridSize;
|
|
357
|
+
|
|
358
|
+
// Expansion to convert gaps to overlaps (push vertices slightly outward from screen center)
|
|
359
|
+
if (uniforms.pixelExpansion > 0.0) {
|
|
360
|
+
let screenCenter = uniforms.screenSize * 0.5;
|
|
361
|
+
let fromCenter = snappedPixel - screenCenter;
|
|
362
|
+
snappedPixel += sign(fromCenter) * uniforms.pixelExpansion;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Convert back to NDC
|
|
366
|
+
let snappedNDC = (snappedPixel / uniforms.screenSize) * 2.0 - 1.0;
|
|
367
|
+
|
|
368
|
+
// Convert back to clip space
|
|
369
|
+
clipPos.x = snappedNDC.x * clipPos.w;
|
|
370
|
+
clipPos.y = snappedNDC.y * clipPos.w;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
output.position = clipPos;
|
|
374
|
+
output.worldPos = worldPos;
|
|
375
|
+
// Apply UV transform for sprite sheets: uv * scale + offset
|
|
376
|
+
output.uv = input.uv * uvTransform.zw + uvTransform.xy;
|
|
377
|
+
output.viewZ = viewDist;
|
|
378
|
+
output.currClipPos = clipPos;
|
|
379
|
+
output.prevClipPos = prevClipPos;
|
|
380
|
+
output.instanceColor = instanceColor;
|
|
381
|
+
|
|
382
|
+
// For billboard sprites, use entity position Y for clip plane testing
|
|
383
|
+
// In planar reflections, the billboard camera vectors are flipped, which causes
|
|
384
|
+
// worldPos to be computed at wrong Y values. Using entityPos directly is simpler
|
|
385
|
+
// and more reliable - if the entity is above ground, the sprite shows.
|
|
386
|
+
if (uniforms.billboardMode > 0.5) {
|
|
387
|
+
let entityPos = instanceModelMatrix[3].xyz;
|
|
388
|
+
output.anchorY = entityPos.y;
|
|
389
|
+
} else {
|
|
390
|
+
output.anchorY = worldPos.y; // For regular meshes, use fragment world Y
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Apply instance transform to normal
|
|
394
|
+
// For billboard sprites, the normal is already in world space (0, 1, 0)
|
|
395
|
+
if (uniforms.billboardMode > 0.5) {
|
|
396
|
+
output.normal = localNormal; // Already world-space (0, 1, 0) for all billboard modes
|
|
397
|
+
} else {
|
|
398
|
+
let normalMatrix = mat3x3f(
|
|
399
|
+
instanceModelMatrix[0].xyz,
|
|
400
|
+
instanceModelMatrix[1].xyz,
|
|
401
|
+
instanceModelMatrix[2].xyz
|
|
402
|
+
);
|
|
403
|
+
output.normal = normalMatrix * localNormal;
|
|
404
|
+
}
|
|
405
|
+
return output;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
struct GBufferOutput {
|
|
409
|
+
@location(0) albedo: vec4f,
|
|
410
|
+
@location(1) normal: vec4f,
|
|
411
|
+
@location(2) arm: vec4f, // Ambient, Roughness, Metallic
|
|
412
|
+
@location(3) emission: vec4f,
|
|
413
|
+
@location(4) velocity: vec2f, // Motion vectors in pixels
|
|
414
|
+
@builtin(frag_depth) depth: f32, // Linear depth
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Interleaved Gradient Noise for alpha hashing
|
|
418
|
+
// From: http://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare
|
|
419
|
+
fn interleavedGradientNoise(screenPos: vec2f) -> f32 {
|
|
420
|
+
let magic = vec3f(0.06711056, 0.00583715, 52.9829189);
|
|
421
|
+
return fract(magic.z * fract(dot(screenPos, magic.xy)));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// Alternative: simple hash for variety
|
|
425
|
+
fn screenHash(screenPos: vec2f) -> f32 {
|
|
426
|
+
let p = fract(screenPos * vec2f(0.1031, 0.1030));
|
|
427
|
+
let p3 = p.xyx * (p.yxy + 33.33);
|
|
428
|
+
return fract((p3.x + p3.y) * p3.z);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
@fragment
|
|
432
|
+
fn fragmentMain(input: VertexOutput) -> GBufferOutput {
|
|
433
|
+
// Clip plane: discard fragments based on clip plane Y level and direction
|
|
434
|
+
// Used for planar reflections to avoid rendering geometry on the wrong side of the water
|
|
435
|
+
// Direction > 0: discard below clipPlaneY (camera above water, show above-water objects)
|
|
436
|
+
// Direction < 0: discard above clipPlaneY (camera below water, show below-water objects)
|
|
437
|
+
// For billboard sprites (anchorY != worldPos.y), skip clip plane - entity placement handles it
|
|
438
|
+
if (uniforms.clipPlaneEnabled > 0.5 && uniforms.billboardMode < 0.5) {
|
|
439
|
+
let clipDist = (input.worldPos.y - uniforms.clipPlaneY) * uniforms.clipPlaneDirection;
|
|
440
|
+
if (clipDist < 0.0) {
|
|
441
|
+
discard;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Distance fade: noise-based dithered fade to prevent popping at culling distance
|
|
446
|
+
// Fade from fully visible at distanceFadeStart to invisible at distanceFadeEnd
|
|
447
|
+
if (uniforms.distanceFadeEnd > 0.0) {
|
|
448
|
+
let distToCamera = length(input.worldPos - uniforms.cameraPosition);
|
|
449
|
+
if (distToCamera >= uniforms.distanceFadeEnd) {
|
|
450
|
+
discard; // Beyond fade end - fully invisible
|
|
451
|
+
}
|
|
452
|
+
if (distToCamera > uniforms.distanceFadeStart) {
|
|
453
|
+
// Calculate fade factor: 1.0 at fadeStart, 0.0 at fadeEnd
|
|
454
|
+
let fadeRange = uniforms.distanceFadeEnd - uniforms.distanceFadeStart;
|
|
455
|
+
let fadeFactor = 1.0 - (distToCamera - uniforms.distanceFadeStart) / fadeRange;
|
|
456
|
+
// Use noise-based dithering for smooth fade
|
|
457
|
+
let noise = sampleNoise(input.position.xy);
|
|
458
|
+
if (fadeFactor < noise) {
|
|
459
|
+
discard; // Dithered fade out
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
var output: GBufferOutput;
|
|
465
|
+
|
|
466
|
+
// Write albedo with mip offset
|
|
467
|
+
let mipBias = uniforms.mipBias; // Offset mip level
|
|
468
|
+
output.albedo = textureSampleBias(albedoTexture, albedoSampler, input.uv, mipBias);
|
|
469
|
+
|
|
470
|
+
// Apply instance color tint (for sprites)
|
|
471
|
+
output.albedo = output.albedo * input.instanceColor;
|
|
472
|
+
|
|
473
|
+
// Luminance to alpha: derive alpha from base color brightness (for old game assets where black=transparent)
|
|
474
|
+
// Only pure black (luminance < 1/255) becomes transparent - HARD discard, no noise
|
|
475
|
+
if (uniforms.luminanceToAlpha > 0.5) {
|
|
476
|
+
let luminance = dot(output.albedo.rgb, vec3f(0.299, 0.587, 0.114));
|
|
477
|
+
if (luminance < 0.004) {
|
|
478
|
+
discard; // Hard discard for pure black - no noise dithering
|
|
479
|
+
}
|
|
480
|
+
output.albedo.a = 1.0; // Everything else is fully opaque
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Alpha hashing: screen-space dithered alpha test (only for non-luminanceToAlpha materials)
|
|
484
|
+
// Uses noise texture (blue noise or bayer) for stable, temporally coherent cutout
|
|
485
|
+
if (uniforms.alphaHashEnabled > 0.5 && uniforms.luminanceToAlpha < 0.5) {
|
|
486
|
+
let alpha = output.albedo.a * uniforms.alphaHashScale;
|
|
487
|
+
let noise = sampleNoise(input.position.xy);
|
|
488
|
+
|
|
489
|
+
// Hard cutoff for very transparent areas (avoid random dots)
|
|
490
|
+
if (alpha < 0.5) {
|
|
491
|
+
discard;
|
|
492
|
+
}
|
|
493
|
+
// Hash only the semi-transparent edge (0.5 to 1.0 range)
|
|
494
|
+
// Remap alpha from [0.5, 1.0] to [0.0, 1.0] for noise comparison
|
|
495
|
+
let remappedAlpha = (alpha - 0.5) * 2.0;
|
|
496
|
+
if (remappedAlpha < noise) {
|
|
497
|
+
discard;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
// Write world-space normal
|
|
503
|
+
|
|
504
|
+
// Sample normal map and convert from [0,1] to [-1,1] range
|
|
505
|
+
let nsample = textureSampleBias(normalTexture, normalSampler, input.uv, mipBias).rgb;
|
|
506
|
+
let tangentNormal = normalize(nsample * 2.0 - 1.0);
|
|
507
|
+
|
|
508
|
+
// Create TBN matrix using world normal
|
|
509
|
+
let N = normalize(input.normal);
|
|
510
|
+
// Use different reference vector when N is close to up/down to avoid zero cross product
|
|
511
|
+
let refVec = select(vec3f(0.0, 1.0, 0.0), vec3f(1.0, 0.0, 0.0), abs(N.y) > 0.9);
|
|
512
|
+
let T = normalize(cross(N, refVec));
|
|
513
|
+
let B = cross(N, T);
|
|
514
|
+
let TBN = mat3x3f(T, B, N);
|
|
515
|
+
|
|
516
|
+
// Transform tangent space normal to world space
|
|
517
|
+
let normal = normalize(TBN * tangentNormal);
|
|
518
|
+
// Store world Y position in .w for planar reflection distance fade
|
|
519
|
+
output.normal = vec4f(normal, input.worldPos.y);
|
|
520
|
+
|
|
521
|
+
// Write ARM values (Ambient, Roughness, Metallic, SpecularBoost)
|
|
522
|
+
var ambient = textureSampleBias(ambientTexture, ambientSampler, input.uv, mipBias).rgb;
|
|
523
|
+
if (ambient.r < 0.04) {
|
|
524
|
+
ambient.r = 1.0;
|
|
525
|
+
}
|
|
526
|
+
let rm = textureSampleBias(rmTexture, rmSampler, input.uv, mipBias).rgb;
|
|
527
|
+
output.arm = vec4f(ambient.r, rm.g, rm.b, uniforms.specularBoost);
|
|
528
|
+
|
|
529
|
+
// Write emission
|
|
530
|
+
output.emission = textureSampleBias(emissionTexture, emissionSampler, input.uv, mipBias) * uniforms.emissionFactor;
|
|
531
|
+
|
|
532
|
+
// Compute motion vectors (velocity) in pixels
|
|
533
|
+
// Convert clip positions to NDC
|
|
534
|
+
let currNDC = input.currClipPos.xy / input.currClipPos.w;
|
|
535
|
+
let prevNDC = input.prevClipPos.xy / input.prevClipPos.w;
|
|
536
|
+
|
|
537
|
+
// Convert NDC difference to pixel velocity
|
|
538
|
+
// NDC is -1 to 1, screen is 0 to screenSize
|
|
539
|
+
// velocity = (currNDC - prevNDC) * screenSize / 2
|
|
540
|
+
let velocityNDC = currNDC - prevNDC;
|
|
541
|
+
output.velocity = velocityNDC * uniforms.screenSize * 0.5;
|
|
542
|
+
|
|
543
|
+
// Linear depth: maps [near, far] to [0, 1]
|
|
544
|
+
let near = uniforms.near;
|
|
545
|
+
let far = uniforms.far;
|
|
546
|
+
let z = input.viewZ;
|
|
547
|
+
output.depth = (z - near) / (far - near);
|
|
548
|
+
|
|
549
|
+
return output;
|
|
550
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// HiZ Reduce Compute Shader
|
|
2
|
+
// Reduces the depth buffer to MIN and MAX depth per 64x64 tile for occlusion culling
|
|
3
|
+
// MIN depth = closest geometry in tile (occluder surface)
|
|
4
|
+
// MAX depth = farthest geometry in tile (if < 1.0, tile is fully covered)
|
|
5
|
+
// Tile thickness = MAX - MIN (thin for walls, thick for angled ground)
|
|
6
|
+
|
|
7
|
+
struct Uniforms {
|
|
8
|
+
screenWidth: f32,
|
|
9
|
+
screenHeight: f32,
|
|
10
|
+
tileCountX: f32,
|
|
11
|
+
tileCountY: f32,
|
|
12
|
+
tileSize: f32,
|
|
13
|
+
near: f32,
|
|
14
|
+
far: f32,
|
|
15
|
+
clearValue: f32, // 1.0 to clear, 0.0 to accumulate
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@group(0) @binding(0) var<uniform> uniforms: Uniforms;
|
|
19
|
+
@group(0) @binding(1) var depthTexture: texture_depth_2d;
|
|
20
|
+
@group(0) @binding(2) var<storage, read_write> hizBuffer: array<f32>;
|
|
21
|
+
|
|
22
|
+
// Workgroup size: 8x8 threads, each thread processes 8x8 pixels = 64x64 per workgroup
|
|
23
|
+
var<workgroup> sharedMinDepth: array<f32, 64>; // 8x8 = 64 threads
|
|
24
|
+
var<workgroup> sharedMaxDepth: array<f32, 64>;
|
|
25
|
+
|
|
26
|
+
@compute @workgroup_size(8, 8, 1)
|
|
27
|
+
fn main(
|
|
28
|
+
@builtin(global_invocation_id) globalId: vec3u,
|
|
29
|
+
@builtin(local_invocation_id) localId: vec3u,
|
|
30
|
+
@builtin(workgroup_id) workgroupId: vec3u
|
|
31
|
+
) {
|
|
32
|
+
let tileX = workgroupId.x;
|
|
33
|
+
let tileY = workgroupId.y;
|
|
34
|
+
let tileIndex = tileY * u32(uniforms.tileCountX) + tileX;
|
|
35
|
+
|
|
36
|
+
// Check if this tile is within bounds
|
|
37
|
+
if (tileX >= u32(uniforms.tileCountX) || tileY >= u32(uniforms.tileCountY)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Each thread processes an 8x8 block within the 64x64 tile
|
|
42
|
+
let localIndex = localId.y * 8u + localId.x;
|
|
43
|
+
let blockStartX = tileX * 64u + localId.x * 8u;
|
|
44
|
+
let blockStartY = tileY * 64u + localId.y * 8u;
|
|
45
|
+
|
|
46
|
+
// Find MIN and MAX depth in this thread's 8x8 block
|
|
47
|
+
var minDepth: f32 = 1.0; // Start at far plane
|
|
48
|
+
var maxDepth: f32 = 0.0; // Start at near plane
|
|
49
|
+
|
|
50
|
+
for (var y = 0u; y < 8u; y = y + 1u) {
|
|
51
|
+
for (var x = 0u; x < 8u; x = x + 1u) {
|
|
52
|
+
let pixelX = blockStartX + x;
|
|
53
|
+
let pixelY = blockStartY + y;
|
|
54
|
+
|
|
55
|
+
// Skip pixels outside screen bounds
|
|
56
|
+
if (pixelX < u32(uniforms.screenWidth) && pixelY < u32(uniforms.screenHeight)) {
|
|
57
|
+
let depth = textureLoad(depthTexture, vec2u(pixelX, pixelY), 0);
|
|
58
|
+
minDepth = min(minDepth, depth);
|
|
59
|
+
maxDepth = max(maxDepth, depth);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Store in shared memory
|
|
65
|
+
sharedMinDepth[localIndex] = minDepth;
|
|
66
|
+
sharedMaxDepth[localIndex] = maxDepth;
|
|
67
|
+
workgroupBarrier();
|
|
68
|
+
|
|
69
|
+
// Parallel reduction in shared memory
|
|
70
|
+
// Step 1: 64 -> 32
|
|
71
|
+
if (localIndex < 32u) {
|
|
72
|
+
sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 32u]);
|
|
73
|
+
sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 32u]);
|
|
74
|
+
}
|
|
75
|
+
workgroupBarrier();
|
|
76
|
+
|
|
77
|
+
// Step 2: 32 -> 16
|
|
78
|
+
if (localIndex < 16u) {
|
|
79
|
+
sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 16u]);
|
|
80
|
+
sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 16u]);
|
|
81
|
+
}
|
|
82
|
+
workgroupBarrier();
|
|
83
|
+
|
|
84
|
+
// Step 3: 16 -> 8
|
|
85
|
+
if (localIndex < 8u) {
|
|
86
|
+
sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 8u]);
|
|
87
|
+
sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 8u]);
|
|
88
|
+
}
|
|
89
|
+
workgroupBarrier();
|
|
90
|
+
|
|
91
|
+
// Step 4: 8 -> 4
|
|
92
|
+
if (localIndex < 4u) {
|
|
93
|
+
sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 4u]);
|
|
94
|
+
sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 4u]);
|
|
95
|
+
}
|
|
96
|
+
workgroupBarrier();
|
|
97
|
+
|
|
98
|
+
// Step 5: 4 -> 2
|
|
99
|
+
if (localIndex < 2u) {
|
|
100
|
+
sharedMinDepth[localIndex] = min(sharedMinDepth[localIndex], sharedMinDepth[localIndex + 2u]);
|
|
101
|
+
sharedMaxDepth[localIndex] = max(sharedMaxDepth[localIndex], sharedMaxDepth[localIndex + 2u]);
|
|
102
|
+
}
|
|
103
|
+
workgroupBarrier();
|
|
104
|
+
|
|
105
|
+
// Step 6: 2 -> 1 (final result)
|
|
106
|
+
if (localIndex == 0u) {
|
|
107
|
+
let finalMinDepth = min(sharedMinDepth[0], sharedMinDepth[1]);
|
|
108
|
+
let finalMaxDepth = max(sharedMaxDepth[0], sharedMaxDepth[1]);
|
|
109
|
+
|
|
110
|
+
// Store both min and max depth (2 floats per tile, interleaved)
|
|
111
|
+
hizBuffer[tileIndex * 2u] = finalMinDepth;
|
|
112
|
+
hizBuffer[tileIndex * 2u + 1u] = finalMaxDepth;
|
|
113
|
+
}
|
|
114
|
+
}
|