i am new to webgpu and trying to render some spheres using frgament shader spheres are defined as
struct Material {
category: u32,
attenuation: vec3f,
fuzz: f32,
refraction_index: f32,
}
struct Sphere {
center: vec3f,
radius: f32,
mat: Material
}
function createSpheresStorageBuffer2(spheres: Sphere[]): GPUBuffer {
const bufferElements: (Float32Array | Uint32Array)[] = [];
let size = 0;
let arrayBuffer;
for (const sphere of spheres) {
arrayBuffer = new Float32Array([...sphere.center, sphere.radius]);
size+=arrayBuffer.byteLength;
bufferElements.push(arrayBuffer);
const {type,attenuation,fuzz,refraction_index} = sphere.material;
arrayBuffer = new Uint32Array([type]);
size+=arrayBuffer.byteLength;
bufferElements.push(arrayBuffer);
arrayBuffer = new Float32Array([...attenuation,fuzz,refraction_index]);
size+=arrayBuffer.byteLength;
bufferElements.push(arrayBuffer);
}
const buffer = device.createBuffer({
label: "spheres buffer",
size: size,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
let offset = 0;
for(const buff of bufferElements){
device.queue.writeBuffer(buffer, offset, buff);
offset+=buff.byteLength
}
return buffer
}
enum MaterialType {
Labertian = 0,
Metal = 1,
}
const spheres: Sphere[] = [
{
center: [0, -100.5, -1],
radius: 100,
material: {
type: MaterialType.Labertian,
attenuation: [0.5,0.5,0.5],
fuzz: 1.0,
refraction_index: 1.0
}
},
{
center: [1, 0, -1],
radius: 0.5,
material: {
type: MaterialType.Metal,
attenuation: [0.5,0.5,0.5],
fuzz: 0,
refraction_index: 1.0
}
},
]
but the value of center of shpere 2 in shader is 0.5,0.5,1 which are the values from attenuation [2,3] + fuzz (which i can to know by trial and error)
the code works fine when the attenuation is f32 instead of vector
It doesn't appear that the way you are packing your data matches the memory layout from WebGPU
Here's the layout
The diagram generated from here
You might want to consider using a library to set storage and uniform buffers.
On the other hand, you might also want to consider making one arraybuffer and 2 views. It will be more efficient to upload one buffer than 3 per sphere you're uploading now.
Something along the lines of
const elemsPerSphere = 16;
const f32s = new Float32Array(elemsPerSphere * spheres.length);
// make a u32 view of the same memory
const u32s = new Uint32Array(f32s.buffer);
const kCenterOffset = 0;
const kRadiusOffset = 3;
const kCategoryOffset = 4;
const kAttenuationOffset = 8;
const kFuzzOffset = 11;
const kRefractionIndexOffset = 12;
let offset = 0;
for (const sphere of spheres) {
f32s.set([...sphere.center, sphere.radius], offset + kCenterOffset);
const {type,attenuation,fuzz,refraction_index} = sphere.material;
u32s[offset + kCategoryOffset] = type;
f32s.set([...attenuation,fuzz,refraction_index], offset + kAttenuationOffset);
offset += kElemsPerSphere;
}
// Upload all spheres at once
device.queue.writeBuffer(buffer, 0, f32s);
See this article for more info
Note: The method above has performance issues as it's creating temporary arrays for each call to set
. I don't know if there is an optimal solution. I'd be fine using the solution above until I found it was too slow for my needs. Then I might try something different. One example might be
let offset = 0;
for (const sphere of spheres) {
f32s.set(sphere.center, offset + kCenterOffset);
f32s[offset + kRadiusOffset] = sphere.radius
const {type,attenuation,fuzz,refraction_index} = sphere.material;
u32s[offset + kCategoryOffset] = type;
f32s.set(offset + kAttenuationOffset, attenuation);
f32s[offset + kFuzzOffset] = fuzz;
f32s[offset + kRefractionIndexOffset] = refaction_index;
offset += kElemsPerSphere;
}
This code at least is not allocating temporary arrays. You'd have to profile if it's faster.
disclosure: I contribute to the site that generated the diagram and the article and the library linked above and most likely the browser you're using when you asked this question