struct Point { pos: vec3, _pad: f32, }; struct BillboardAxis { vector : vec3, _pad : f32, }; struct VSOut { @builtin(position) Position : vec4, @location(0) uv : vec2, @location(1) color : vec3, }; @group(0) @binding(0) var positions: array; @group(0) @binding(1) var sortedIndices: array; // New binding for sorted indices @group(0) @binding(2) var viewProjectionMatrix: mat4x4; @group(0) @binding(3) var cameraRight : BillboardAxis; @group(0) @binding(4) var cameraUp : BillboardAxis; @vertex fn vertexEntryPoint( @builtin(vertex_index) vertexIndex: u32, @builtin(instance_index) instanceIndex: u32, @location(0) quadOffset: vec2 ) -> VSOut { var output: VSOut; // Use the sorted index to get the actual particle index let actualIndex = sortedIndices[instanceIndex]; let point = positions[actualIndex]; let center = point.pos; let radius = 0.03; let rightOffset = cameraRight.vector * quadOffset.x * radius; let upOffset = cameraUp.vector * quadOffset.y * radius; let worldPos = vec4(center + rightOffset + upOffset, 1.0); output.Position = viewProjectionMatrix * worldPos; output.uv = quadOffset; output.color = (center + vec3(1.0, 1.0, 1.0)) * 0.5; return output; } @fragment fn fragmentEntryPoint( @location(0) uv: vec2, @location(1) color: vec3 ) -> @location(0) vec4 { let dist = length(uv); if (dist > 1.0) { discard; } let z = sqrt(1.0 - dist * dist); let normal = normalize(vec3(uv.x, uv.y, z)); let light = normalize(vec3(1.0, 1.0, 1.0)); let diffuse = max(dot(normal, light), 0.0); return vec4(color * diffuse, 1.0); }