wgpu_examples/ray_scene/
mod.rs

1use crate::utils;
2use bytemuck::{Pod, Zeroable};
3use glam::{Mat4, Quat, Vec3};
4use std::f32::consts::PI;
5use std::ops::IndexMut;
6use std::{borrow::Cow, iter, mem, ops::Range};
7use wgpu::util::DeviceExt;
8
9// from cube
10#[repr(C)]
11#[derive(Debug, Clone, Copy, Pod, Zeroable, Default)]
12struct Vertex {
13    pos: [f32; 3],
14    _p0: [u32; 1],
15    normal: [f32; 3],
16    _p1: [u32; 1],
17    uv: [f32; 2],
18    _p2: [u32; 2],
19}
20
21#[repr(C)]
22#[derive(Clone, Copy, Pod, Zeroable)]
23struct Uniforms {
24    view_inverse: Mat4,
25    proj_inverse: Mat4,
26}
27
28#[derive(Debug, Clone, Default)]
29struct RawSceneComponents {
30    vertices: Vec<Vertex>,
31    indices: Vec<u32>,
32    geometries: Vec<(Range<usize>, Material)>, // index range, material
33    instances: Vec<(Range<usize>, Range<usize>)>, // vertex range, geometry range
34}
35
36struct SceneComponents {
37    vertices: wgpu::Buffer,
38    indices: wgpu::Buffer,
39    geometries: wgpu::Buffer,
40    instances: wgpu::Buffer,
41    bottom_level_acceleration_structures: Vec<wgpu::Blas>,
42}
43
44#[repr(C)]
45#[derive(Clone, Copy, Pod, Zeroable)]
46struct InstanceEntry {
47    first_vertex: u32,
48    first_geometry: u32,
49    last_geometry: u32,
50    _pad: u32,
51}
52
53#[repr(C)]
54#[derive(Clone, Copy, Pod, Zeroable, Default)]
55struct GeometryEntry {
56    first_index: u32,
57    _p0: [u32; 3],
58    material: Material,
59}
60
61#[repr(C)]
62#[derive(Clone, Copy, Pod, Zeroable, Default, Debug)]
63struct Material {
64    roughness_exponent: f32,
65    metalness: f32,
66    specularity: f32,
67    _p0: [u32; 1],
68    albedo: [f32; 3],
69    _p1: [u32; 1],
70}
71
72fn load_model(scene: &mut RawSceneComponents, path: &str) {
73    let path = env!("CARGO_MANIFEST_DIR").to_string() + "/src" + path;
74    println!("{path}");
75    let mut object = obj::Obj::load(path).unwrap();
76    object.load_mtls().unwrap();
77
78    let data = object.data;
79
80    let start_vertex_index = scene.vertices.len();
81    let start_geometry_index = scene.geometries.len();
82
83    let mut mapping = std::collections::HashMap::<(usize, Option<usize>, usize), usize>::new();
84
85    let mut next_index = 0;
86
87    for object in data.objects {
88        for group in object.groups {
89            let start_index_index = scene.indices.len();
90            for poly in group.polys {
91                for end_index in 2..poly.0.len() {
92                    for &index in &[0, end_index - 1, end_index] {
93                        let obj::IndexTuple(position_id, texture_id, normal_id) = poly.0[index];
94                        let uv = texture_id
95                            .map(|texture_id| data.texture[texture_id])
96                            .unwrap_or_default();
97                        let normal_id = normal_id.expect("normals required");
98
99                        let index = *mapping
100                            .entry((position_id, texture_id, normal_id))
101                            .or_insert(next_index);
102                        if index == next_index {
103                            next_index += 1;
104
105                            scene.vertices.push(Vertex {
106                                pos: data.position[position_id],
107                                uv,
108                                normal: data.normal[normal_id],
109                                ..Default::default()
110                            })
111                        }
112
113                        scene.indices.push(index as u32);
114                    }
115                }
116            }
117
118            let mut material: Material = Default::default();
119
120            if let Some(obj::ObjMaterial::Mtl(mat)) = group.material {
121                if let Some(kd) = mat.kd {
122                    material.albedo = kd;
123                }
124                if let Some(ns) = mat.ns {
125                    material.roughness_exponent = ns;
126                }
127                if let Some(ka) = mat.ka {
128                    material.metalness = ka[0];
129                }
130                if let Some(ks) = mat.ks {
131                    material.specularity = ks[0];
132                }
133            }
134
135            scene
136                .geometries
137                .push((start_index_index..scene.indices.len(), material));
138        }
139    }
140    scene.instances.push((
141        start_vertex_index..scene.vertices.len(),
142        start_geometry_index..scene.geometries.len(),
143    ));
144
145    // dbg!(scene.vertices.len());
146    // dbg!(scene.indices.len());
147    // dbg!(&scene.geometries);
148    // dbg!(&scene.instances);
149}
150
151fn upload_scene_components(
152    device: &wgpu::Device,
153    queue: &wgpu::Queue,
154    scene: &RawSceneComponents,
155) -> SceneComponents {
156    let geometry_buffer_content = scene
157        .geometries
158        .iter()
159        .map(|(index_range, material)| GeometryEntry {
160            first_index: index_range.start as u32,
161            material: *material,
162            ..Default::default()
163        })
164        .collect::<Vec<_>>();
165
166    let instance_buffer_content = scene
167        .instances
168        .iter()
169        .map(|geometry| InstanceEntry {
170            first_vertex: geometry.0.start as u32,
171            first_geometry: geometry.1.start as u32,
172            last_geometry: geometry.1.end as u32,
173            _pad: 1,
174        })
175        .collect::<Vec<_>>();
176
177    let vertices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
178        label: Some("Vertices"),
179        contents: bytemuck::cast_slice(&scene.vertices),
180        usage: wgpu::BufferUsages::VERTEX
181            | wgpu::BufferUsages::STORAGE
182            | wgpu::BufferUsages::BLAS_INPUT,
183    });
184    let indices = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
185        label: Some("Indices"),
186        contents: bytemuck::cast_slice(&scene.indices),
187        usage: wgpu::BufferUsages::INDEX
188            | wgpu::BufferUsages::STORAGE
189            | wgpu::BufferUsages::BLAS_INPUT,
190    });
191    let geometries = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
192        label: Some("Geometries"),
193        contents: bytemuck::cast_slice(&geometry_buffer_content),
194        usage: wgpu::BufferUsages::STORAGE,
195    });
196    let instances = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
197        label: Some("Instances"),
198        contents: bytemuck::cast_slice(&instance_buffer_content),
199        usage: wgpu::BufferUsages::STORAGE,
200    });
201
202    let (size_descriptors, bottom_level_acceleration_structures): (Vec<_>, Vec<_>) = scene
203        .instances
204        .iter()
205        .map(|(vertex_range, geometry_range)| {
206            let size_desc: Vec<wgpu::BlasTriangleGeometrySizeDescriptor> = (*geometry_range)
207                .clone()
208                .map(|i| wgpu::BlasTriangleGeometrySizeDescriptor {
209                    vertex_format: wgpu::VertexFormat::Float32x3,
210                    vertex_count: vertex_range.end as u32 - vertex_range.start as u32,
211                    index_format: Some(wgpu::IndexFormat::Uint32),
212                    index_count: Some(
213                        scene.geometries[i].0.end as u32 - scene.geometries[i].0.start as u32,
214                    ),
215                    flags: wgpu::AccelerationStructureGeometryFlags::OPAQUE,
216                })
217                .collect();
218
219            let blas = device.create_blas(
220                &wgpu::CreateBlasDescriptor {
221                    label: None,
222                    flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE,
223                    update_mode: wgpu::AccelerationStructureUpdateMode::Build,
224                },
225                wgpu::BlasGeometrySizeDescriptors::Triangles {
226                    descriptors: size_desc.clone(),
227                },
228            );
229            (size_desc, blas)
230        })
231        .unzip();
232
233    let build_entries: Vec<_> = scene
234        .instances
235        .iter()
236        .zip(size_descriptors.iter())
237        .zip(bottom_level_acceleration_structures.iter())
238        .map(|(((vertex_range, geometry_range), size_desc), blas)| {
239            let triangle_geometries: Vec<_> = size_desc
240                .iter()
241                .zip(geometry_range.clone())
242                .map(|(size, i)| wgpu::BlasTriangleGeometry {
243                    size,
244                    vertex_buffer: &vertices,
245                    first_vertex: vertex_range.start as u32,
246                    vertex_stride: mem::size_of::<Vertex>() as u64,
247                    index_buffer: Some(&indices),
248                    first_index: Some(scene.geometries[i].0.start as u32),
249                    transform_buffer: None,
250                    transform_buffer_offset: None,
251                })
252                .collect();
253
254            wgpu::BlasBuildEntry {
255                blas,
256                geometry: wgpu::BlasGeometries::TriangleGeometries(triangle_geometries),
257            }
258        })
259        .collect();
260
261    let mut encoder =
262        device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
263
264    encoder.build_acceleration_structures(build_entries.iter(), iter::empty());
265
266    queue.submit(Some(encoder.finish()));
267
268    SceneComponents {
269        vertices,
270        indices,
271        geometries,
272        instances,
273        bottom_level_acceleration_structures,
274    }
275}
276
277fn load_scene(device: &wgpu::Device, queue: &wgpu::Queue) -> SceneComponents {
278    let mut scene = RawSceneComponents::default();
279
280    load_model(&mut scene, "/skybox/models/rustacean-3d.obj");
281    load_model(&mut scene, "/ray_scene/cube.obj");
282
283    upload_scene_components(device, queue, &scene)
284}
285
286struct Example {
287    uniforms: Uniforms,
288    uniform_buf: wgpu::Buffer,
289    tlas: wgpu::Tlas,
290    pipeline: wgpu::RenderPipeline,
291    bind_group: wgpu::BindGroup,
292    scene_components: SceneComponents,
293    animation_timer: utils::AnimationTimer,
294}
295
296impl crate::framework::Example for Example {
297    fn required_features() -> wgpu::Features {
298        wgpu::Features::EXPERIMENTAL_RAY_QUERY
299    }
300
301    fn required_downlevel_capabilities() -> wgpu::DownlevelCapabilities {
302        wgpu::DownlevelCapabilities {
303            flags: wgpu::DownlevelFlags::COMPUTE_SHADERS,
304            ..Default::default()
305        }
306    }
307
308    fn required_limits() -> wgpu::Limits {
309        wgpu::Limits::default().using_minimum_supported_acceleration_structure_values()
310    }
311
312    fn init(
313        config: &wgpu::SurfaceConfiguration,
314        _adapter: &wgpu::Adapter,
315        device: &wgpu::Device,
316        queue: &wgpu::Queue,
317    ) -> Self {
318        let side_count = 8;
319
320        let scene_components = load_scene(device, queue);
321
322        let uniforms = {
323            let view = Mat4::look_at_rh(Vec3::new(0.0, 0.0, 2.5), Vec3::ZERO, Vec3::Y);
324            let proj = Mat4::perspective_rh(
325                59.0_f32.to_radians(),
326                config.width as f32 / config.height as f32,
327                0.001,
328                1000.0,
329            );
330
331            Uniforms {
332                view_inverse: view.inverse(),
333                proj_inverse: proj.inverse(),
334            }
335        };
336
337        let uniform_buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
338            label: Some("Uniform Buffer"),
339            contents: bytemuck::cast_slice(&[uniforms]),
340            usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
341        });
342
343        let tlas = device.create_tlas(&wgpu::CreateTlasDescriptor {
344            label: None,
345            flags: wgpu::AccelerationStructureFlags::PREFER_FAST_TRACE,
346            update_mode: wgpu::AccelerationStructureUpdateMode::Build,
347            max_instances: side_count * side_count,
348        });
349
350        let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
351            label: None,
352            source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("shader.wgsl"))),
353        });
354
355        let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
356            label: None,
357            layout: None,
358            vertex: wgpu::VertexState {
359                module: &shader,
360                entry_point: Some("vs_main"),
361                compilation_options: Default::default(),
362                buffers: &[],
363            },
364            fragment: Some(wgpu::FragmentState {
365                module: &shader,
366                entry_point: Some("fs_main"),
367                compilation_options: Default::default(),
368                targets: &[Some(config.format.into())],
369            }),
370            primitive: wgpu::PrimitiveState {
371                topology: wgpu::PrimitiveTopology::TriangleList,
372                ..Default::default()
373            },
374            depth_stencil: None,
375            multisample: wgpu::MultisampleState::default(),
376            multiview_mask: None,
377            cache: None,
378        });
379
380        let bind_group_layout = pipeline.get_bind_group_layout(0);
381
382        let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
383            label: None,
384            layout: &bind_group_layout,
385            entries: &[
386                wgpu::BindGroupEntry {
387                    binding: 0,
388                    resource: uniform_buf.as_entire_binding(),
389                },
390                wgpu::BindGroupEntry {
391                    binding: 5,
392                    resource: tlas.as_binding(),
393                },
394                wgpu::BindGroupEntry {
395                    binding: 1,
396                    resource: scene_components.vertices.as_entire_binding(),
397                },
398                wgpu::BindGroupEntry {
399                    binding: 2,
400                    resource: scene_components.indices.as_entire_binding(),
401                },
402                wgpu::BindGroupEntry {
403                    binding: 3,
404                    resource: scene_components.geometries.as_entire_binding(),
405                },
406                wgpu::BindGroupEntry {
407                    binding: 4,
408                    resource: scene_components.instances.as_entire_binding(),
409                },
410            ],
411        });
412
413        Example {
414            uniforms,
415            uniform_buf,
416            tlas,
417            pipeline,
418            bind_group,
419            scene_components,
420            animation_timer: utils::AnimationTimer::default(),
421        }
422    }
423
424    fn update(&mut self, _event: winit::event::WindowEvent) {}
425
426    fn resize(
427        &mut self,
428        config: &wgpu::SurfaceConfiguration,
429        _device: &wgpu::Device,
430        queue: &wgpu::Queue,
431    ) {
432        let proj = Mat4::perspective_rh(
433            59.0_f32.to_radians(),
434            config.width as f32 / config.height as f32,
435            0.001,
436            1000.0,
437        );
438
439        self.uniforms.proj_inverse = proj.inverse();
440
441        queue.write_buffer(&self.uniform_buf, 0, bytemuck::cast_slice(&[self.uniforms]));
442    }
443
444    fn render(&mut self, view: &wgpu::TextureView, device: &wgpu::Device, queue: &wgpu::Queue) {
445        // scene update
446        {
447            let dist = 3.5;
448
449            let side_count = 2;
450
451            let anim_time = self.animation_timer.time();
452
453            for x in 0..side_count {
454                for y in 0..side_count {
455                    let instance = self.tlas.index_mut(x + y * side_count);
456
457                    let blas_index = (x + y)
458                        % self
459                            .scene_components
460                            .bottom_level_acceleration_structures
461                            .len();
462
463                    let x = x as f32 / (side_count - 1) as f32;
464                    let y = y as f32 / (side_count - 1) as f32;
465                    let x = x * 2.0 - 1.0;
466                    let y = y * 2.0 - 1.0;
467
468                    let transform = Mat4::from_rotation_translation(
469                        Quat::from_euler(
470                            glam::EulerRot::XYZ,
471                            anim_time * 0.5 * 0.342,
472                            anim_time * 0.5 * 0.254,
473                            anim_time * 0.5 * 0.832 + PI,
474                        ),
475                        Vec3 {
476                            x: x * dist,
477                            y: y * dist,
478                            z: -14.0,
479                        },
480                    );
481                    let transform = transform.transpose().to_cols_array()[..12]
482                        .try_into()
483                        .unwrap();
484                    *instance = Some(wgpu::TlasInstance::new(
485                        &self.scene_components.bottom_level_acceleration_structures[blas_index],
486                        transform,
487                        blas_index as u32,
488                        0xff,
489                    ));
490                }
491            }
492        }
493
494        let mut encoder =
495            device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
496
497        encoder.build_acceleration_structures(iter::empty(), iter::once(&self.tlas));
498
499        {
500            let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
501                label: None,
502                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
503                    view,
504                    depth_slice: None,
505                    resolve_target: None,
506                    ops: wgpu::Operations {
507                        load: wgpu::LoadOp::Clear(wgpu::Color::GREEN),
508                        store: wgpu::StoreOp::Store,
509                    },
510                })],
511                depth_stencil_attachment: None,
512                timestamp_writes: None,
513                occlusion_query_set: None,
514                multiview_mask: None,
515            });
516
517            rpass.set_pipeline(&self.pipeline);
518            rpass.set_bind_group(0, Some(&self.bind_group), &[]);
519            rpass.draw(0..3, 0..1);
520        }
521
522        queue.submit(Some(encoder.finish()));
523    }
524}
525
526pub fn main() {
527    crate::framework::run::<Example>("ray_scene");
528}
529
530#[cfg(test)]
531#[wgpu_test::gpu_test]
532pub static TEST: crate::framework::ExampleTestParams = crate::framework::ExampleTestParams {
533    name: "ray_scene",
534    image_path: "/examples/features/src/ray_scene/screenshot.png",
535    width: 1024,
536    height: 768,
537    optional_features: wgpu::Features::default(),
538    base_test_parameters: wgpu_test::TestParameters::default()
539        // https://github.com/gfx-rs/wgpu/issues/9100
540        .expect_fail(wgpu_test::FailureCase::backend(wgpu::Backends::METAL)),
541    comparisons: &[wgpu_test::ComparisonType::Mean(0.02)],
542    _phantom: std::marker::PhantomData::<Example>,
543};