Skip to content

Commit 90716b9

Browse files
committed
- improving pbr example
1 parent 6b90351 commit 90716b9

10 files changed

Lines changed: 358 additions & 161 deletions

File tree

config.jsn

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@
8989
]
9090
}
9191

92+
// pack roughness (R) + metallic (G) into a single *_roughness_metallic.png in src dir
93+
pack_textures: {
94+
explicit: true
95+
type: shell
96+
commands: [
97+
"py -3 hotline-data/scripts/pack_pbr_textures.py ${src_data_dir}/textures ${src_data_dir}/textures"
98+
]
99+
}
100+
92101
// convert png, jpg, tga, gif to dds and pack cubemaps / arrays
93102
texturec: {
94103
args: [

hotline-data

Submodule hotline-data updated 32 files

plugins/ecs/src/lib.rs

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -430,7 +430,10 @@ impl BevyPlugin {
430430
client.swap_chain.wait_for_last_frame();
431431
self.unload(client);
432432
client.pmfx.unload_views();
433-
self.setup(client)
433+
self.setup(client);
434+
// spawn MainCamera immediately so ui() can find it before update() runs
435+
// (update_plugins calls ui() before update() each frame)
436+
self.spawn_main_camera();
434437
}
435438

436439
/// Custom function to handle custome data change events which can trigger resetup
@@ -530,6 +533,17 @@ impl BevyPlugin {
530533
}
531534
}
532535

536+
fn spawn_main_camera(&mut self) {
537+
let (cam, vp, pos) = self.setup_camera();
538+
self.world.spawn((
539+
ViewProjectionMatrix(vp),
540+
pos,
541+
cam,
542+
MainCamera,
543+
Name(String::from("main_camera"))
544+
));
545+
}
546+
533547
fn setup_camera(&mut self) -> (Camera, Mat4f, Position) {
534548
// use a default demo camera, if we have no main camera (mainly for test runners)
535549
if let Some(default_cameras) = &self.session_info.default_cameras {
@@ -694,14 +708,10 @@ impl Plugin<gfx_platform::Device, os_platform::App> for BevyPlugin {
694708

695709
// run setup if requested, we did it here so hotline resources are inserted into World
696710
if self.run_setup {
697-
let (cam, vp, pos) = self.setup_camera();
698-
self.world.spawn((
699-
ViewProjectionMatrix(vp),
700-
pos,
701-
cam,
702-
MainCamera,
703-
Name(String::from("main_camera"))
704-
));
711+
// only spawn if resetup() hasn't already spawned it (ui() runs before update())
712+
if self.world.query_filtered::<Entity, With<MainCamera>>().iter(&self.world).next().is_none() {
713+
self.spawn_main_camera();
714+
}
705715

706716
self.setup_schedule.run(&mut self.world);
707717
self.run_setup = false;
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
///
2+
/// Bindless Material IBL
3+
///
4+
/// 10 PBR materials × 4 mesh types arranged in a curated 10×4 grid,
5+
/// lit by image-based lighting (cubemap + BRDF LUT) instead of point lights.
6+
///
7+
8+
use crate::prelude::*;
9+
10+
#[derive(Resource)]
11+
pub struct IblData {
12+
cubemap_srv: u32,
13+
lut_srv: u32,
14+
}
15+
16+
#[no_mangle]
17+
pub fn bindless_material_ibl(client: &mut Client<gfx_platform::Device, os_platform::App>) -> ScheduleInfo {
18+
client.pmfx.load(hotline_rs::get_data_path("shaders/util").as_str()).unwrap();
19+
client.pmfx.load(hotline_rs::get_data_path("shaders/ecs_examples").as_str()).unwrap();
20+
ScheduleInfo {
21+
setup: systems![
22+
"setup_bindless_material_ibl"
23+
],
24+
update: systems![
25+
"batch_material_instances",
26+
"batch_bindless_draw_data"
27+
],
28+
render_graph: "mesh_instanced_bindless_material_ibl",
29+
..Default::default()
30+
}
31+
}
32+
33+
#[export_update_fn]
34+
pub fn setup_bindless_material_ibl(
35+
mut device: ResMut<DeviceRes>,
36+
mut pmfx: ResMut<PmfxRes>,
37+
mut commands: Commands) -> Result<(), hotline_rs::Error> {
38+
39+
let mesh = hotline_rs::primitives::create_teapot_mesh(&mut device.0, 8);
40+
41+
let materials = vec![
42+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/copper-scuffed"))?,
43+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/angled-tiled-floor"))?,
44+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/used-stainless-steel"))?,
45+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/office-carpet-fabric1"))?,
46+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/antique-grate1"))?,
47+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/green-ceramic-tiles"))?,
48+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/dirty-padded-leather"))?,
49+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/rusting-lined-metal2"))?,
50+
load_material(&mut device, &mut pmfx.0, &hotline_rs::get_data_path("textures/pbr/worn-painted-cement"))?,
51+
];
52+
53+
// load IBL textures
54+
let cubemap_filepath = hotline_rs::get_data_path("textures/cubemap.dds");
55+
let cubemap = hotline_rs::image::load_texture_from_file(
56+
&mut device.0, &cubemap_filepath, Some(&mut pmfx.shader_heap)).unwrap();
57+
58+
let lut_filepath = hotline_rs::get_data_path("textures/luts/ibl_brdf_lut.dds");
59+
let lut = hotline_rs::image::load_texture_from_file(
60+
&mut device.0, &lut_filepath, Some(&mut pmfx.shader_heap)).unwrap();
61+
62+
let cubemap_srv = cubemap.get_srv_index().unwrap() as u32;
63+
let lut_srv = lut.get_srv_index().unwrap() as u32;
64+
65+
commands.spawn(TextureComponent(cubemap));
66+
commands.spawn(TextureComponent(lut));
67+
68+
// grid layout: 3x3 columns
69+
let num_materials = materials.len();
70+
let spacing = 40.0_f32;
71+
let size = 10.0_f32;
72+
73+
let mut entity_itr: u32 = 0;
74+
75+
let instance_count = num_materials as u32;
76+
let parent = commands.spawn(InstanceBatch {
77+
mesh: MeshComponent(mesh.clone()),
78+
pipeline: PipelineComponent(String::new()),
79+
instance_buffer: InstanceBuffer {
80+
buffer: device.create_buffer(&gfx::BufferInfo {
81+
usage: gfx::BufferUsage::VERTEX,
82+
cpu_access: gfx::CpuAccessFlags::WRITE,
83+
format: gfx::Format::Unknown,
84+
stride: std::mem::size_of::<Vec4u>(),
85+
num_elements: instance_count as usize,
86+
initial_state: gfx::ResourceState::VertexConstantBuffer,
87+
}, hotline_rs::data![]).unwrap(),
88+
instance_count,
89+
heap: None,
90+
},
91+
}).id();
92+
93+
let num_cols = (num_materials as f32).sqrt().round() as usize;
94+
let num_rows = (num_materials + num_cols - 1) / num_cols;
95+
96+
let cell = spacing;
97+
let x_offset = (num_cols as f32 - 1.0) * cell * 0.5;
98+
let z_offset = (num_rows as f32 - 1.0) * cell * 0.5;
99+
100+
for i in 0..num_materials {
101+
102+
let col = i % num_cols;
103+
let row = i / num_cols;
104+
105+
let x = -x_offset + col as f32 * cell;
106+
let z = -z_offset + row as f32 * cell;
107+
108+
let pos = vec3f(x, 0.0, z);
109+
110+
commands.spawn((
111+
Instance {
112+
pos: Position(pos),
113+
rot: Rotation(Quatf::identity()),
114+
scale: Scale(splat3f(size)),
115+
world_matrix: WorldMatrix(Mat34f::identity()),
116+
parent: Parent(parent),
117+
},
118+
InstanceIds {
119+
entity_id: entity_itr,
120+
material_id: i as u32,
121+
},
122+
Extents {
123+
aabb_min: mesh.aabb_min,
124+
aabb_max: mesh.aabb_max,
125+
},
126+
));
127+
128+
entity_itr += 1;
129+
}
130+
131+
// write material data to world buffers
132+
let mut material_data = Vec::new();
133+
for material in &materials {
134+
material_data.push(MaterialData {
135+
albedo_id: material.albedo.get_srv_index().unwrap() as u32,
136+
normal_id: material.normal.get_srv_index().unwrap() as u32,
137+
roughness_id: material.roughness.get_srv_index().unwrap() as u32,
138+
padding: 0,
139+
});
140+
}
141+
142+
pmfx.reserve_world_buffers(&mut device, WorldBufferReserveInfo {
143+
draw_capacity: entity_itr as usize,
144+
extent_capacity: entity_itr as usize,
145+
material_capacity: materials.len(),
146+
..Default::default()
147+
});
148+
149+
pmfx.get_world_buffers_mut().material.write(0, &material_data);
150+
151+
for material in materials {
152+
commands.spawn(material);
153+
}
154+
155+
commands.insert_resource(IblData { cubemap_srv, lut_srv });
156+
157+
Ok(())
158+
}
159+
160+
#[export_render_fn]
161+
pub fn render_meshes_bindless_ibl(
162+
pmfx: &Res<PmfxRes>,
163+
view: &pmfx::View<gfx_platform::Device>,
164+
cmd_buf: &mut <gfx_platform::Device as Device>::CmdBuf,
165+
ibl_data: &Res<IblData>,
166+
queries: (
167+
Query<(&InstanceBuffer, &MeshComponent)>,
168+
Query<(&MeshComponent, &WorldMatrix), Without<InstanceBuffer>>
169+
)
170+
) -> Result<(), hotline_rs::Error> {
171+
172+
let (instance_draw_query, single_draw_query) = queries;
173+
174+
let pmfx = &pmfx;
175+
let fmt = view.pass.get_format_hash();
176+
let camera = pmfx.get_camera_constants(&view.camera)?;
177+
178+
let pipeline = pmfx.get_render_pipeline_for_format(&view.view_pipeline, fmt)?;
179+
cmd_buf.set_render_pipeline(&pipeline);
180+
181+
// bind view push constants
182+
let slot = pipeline.get_pipeline_slot(0, 0, gfx::DescriptorType::PushConstants);
183+
if let Some(slot) = slot {
184+
cmd_buf.push_render_constants(slot.index, 16, 0, gfx::as_u8_slice(&camera.view_projection_matrix));
185+
cmd_buf.push_render_constants(slot.index, 4, 16, gfx::as_u8_slice(&camera.view_position));
186+
}
187+
188+
// bind world buffer info with IBL indices in user_data
189+
let mut world_buffer_info = pmfx.get_world_buffer_info();
190+
world_buffer_info.user_data[0] = ibl_data.cubemap_srv;
191+
world_buffer_info.user_data[1] = ibl_data.lut_srv;
192+
let slot = pipeline.get_pipeline_slot(2, 0, gfx::DescriptorType::PushConstants);
193+
if let Some(slot) = slot {
194+
cmd_buf.push_render_constants(
195+
slot.index, gfx::num_32bit_constants(&world_buffer_info), 0, gfx::as_u8_slice(&world_buffer_info));
196+
}
197+
198+
// bind the shader resource heap
199+
cmd_buf.set_heap(pipeline, &pmfx.shader_heap);
200+
201+
// instance batch draw calls
202+
for (instance_batch, mesh) in &instance_draw_query {
203+
cmd_buf.set_index_buffer(&mesh.0.ib);
204+
cmd_buf.set_vertex_buffer(&mesh.0.vb, 0);
205+
cmd_buf.set_vertex_buffer(&instance_batch.buffer, 1);
206+
cmd_buf.draw_indexed_instanced(mesh.0.num_indices, instance_batch.instance_count, 0, 0, 0);
207+
}
208+
209+
// single draw calls
210+
for (mesh, world_matrix) in &single_draw_query {
211+
let slot = pipeline.get_pipeline_slot(1, 0, gfx::DescriptorType::PushConstants);
212+
if let Some(slot) = slot {
213+
cmd_buf.push_render_constants(slot.index, 12, 0, &world_matrix.0);
214+
}
215+
cmd_buf.set_index_buffer(&mesh.0.ib);
216+
cmd_buf.set_vertex_buffer(&mesh.0.vb, 0);
217+
cmd_buf.draw_indexed_instanced(mesh.0.num_indices, 1, 0, 0, 0);
218+
}
219+
220+
Ok(())
221+
}

plugins/ecs_examples/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ mod generate_mip_maps;
2828
mod shadow_map;
2929
mod omni_shadow_map;
3030
mod dynamic_cubemap;
31-
mod pbr;
31+
mod bindless_material_ibl;
3232
mod raytracing_pipeline;
3333
mod raytraced_shadows;
3434
mod claude;
@@ -43,7 +43,7 @@ pub fn load_material(
4343
let maps = vec![
4444
"_albedo.dds",
4545
"_normal.dds",
46-
"_roughness.dds"
46+
"_roughness_metallic.dds"
4747
];
4848

4949
let mut textures = Vec::new();
@@ -688,7 +688,7 @@ pub fn get_demos_ecs_examples() -> Vec<String> {
688688
"shadow_map",
689689
"dynamic_cubemap",
690690
"omni_shadow_map",
691-
"pbr",
691+
"bindless_material_ibl",
692692
"raytracing_pipeline",
693693
"raytraced_shadows",
694694
"claude"

0 commit comments

Comments
 (0)