feat: add IBL demo with normal mapping and procedural environment lighting

Fix pbr_demo, multi_light_demo, and shadow_demo to use the new 7-param
create_pbr_pipeline with PBR texture bind group (4-entry: albedo+normal)
and IBL bind group. Create ibl_demo showcasing a 7x7 metallic/roughness
sphere grid with IBL-based ambient lighting via BRDF LUT integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 21:34:06 +09:00
parent 5232552aa4
commit 9202bfadef
7 changed files with 668 additions and 21 deletions

View File

@@ -12,6 +12,7 @@ use voltex_renderer::{
Mesh, GpuTexture, MaterialUniform, generate_sphere, create_pbr_pipeline, obj,
ShadowMap, ShadowUniform, ShadowPassUniform, SHADOW_MAP_SIZE,
create_shadow_pipeline, shadow_pass_bind_group_layout,
IblResources, pbr_texture_bind_group_layout, create_pbr_texture_bind_group,
};
use wgpu::util::DeviceExt;
@@ -36,7 +37,9 @@ struct AppState {
light_buffer: wgpu::Buffer,
material_buffer: wgpu::Buffer,
camera_light_bind_group: wgpu::BindGroup,
_texture: GpuTexture,
_albedo_tex: GpuTexture,
_normal_tex: (wgpu::Texture, wgpu::TextureView, wgpu::Sampler),
pbr_texture_bind_group: wgpu::BindGroup,
material_bind_group: wgpu::BindGroup,
// Shadow resources
shadow_map: ShadowMap,
@@ -44,6 +47,8 @@ struct AppState {
shadow_bind_group: wgpu::BindGroup,
shadow_pass_buffer: wgpu::Buffer,
shadow_pass_bind_group: wgpu::BindGroup,
_ibl: IblResources,
ibl_bind_group: wgpu::BindGroup,
// Misc
input: InputState,
timer: GameTimer,
@@ -184,7 +189,7 @@ impl ApplicationHandler for ShadowDemoApp {
// Bind group layouts
let cl_layout = camera_light_bind_group_layout(&gpu.device);
let tex_layout = GpuTexture::bind_group_layout(&gpu.device);
let pbr_tex_layout = pbr_texture_bind_group_layout(&gpu.device);
let mat_layout = MaterialUniform::bind_group_layout(&gpu.device);
// Camera+Light bind group
@@ -207,8 +212,23 @@ impl ApplicationHandler for ShadowDemoApp {
],
});
// Texture (white 1x1)
let texture = GpuTexture::white_1x1(&gpu.device, &gpu.queue, &tex_layout);
// PBR texture bind group (albedo + normal)
let old_tex_layout = GpuTexture::bind_group_layout(&gpu.device);
let albedo_tex = GpuTexture::white_1x1(&gpu.device, &gpu.queue, &old_tex_layout);
let normal_tex = GpuTexture::flat_normal_1x1(&gpu.device, &gpu.queue);
let pbr_texture_bind_group = create_pbr_texture_bind_group(
&gpu.device,
&pbr_tex_layout,
&albedo_tex.view,
&albedo_tex.sampler,
&normal_tex.1,
&normal_tex.2,
);
// IBL resources
let ibl = IblResources::new(&gpu.device, &gpu.queue);
let ibl_layout = IblResources::bind_group_layout(&gpu.device);
let ibl_bind_group = ibl.create_bind_group(&gpu.device, &ibl_layout);
// Material bind group
let material_bind_group = gpu.device.create_bind_group(&wgpu::BindGroupDescriptor {
@@ -272,9 +292,10 @@ impl ApplicationHandler for ShadowDemoApp {
&gpu.device,
gpu.surface_format,
&cl_layout,
&tex_layout,
&pbr_tex_layout,
&mat_layout,
&shadow_layout,
&ibl_layout,
);
self.state = Some(AppState {
@@ -290,13 +311,17 @@ impl ApplicationHandler for ShadowDemoApp {
light_buffer,
material_buffer,
camera_light_bind_group,
_texture: texture,
_albedo_tex: albedo_tex,
_normal_tex: normal_tex,
pbr_texture_bind_group,
material_bind_group,
shadow_map,
shadow_uniform_buffer,
shadow_bind_group,
shadow_pass_buffer,
shadow_pass_bind_group,
_ibl: ibl,
ibl_bind_group,
input: InputState::new(),
timer: GameTimer::new(60),
cam_aligned_size,
@@ -557,8 +582,9 @@ impl ApplicationHandler for ShadowDemoApp {
});
render_pass.set_pipeline(&state.pbr_pipeline);
render_pass.set_bind_group(1, &state._texture.bind_group, &[]);
render_pass.set_bind_group(1, &state.pbr_texture_bind_group, &[]);
render_pass.set_bind_group(3, &state.shadow_bind_group, &[]);
render_pass.set_bind_group(4, &state.ibl_bind_group, &[]);
for i in 0..NUM_OBJECTS {
let cam_offset = (i as u32) * state.cam_aligned_size;