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