wde_pbr/deferred/pbr_material.rs
1use bevy::{
2 ecs::system::{SystemParamItem, lifetimeless::SRes},
3 prelude::*
4};
5use wde_renderer::prelude::*;
6
7use crate::prelude::PbrBatchesMarker;
8
9/// Marker component to indicate that an entity's transform should be included in the [PbrSsboTransform] updates.
10/// This automatically adds the [PbrBatchesMarker] (and thus the [PbrSsboTransformMarker]) to the entity.
11#[derive(Component, Reflect, Clone)]
12#[reflect(Component)]
13#[require(PbrBatchesMarker)]
14pub struct PbrMaterial3d<T: RenderBinding + Asset>(pub Handle<T>);
15
16/// Uniform structure for PBR material data.
17///
18/// The structure sent to the GPU is the following :
19/// ```wgsl
20/// struct Material3dUniform {
21/// flags: vec4<f32>, // Flags indicating material textures (1.0 = present, 0.0 = absent) - albedo, metallic-roughness, normal, occlusion
22/// albedo: vec4<f32>, // Albedo color of the material (r, g, b)
23/// metallic: f32, // Metallic intensity of the material
24/// roughness: f32, // Roughness intensity of the material
25/// _padding: vec2<f32>, // Unused padding to align to 16 bytes
26/// };
27///
28/// @group(2) @binding(0) var<uniform> pbr_material: PbrMaterialUniform; /// Material uniform buffer
29/// @group(2) @binding(1) var albedo_texture: texture_2d<f32>; /// Albedo texture (r, g, b)
30/// @group(2) @binding(2) var albedo_sampler: sampler; /// Albedo texture sampler
31/// @group(2) @binding(3) var metallic_roughness_texture: texture_2d<f32>; /// Metallic-roughness texture (b = metallic, g = roughness)
32/// @group(2) @binding(4) var metallic_roughness_sampler: sampler; /// Metallic-roughness texture sampler
33/// @group(2) @binding(5) var normal_texture: texture_2d<f32>; /// Normal texture (x, y, z)
34/// @group(2) @binding(6) var normal_sampler: sampler; /// Normal texture sampler
35/// @group(2) @binding(7) var occlusion_texture: texture_2d<f32>; /// Occlusion texture (r)
36/// @group(2) @binding(8) var occlusion_sampler: sampler; /// Occlusion texture sampler
37/// ```
38#[repr(C)]
39#[derive(Default, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)]
40pub(crate) struct PbrMaterialUniform {
41 /// Flags indicating material textures (1.0 = present, 0.0 = absent) - albedo, metallic-roughness, normal, occlusion
42 pub flags: [f32; 4],
43 /// Albedo color of the material (r, g, b).
44 pub albedo: [f32; 4],
45 /// Metallic intensity of the material.
46 pub metallic: f32,
47 /// Roughness intensity of the material.
48 pub roughness: f32,
49 /// Unused padding to align to 16 bytes.
50 _padding: [f32; 2]
51}
52
53/// Describes a physically based rendering material.
54/// It contains the material properties such as albedo color, metallic and roughness intensity, as well as the textures for these properties. It also contains a uniform buffer that is automatically created and updated with the material data, which can be used in the shader to access the material properties.
55/// The [PbrMaterial3d] component is a wrapper around a handle to a [PbrMaterial] asset, which also adds the [PbrSsboTransformMarker] to the entity, indicating that its transform should be included in the [PbrSsboTransform](crate::prelude::PbrSsboTransform) updates.
56#[derive(Asset, Clone, TypePath)]
57pub struct PbrMaterial {
58 pub label: String,
59
60 /// The albedo color of the material instance (r, g, b).
61 /// The alpha channel is unused.
62 pub albedo: (f32, f32, f32, f32),
63 /// The albedo texture of the material instance. If `None`, the material will use the albedo color.
64 pub albedo_t: Option<Handle<Texture>>,
65
66 /// The metallic intensity of the material instance.
67 pub metallic: f32,
68 /// The roughness intensity of the material instance.
69 pub roughness: f32,
70 /// The metallic-roughness texture of the material instance. If `None`, the material will use the metallic and roughness scalar intensity.
71 /// The metallic value is stored in the blue channel, and the roughness value is stored in the green channel.
72 pub metallic_roughness_t: Option<Handle<Texture>>,
73
74 /// The normal texture of the material instance.
75 /// The normal map is expected to be in tangent space.
76 /// The alpha channel is unused.
77 pub normal_t: Option<Handle<Texture>>,
78 /// The occlusion texture of the material instance.
79 /// The occlusion value is stored in the red channel.
80 pub occlusion_t: Option<Handle<Texture>>,
81
82 /// An optional stencil value to set the stencil buffer to when rendering the material.
83 pub stencil_value: Option<u32>,
84
85 /// The buffer containing the material data. It is automatically created and updated if not set.
86 pub uniform_buffer: Option<Handle<Buffer>>
87}
88impl Default for PbrMaterial {
89 fn default() -> Self {
90 PbrMaterial {
91 label: "pbr-material".to_string(),
92
93 albedo: (1.0, 1.0, 1.0, 0.0),
94 albedo_t: None,
95
96 metallic: 1.0,
97 roughness: 1.0,
98 metallic_roughness_t: None,
99
100 normal_t: None,
101 occlusion_t: None,
102
103 stencil_value: None,
104
105 uniform_buffer: None
106 }
107 }
108}
109impl RenderBinding for PbrMaterial {
110 type Params = (SRes<AssetServer>, SRes<PlaceholderTexture>);
111
112 fn describe(
113 &mut self,
114 (asset_server, placeholder_tex): &SystemParamItem<Self::Params>,
115 builder: &mut RenderBindingBuilder
116 ) {
117 // Create the uniform buffer
118 let uniform = PbrMaterialUniform {
119 flags: [
120 if self.albedo_t.is_some() { 1.0 } else { 0.0 },
121 if self.metallic_roughness_t.is_some() {
122 1.0
123 } else {
124 0.0
125 },
126 if self.normal_t.is_some() { 1.0 } else { 0.0 },
127 if self.occlusion_t.is_some() { 1.0 } else { 0.0 }
128 ],
129 albedo: [self.albedo.0, self.albedo.1, self.albedo.2, self.albedo.3],
130 metallic: self.metallic,
131 roughness: self.roughness,
132 _padding: [0.0, 0.0]
133 };
134 if self.uniform_buffer.is_none() {
135 let uniform_handle = asset_server.add(Buffer {
136 label: format!("{}-uniform-buffer", self.label),
137 size: std::mem::size_of::<PbrMaterialUniform>(),
138 usage: BufferUsage::UNIFORM | BufferUsage::COPY_DST,
139 content: Some(bytemuck::cast_slice(&[uniform]).to_vec())
140 });
141 self.uniform_buffer = Some(uniform_handle);
142 }
143
144 // Build the material
145 builder.add_buffer_from_id(Some(self.uniform_buffer.as_ref().unwrap().id()));
146 if let Some(albedo_t) = &self.albedo_t {
147 builder.add_texture_view_from_id(Some(albedo_t.id()));
148 builder.add_texture_sampler_from_id(Some(albedo_t.id()));
149 } else {
150 builder.add_texture_view_from_id(Some(placeholder_tex.0.id()));
151 builder.add_texture_sampler_from_id(Some(placeholder_tex.0.id()));
152 }
153 if let Some(metallic_roughness_t) = &self.metallic_roughness_t {
154 builder.add_texture_view_from_id(Some(metallic_roughness_t.id()));
155 builder.add_texture_sampler_from_id(Some(metallic_roughness_t.id()));
156 } else {
157 builder.add_texture_view_from_id(Some(placeholder_tex.0.id()));
158 builder.add_texture_sampler_from_id(Some(placeholder_tex.0.id()));
159 }
160 if let Some(normal_t) = &self.normal_t {
161 builder.add_texture_view_from_id(Some(normal_t.id()));
162 builder.add_texture_sampler_from_id(Some(normal_t.id()));
163 } else {
164 builder.add_texture_view_from_id(Some(placeholder_tex.0.id()));
165 builder.add_texture_sampler_from_id(Some(placeholder_tex.0.id()));
166 }
167 if let Some(occlusion_t) = &self.occlusion_t {
168 builder.add_texture_view_from_id(Some(occlusion_t.id()));
169 builder.add_texture_sampler_from_id(Some(occlusion_t.id()));
170 } else {
171 builder.add_texture_view_from_id(Some(placeholder_tex.0.id()));
172 builder.add_texture_sampler_from_id(Some(placeholder_tex.0.id()));
173 }
174 }
175
176 fn label(&self) -> &str {
177 &self.label
178 }
179}