fyrox_material/lib.rs
1//! Material is a set of parameters for a shader. This module contains everything related to materials.
2//!
3//! See [Material struct docs](self::Material) for more info.
4
5#![warn(missing_docs)]
6
7use crate::shader::{ShaderResource, ShaderResourceExtension};
8use fxhash::FxHashMap;
9use fyrox_core::{
10 algebra::{Matrix2, Matrix3, Matrix4, Vector2, Vector3, Vector4},
11 color::Color,
12 io::FileError,
13 reflect::prelude::*,
14 sstorage::ImmutableString,
15 uuid::{uuid, Uuid},
16 visitor::prelude::*,
17 TypeUuidProvider,
18};
19use fyrox_resource::{
20 io::ResourceIo,
21 manager::{BuiltInResource, ResourceManager},
22 state::ResourceState,
23 untyped::ResourceKind,
24 Resource, ResourceData,
25};
26use fyrox_texture::TextureResource;
27use std::{
28 any::Any,
29 error::Error,
30 fmt::{Display, Formatter},
31 path::Path,
32 sync::{Arc, LazyLock},
33};
34use strum_macros::{AsRefStr, EnumString, VariantNames};
35
36pub mod loader;
37pub mod shader;
38
39/// A texture binding.
40#[derive(Default, Debug, Visit, Clone, Reflect, TypeUuidProvider)]
41#[type_uuid(id = "e1642a47-d372-4840-a8eb-f16350f436f8")]
42pub struct MaterialTextureBinding {
43 /// Actual value of the texture binding. Could be [`None`], in this case fallback value of the
44 /// shader will be used.
45 pub value: Option<TextureResource>,
46}
47
48/// A value of a resource binding that will be used for rendering.
49///
50/// # Limitations
51///
52/// There is a limited set of possible types that can be passed to a shader, most of them are
53/// just simple data types.
54#[derive(Debug, Visit, Clone, Reflect, TypeUuidProvider, AsRefStr, EnumString, VariantNames)]
55#[type_uuid(id = "2df8f1e5-0075-4d0d-9860-70fc27d3e165")]
56pub enum MaterialResourceBinding {
57 /// A texture.
58 Texture(MaterialTextureBinding),
59 /// A group of properties.
60 PropertyGroup(MaterialPropertyGroup),
61}
62
63impl Default for MaterialResourceBinding {
64 fn default() -> Self {
65 Self::PropertyGroup(Default::default())
66 }
67}
68
69impl MaterialResourceBinding {
70 /// Tries to extract a texture from the resource binding.
71 pub fn as_texture(&self) -> Option<TextureResource> {
72 if let Self::Texture(binding) = self {
73 binding.value.clone()
74 } else {
75 None
76 }
77 }
78}
79
80/// Property group stores a bunch of named values of a fixed set of types, that will be used for
81/// rendering with some shader.
82#[derive(Default, Debug, Visit, Clone, Reflect)]
83pub struct MaterialPropertyGroup {
84 properties: FxHashMap<ImmutableString, MaterialProperty>,
85}
86
87impl MaterialPropertyGroup {
88 /// Searches for a property with given name.
89 ///
90 /// # Complexity
91 ///
92 /// O(1)
93 ///
94 /// # Examples
95 ///
96 /// ```no_run
97 /// # use fyrox_core::sstorage::ImmutableString;
98 /// # use fyrox_material::MaterialPropertyGroup;
99 ///
100 /// let mut group = MaterialPropertyGroup::default();
101 /// let color = group.property_ref("diffuseColor").unwrap().as_color();
102 /// ```
103 pub fn property_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialProperty> {
104 let name = name.into();
105 self.properties.get(&name)
106 }
107
108 /// Searches for a property with given name.
109 ///
110 /// # Complexity
111 ///
112 /// O(1)
113 ///
114 /// # Examples
115 ///
116 /// ```no_run
117 /// # use fyrox_core::color::Color;
118 /// # use fyrox_core::sstorage::ImmutableString;
119 /// # use fyrox_material::{MaterialProperty, MaterialPropertyGroup};
120 ///
121 /// let mut group = MaterialPropertyGroup::default();
122 /// *group.property_mut("diffuseColor").unwrap() = MaterialProperty::Color(Color::RED);
123 /// ```
124 pub fn property_mut(
125 &mut self,
126 name: impl Into<ImmutableString>,
127 ) -> Option<&mut MaterialProperty> {
128 let name = name.into();
129 self.properties.get_mut(&name)
130 }
131
132 /// Sets new value of the property with given name.
133 ///
134 /// # Type checking
135 ///
136 /// This method does not check if the property exists in the shader nor its type. Validation
137 /// happens in the renderer, when it tries to use the property group. This is made for performance
138 /// reasons.
139 ///
140 /// # Example
141 ///
142 /// ```no_run
143 /// # use fyrox_material::{Material, MaterialPropertyGroup};
144 /// # use fyrox_core::color::Color;
145 ///
146 /// let mut group = MaterialPropertyGroup::default();
147 /// group.set_property("diffuseColor", Color::WHITE);
148 /// ```
149 pub fn set_property(
150 &mut self,
151 name: impl Into<ImmutableString>,
152 new_value: impl Into<MaterialProperty>,
153 ) {
154 self.properties.insert(name.into(), new_value.into());
155 }
156
157 /// Removes the property from the group. The renderer will use shader defaults for this property.
158 pub fn unset_property(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialProperty> {
159 self.properties.remove(&name.into())
160 }
161
162 /// Returns immutable reference to internal property storage.
163 pub fn properties(&self) -> &FxHashMap<ImmutableString, MaterialProperty> {
164 &self.properties
165 }
166}
167
168/// A set of possible material property types.
169#[derive(Debug, Visit, Clone, Reflect, AsRefStr, EnumString, VariantNames, TypeUuidProvider)]
170#[type_uuid(id = "1c25018d-ab6e-4dca-99a6-e3d9639bc33c")]
171pub enum MaterialProperty {
172 /// Real number.
173 Float(f32),
174
175 /// Real number array.
176 FloatArray(Vec<f32>),
177
178 /// Integer number.
179 Int(i32),
180
181 /// Integer number array.
182 IntArray(Vec<i32>),
183
184 /// Natural number.
185 UInt(u32),
186
187 /// Natural number array.
188 UIntArray(Vec<u32>),
189
190 /// Two-dimensional vector.
191 Vector2(Vector2<f32>),
192
193 /// Two-dimensional vector array.
194 Vector2Array(Vec<Vector2<f32>>),
195
196 /// Three-dimensional vector.
197 Vector3(Vector3<f32>),
198
199 /// Three-dimensional vector array.
200 Vector3Array(Vec<Vector3<f32>>),
201
202 /// Four-dimensional vector.
203 Vector4(Vector4<f32>),
204
205 /// Four-dimensional vector array.
206 Vector4Array(Vec<Vector4<f32>>),
207
208 /// 2x2 Matrix.
209 Matrix2(Matrix2<f32>),
210
211 /// 2x2 Matrix array.
212 Matrix2Array(Vec<Matrix2<f32>>),
213
214 /// 3x3 Matrix.
215 Matrix3(Matrix3<f32>),
216
217 /// 3x3 Matrix array.
218 Matrix3Array(Vec<Matrix3<f32>>),
219
220 /// 4x4 Matrix.
221 Matrix4(Matrix4<f32>),
222
223 /// 4x4 Matrix array.
224 Matrix4Array(Vec<Matrix4<f32>>),
225
226 /// Boolean value.
227 Bool(bool),
228
229 /// An sRGB color.
230 Color(Color),
231}
232
233macro_rules! impl_from {
234 ($variant:ident => $value_type:ty) => {
235 impl From<$value_type> for MaterialProperty {
236 fn from(value: $value_type) -> Self {
237 Self::$variant(value)
238 }
239 }
240 };
241}
242
243impl_from!(Float => f32);
244impl_from!(FloatArray => Vec<f32>);
245impl_from!(Int => i32);
246impl_from!(IntArray => Vec<i32>);
247impl_from!(UInt => u32);
248impl_from!(UIntArray => Vec<u32>);
249impl_from!(Vector2 => Vector2<f32>);
250impl_from!(Vector2Array => Vec<Vector2<f32>>);
251impl_from!(Vector3 => Vector3<f32>);
252impl_from!(Vector3Array => Vec<Vector3<f32>>);
253impl_from!(Vector4 => Vector4<f32>);
254impl_from!(Vector4Array => Vec<Vector4<f32>>);
255impl_from!(Matrix2 => Matrix2<f32>);
256impl_from!(Matrix2Array => Vec<Matrix2<f32>>);
257impl_from!(Matrix3 => Matrix3<f32>);
258impl_from!(Matrix3Array => Vec<Matrix3<f32>>);
259impl_from!(Matrix4 => Matrix4<f32>);
260impl_from!(Matrix4Array => Vec<Matrix4<f32>>);
261impl_from!(Bool => bool);
262impl_from!(Color => Color);
263
264/// A set of possible material property types.
265#[derive(Debug, Copy, Clone)]
266pub enum MaterialPropertyRef<'a> {
267 /// Real number.
268 Float(&'a f32),
269
270 /// Real number array.
271 FloatArray(&'a [f32]),
272
273 /// Integer number.
274 Int(&'a i32),
275
276 /// Integer number array.
277 IntArray(&'a [i32]),
278
279 /// Natural number.
280 UInt(&'a u32),
281
282 /// Natural number array.
283 UIntArray(&'a [u32]),
284
285 /// Two-dimensional vector.
286 Vector2(&'a Vector2<f32>),
287
288 /// Two-dimensional vector array.
289 Vector2Array(&'a [Vector2<f32>]),
290
291 /// Three-dimensional vector.
292 Vector3(&'a Vector3<f32>),
293
294 /// Three-dimensional vector array.
295 Vector3Array(&'a [Vector3<f32>]),
296
297 /// Four-dimensional vector.
298 Vector4(&'a Vector4<f32>),
299
300 /// Four-dimensional vector array.
301 Vector4Array(&'a [Vector4<f32>]),
302
303 /// 2x2 Matrix.
304 Matrix2(&'a Matrix2<f32>),
305
306 /// 2x2 Matrix array.
307 Matrix2Array(&'a [Matrix2<f32>]),
308
309 /// 3x3 Matrix.
310 Matrix3(&'a Matrix3<f32>),
311
312 /// 3x3 Matrix array.
313 Matrix3Array(&'a [Matrix3<f32>]),
314
315 /// 4x4 Matrix.
316 Matrix4(&'a Matrix4<f32>),
317
318 /// 4x4 Matrix array.
319 Matrix4Array(&'a [Matrix4<f32>]),
320
321 /// Boolean value.
322 Bool(&'a bool),
323
324 /// An sRGB color.
325 Color(&'a Color),
326}
327
328impl MaterialProperty {
329 /// Maps the inner value of the property to its respective reference.
330 pub fn as_ref(&self) -> MaterialPropertyRef<'_> {
331 match self {
332 MaterialProperty::Float(v) => MaterialPropertyRef::Float(v),
333 MaterialProperty::FloatArray(v) => MaterialPropertyRef::FloatArray(v),
334 MaterialProperty::Int(v) => MaterialPropertyRef::Int(v),
335 MaterialProperty::IntArray(v) => MaterialPropertyRef::IntArray(v),
336 MaterialProperty::UInt(v) => MaterialPropertyRef::UInt(v),
337 MaterialProperty::UIntArray(v) => MaterialPropertyRef::UIntArray(v),
338 MaterialProperty::Vector2(v) => MaterialPropertyRef::Vector2(v),
339 MaterialProperty::Vector2Array(v) => MaterialPropertyRef::Vector2Array(v),
340 MaterialProperty::Vector3(v) => MaterialPropertyRef::Vector3(v),
341 MaterialProperty::Vector3Array(v) => MaterialPropertyRef::Vector3Array(v),
342 MaterialProperty::Vector4(v) => MaterialPropertyRef::Vector4(v),
343 MaterialProperty::Vector4Array(v) => MaterialPropertyRef::Vector4Array(v),
344 MaterialProperty::Matrix2(v) => MaterialPropertyRef::Matrix2(v),
345 MaterialProperty::Matrix2Array(v) => MaterialPropertyRef::Matrix2Array(v),
346 MaterialProperty::Matrix3(v) => MaterialPropertyRef::Matrix3(v),
347 MaterialProperty::Matrix3Array(v) => MaterialPropertyRef::Matrix3Array(v),
348 MaterialProperty::Matrix4(v) => MaterialPropertyRef::Matrix4(v),
349 MaterialProperty::Matrix4Array(v) => MaterialPropertyRef::Matrix4Array(v),
350 MaterialProperty::Bool(v) => MaterialPropertyRef::Bool(v),
351 MaterialProperty::Color(v) => MaterialPropertyRef::Color(v),
352 }
353 }
354}
355
356macro_rules! impl_from_ref {
357 ($variant:ident => $value_type:ty) => {
358 impl<'a> From<&'a $value_type> for MaterialPropertyRef<'a> {
359 fn from(value: &'a $value_type) -> Self {
360 Self::$variant(value)
361 }
362 }
363 };
364}
365
366impl_from_ref!(Float => f32);
367impl_from_ref!(FloatArray => [f32]);
368impl_from_ref!(Int => i32);
369impl_from_ref!(IntArray => [i32]);
370impl_from_ref!(UInt => u32);
371impl_from_ref!(UIntArray => [u32]);
372impl_from_ref!(Vector2 => Vector2<f32>);
373impl_from_ref!(Vector2Array => [Vector2<f32>]);
374impl_from_ref!(Vector3 => Vector3<f32>);
375impl_from_ref!(Vector3Array => [Vector3<f32>]);
376impl_from_ref!(Vector4 => Vector4<f32>);
377impl_from_ref!(Vector4Array => [Vector4<f32>]);
378impl_from_ref!(Matrix2 => Matrix2<f32>);
379impl_from_ref!(Matrix2Array => [Matrix2<f32>]);
380impl_from_ref!(Matrix3 => Matrix3<f32>);
381impl_from_ref!(Matrix3Array => [Matrix3<f32>]);
382impl_from_ref!(Matrix4 => Matrix4<f32>);
383impl_from_ref!(Matrix4Array => [Matrix4<f32>]);
384impl_from_ref!(Bool => bool);
385impl_from_ref!(Color => Color);
386
387impl From<Option<TextureResource>> for MaterialResourceBinding {
388 fn from(value: Option<TextureResource>) -> Self {
389 Self::Texture(MaterialTextureBinding { value })
390 }
391}
392
393impl From<TextureResource> for MaterialResourceBinding {
394 fn from(value: TextureResource) -> Self {
395 Self::Texture(MaterialTextureBinding { value: Some(value) })
396 }
397}
398
399macro_rules! define_as {
400 ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
401 $(#[$meta])*
402 pub fn $name(&self) -> Option<$ty> {
403 if let MaterialProperty::$variant(v) = self {
404 Some(*v)
405 } else {
406 None
407 }
408 }
409 };
410}
411
412macro_rules! define_as_ref {
413 ($(#[$meta:meta])* $name:ident = $variant:ident -> $ty:ty) => {
414 $(#[$meta])*
415 pub fn $name(&self) -> Option<&$ty> {
416 if let MaterialProperty::$variant(v) = self {
417 Some(v)
418 } else {
419 None
420 }
421 }
422 };
423}
424
425impl MaterialProperty {
426 define_as!(
427 /// Tries to unwrap property value as float.
428 as_float = Float -> f32
429 );
430 define_as_ref!(
431 /// Tries to unwrap property value as float array.
432 as_float_array = FloatArray -> [f32]
433 );
434 define_as!(
435 /// Tries to unwrap property value as integer.
436 as_int = Int -> i32
437 );
438 define_as_ref!(
439 /// Tries to unwrap property value as integer array.
440 as_int_array = IntArray -> [i32]
441 );
442 define_as!(
443 /// Tries to unwrap property value as unsigned integer.
444 as_uint = UInt -> u32
445 );
446 define_as_ref!(
447 /// Tries to unwrap property value as unsigned integer array.
448 as_uint_array = UIntArray -> [u32]
449 );
450 define_as!(
451 /// Tries to unwrap property value as boolean.
452 as_bool = Bool -> bool
453 );
454 define_as!(
455 /// Tries to unwrap property value as color.
456 as_color = Color -> Color
457 );
458 define_as!(
459 /// Tries to unwrap property value as two-dimensional vector.
460 as_vector2 = Vector2 -> Vector2<f32>
461 );
462 define_as_ref!(
463 /// Tries to unwrap property value as two-dimensional vector array.
464 as_vector2_array = Vector2Array -> [Vector2<f32>]
465 );
466 define_as!(
467 /// Tries to unwrap property value as three-dimensional vector.
468 as_vector3 = Vector3 -> Vector3<f32>
469 );
470 define_as_ref!(
471 /// Tries to unwrap property value as three-dimensional vector array.
472 as_vector3_array = Vector3Array -> [Vector3<f32>]
473 );
474 define_as!(
475 /// Tries to unwrap property value as four-dimensional vector.
476 as_vector4 = Vector4 -> Vector4<f32>
477 );
478 define_as_ref!(
479 /// Tries to unwrap property value as four-dimensional vector array.
480 as_vector4_array = Vector4Array -> [Vector4<f32>]
481 );
482 define_as!(
483 /// Tries to unwrap property value as 2x2 matrix.
484 as_matrix2 = Matrix2 -> Matrix2<f32>
485 );
486 define_as_ref!(
487 /// Tries to unwrap property value as 2x2 matrix array.
488 as_matrix2_array = Matrix2Array -> [Matrix2<f32>]
489 );
490 define_as!(
491 /// Tries to unwrap property value as 3x3 matrix.
492 as_matrix3 = Matrix3 -> Matrix3<f32>
493 );
494 define_as_ref!(
495 /// Tries to unwrap property value as 3x3 matrix array.
496 as_matrix3_array = Matrix3Array -> [Matrix3<f32>]
497 );
498 define_as!(
499 /// Tries to unwrap property value as 4x4 matrix.
500 as_matrix4 = Matrix4 -> Matrix4<f32>
501 );
502 define_as_ref!(
503 /// Tries to unwrap property value as 4x4 matrix array.
504 as_matrix4_array = Matrix4Array -> [Matrix4<f32>]
505 );
506}
507
508impl Default for MaterialProperty {
509 fn default() -> Self {
510 Self::Float(0.0)
511 }
512}
513
514/// Material defines a set of resource bindings for a shader. It could be textures and property groups.
515/// Textures contains graphical information (such as diffuse, normal, height, emission, etc. maps),
516/// while property groups contains numerical values (matrices, vectors, numbers, etc.).
517///
518/// Each resource binding can be changed in runtime giving you the ability to create animated materials.
519/// However, in practice most materials are static, this means that once it created, it won't be
520/// changed anymore.
521///
522/// Please keep in mind that the actual "rules" of drawing an entity are stored in the shader,
523/// **material is only a storage** for specific uses of the shader.
524///
525/// Multiple materials can share the same shader, for example standard shader covers 95% of most
526/// common use cases, and it is shared across multiple materials. The only difference are property
527/// values, for example you can draw multiple cubes using the same shader, but with different
528/// textures.
529///
530/// Material itself can be shared across multiple places as well as the shader. This gives you the
531/// ability to render multiple objects with the same material efficiently.
532///
533/// # Performance
534///
535/// It is very important to re-use materials as much as possible, because the amount of materials used
536/// per frame significantly correlates with performance. The more unique materials you have per frame,
537/// the more work has to be done by the renderer and the video driver to render a frame and the more time
538/// the frame will require for rendering, thus lowering your FPS.
539///
540/// # Examples
541///
542/// A material can only be created using a shader instance, every material must have a shader. The
543/// shader provides information about its resources. Default values of each property defined in the
544/// shader.
545///
546/// ## Standard material
547///
548/// Usually standard shader is enough for most cases, [`Material`] even has a [`Material::standard()`]
549/// method to create a material with standard shader:
550///
551/// ```no_run
552/// # use fyrox_resource::manager::ResourceManager;
553/// # use fyrox_core::sstorage::ImmutableString;
554/// # use fyrox_material::{
555/// # Material, MaterialProperty,
556/// # shader::{ShaderResource, SamplerFallback},
557/// # };
558/// # use fyrox_texture::Texture;
559///
560/// fn create_brick_material(resource_manager: ResourceManager) -> Material {
561/// let mut material = Material::standard();
562///
563/// material.bind(
564/// "diffuseTexture",
565/// resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
566/// );
567///
568/// material
569/// }
570/// ```
571///
572/// As you can see it is pretty simple with standard material, all you need is to set values to desired
573/// properties and you good to go. All you need to do is to apply the material, for example it could be
574/// mesh surface or some other place that supports materials. For the full list of properties of the
575/// standard shader see [shader module docs](self::shader).
576///
577/// ## Custom material
578///
579/// Custom materials is a bit more complex, you need to get a shader instance using the resource manager
580/// (or create a shader in-place by embedding its source code in the binary) and then create the material
581/// and populate it with a set of property values.
582///
583/// ```no_run
584/// # use fyrox_core::{sstorage::ImmutableString, algebra::Vector3};
585/// # use fyrox_resource::manager::ResourceManager;
586/// # use fyrox_material::{Material, MaterialProperty, shader::Shader};
587///
588/// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
589/// let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
590///
591/// // Here we assume that the material really has the properties defined below.
592/// let mut material = Material::from_shader(shader);
593///
594/// material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
595///
596/// material
597/// }
598/// ```
599///
600/// As you can see it is slightly more complex that with the standard shader. The main difference here is
601/// that we using resource manager to get shader instance, and then we just use the instance to create
602/// material instance. Then we populate properties as usual.
603#[derive(Debug, Clone, Reflect)]
604pub struct Material {
605 shader: ShaderResource,
606 resource_bindings: FxHashMap<ImmutableString, MaterialResourceBinding>,
607}
608
609impl Visit for Material {
610 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
611 let mut region = visitor.enter_region(name)?;
612
613 let mut shader = if region.is_reading() {
614 // It is very important to give a proper default state to the shader resource
615 // here. Its standard default is set to shared "Standard" shader. If it is left
616 // as is, deserialization will modify the "Standard" shader and this will lead
617 // to "amazing" results and hours of debugging.
618 ShaderResource::default()
619 } else {
620 self.shader.clone()
621 };
622 shader.visit("Shader", &mut region)?;
623 self.shader = shader;
624
625 self.resource_bindings
626 .visit("ResourceBindings", &mut region)?;
627
628 Ok(())
629 }
630}
631
632impl Default for Material {
633 fn default() -> Self {
634 Material::standard()
635 }
636}
637
638impl TypeUuidProvider for Material {
639 fn type_uuid() -> Uuid {
640 uuid!("0e54fe44-0c58-4108-a681-d6eefc88c234")
641 }
642}
643
644impl ResourceData for Material {
645 fn type_uuid(&self) -> Uuid {
646 <Self as TypeUuidProvider>::type_uuid()
647 }
648
649 fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
650 let mut visitor = Visitor::new();
651 self.visit("Material", &mut visitor)?;
652 visitor.save_ascii_to_file(path)?;
653 Ok(())
654 }
655
656 fn can_be_saved(&self) -> bool {
657 true
658 }
659
660 fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
661 Some(Box::new(self.clone()))
662 }
663}
664
665/// A set of possible errors that can occur when working with materials.
666#[derive(Debug)]
667pub enum MaterialError {
668 /// Unable to read data source.
669 Visit(VisitError),
670}
671
672impl From<VisitError> for MaterialError {
673 fn from(value: VisitError) -> Self {
674 Self::Visit(value)
675 }
676}
677
678impl From<FileError> for MaterialError {
679 fn from(value: FileError) -> Self {
680 Self::Visit(VisitError::FileLoadError(value))
681 }
682}
683
684impl Display for MaterialError {
685 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
686 match self {
687 MaterialError::Visit(e) => {
688 write!(f, "Failed to visit data source. Reason: {e:?}")
689 }
690 }
691 }
692}
693
694impl Material {
695 /// Creates a new instance of material with the standard shader.
696 ///
697 /// # Example
698 ///
699 /// ```no_run
700 /// # use fyrox_core::sstorage::ImmutableString;
701 /// # use fyrox_texture::Texture;
702 /// # use fyrox_material::{Material, MaterialProperty, shader::{ShaderResource, SamplerFallback}};
703 /// # use fyrox_resource::manager::ResourceManager;
704 ///
705 /// fn create_brick_material(resource_manager: ResourceManager) -> Material {
706 /// let mut material = Material::standard();
707 ///
708 /// material.bind(
709 /// "diffuseTexture",
710 /// resource_manager.request::<Texture>("Brick_DiffuseTexture.jpg")
711 /// );
712 ///
713 /// material
714 /// }
715 /// ```
716 pub fn standard() -> Self {
717 Self::from_shader(ShaderResource::standard())
718 }
719
720 /// Creates new instance of standard 2D material.
721 pub fn standard_2d() -> Self {
722 Self::from_shader(ShaderResource::standard_2d())
723 }
724
725 /// Creates new instance of standard 2D material.
726 pub fn standard_particle_system() -> Self {
727 Self::from_shader(ShaderResource::standard_particle_system())
728 }
729
730 /// Creates new instance of standard sprite material.
731 pub fn standard_sprite() -> Self {
732 Self::from_shader(ShaderResource::standard_sprite())
733 }
734
735 /// Creates new instance of standard material that renders both sides of a face.
736 pub fn standard_two_sides() -> Self {
737 Self::from_shader(ShaderResource::standard_twosides())
738 }
739
740 /// Creates new instance of standard terrain material.
741 pub fn standard_terrain() -> Self {
742 Self::from_shader(ShaderResource::standard_terrain())
743 }
744
745 /// Creates new instance of standard tile material.
746 pub fn standard_tile() -> Self {
747 Self::from_shader(ShaderResource::standard_tile())
748 }
749
750 /// Creates new instance of standard widget material.
751 pub fn standard_widget() -> Self {
752 Self::from_shader(ShaderResource::standard_widget())
753 }
754
755 /// Creates a new material instance with given shader. By default, a material does not store any
756 /// resource bindings. In this case the renderer will use shader default values for rendering.
757 /// Materials could be considered as container with values that overwrites shader values.
758 ///
759 /// # Example
760 ///
761 /// ```no_run
762 /// # use fyrox_core::{sstorage::ImmutableString, algebra::Vector3};
763 /// # use fyrox_resource::manager::ResourceManager;
764 /// # use fyrox_material::{Material, MaterialProperty, shader::Shader};
765 ///
766 /// async fn create_grass_material(resource_manager: ResourceManager) -> Material {
767 /// let shader = resource_manager.request::<Shader>("my_grass_shader.ron").await.unwrap();
768 ///
769 /// // Here we assume that the material really has the properties defined below.
770 /// let mut material = Material::from_shader(shader);
771 ///
772 /// material.set_property("windDirection", Vector3::new(1.0, 0.0, 0.5));
773 ///
774 /// material
775 /// }
776 /// ```
777 pub fn from_shader(shader: ShaderResource) -> Self {
778 Self {
779 shader,
780 resource_bindings: Default::default(),
781 }
782 }
783
784 /// Loads a material from file.
785 pub async fn from_file<P>(
786 path: P,
787 io: &dyn ResourceIo,
788 resource_manager: ResourceManager,
789 ) -> Result<Self, MaterialError>
790 where
791 P: AsRef<Path>,
792 {
793 let content = io.load_file(path.as_ref()).await?;
794 let mut material = Material {
795 shader: Default::default(),
796 resource_bindings: Default::default(),
797 };
798 let mut visitor = Visitor::load_from_memory(&content)?;
799 visitor.blackboard.register(Arc::new(resource_manager));
800 material.visit("Material", &mut visitor)?;
801 Ok(material)
802 }
803
804 /// Searches for a resource binding with the given name and returns immutable reference to it
805 /// (if any).
806 ///
807 /// # Complexity
808 ///
809 /// O(1)
810 pub fn binding_ref(
811 &self,
812 name: impl Into<ImmutableString>,
813 ) -> Option<&MaterialResourceBinding> {
814 let name = name.into();
815 self.resource_bindings.get(&name)
816 }
817
818 /// Searches for a resource binding with the given name and returns mutable reference to it,
819 /// allowing you to modify the value.
820 ///
821 /// # Complexity
822 ///
823 /// O(1)
824 pub fn binding_mut(
825 &mut self,
826 name: impl Into<ImmutableString>,
827 ) -> Option<&mut MaterialResourceBinding> {
828 let name = name.into();
829 self.resource_bindings.get_mut(&name)
830 }
831
832 /// Searches for a texture with the given name.
833 pub fn texture_ref(&self, name: impl Into<ImmutableString>) -> Option<&MaterialTextureBinding> {
834 if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_ref(name) {
835 Some(binding)
836 } else {
837 None
838 }
839 }
840
841 /// Searches for a texture with the given name.
842 pub fn texture_mut(
843 &mut self,
844 name: impl Into<ImmutableString>,
845 ) -> Option<&mut MaterialTextureBinding> {
846 if let Some(MaterialResourceBinding::Texture(binding)) = self.binding_mut(name) {
847 Some(binding)
848 } else {
849 None
850 }
851 }
852
853 /// Searches for a property group binding with the given name and returns immutable reference to it
854 /// (if any).
855 ///
856 /// # Complexity
857 ///
858 /// O(1)
859 ///
860 /// # Examples
861 ///
862 /// ```no_run
863 /// # use fyrox_core::sstorage::ImmutableString;
864 /// # use fyrox_material::Material;
865 ///
866 /// let mut material = Material::standard();
867 ///
868 /// let color = material
869 /// .property_group_ref("properties")
870 /// .unwrap()
871 /// .property_ref("diffuseColor")
872 /// .unwrap()
873 /// .as_color();
874 /// ```
875 pub fn property_group_ref(
876 &self,
877 name: impl Into<ImmutableString>,
878 ) -> Option<&MaterialPropertyGroup> {
879 self.binding_ref(name).and_then(|binding| match binding {
880 MaterialResourceBinding::Texture { .. } => None,
881 MaterialResourceBinding::PropertyGroup(group) => Some(group),
882 })
883 }
884
885 /// Searches for a property group binding with the given name and returns immutable reference to it
886 /// (if any).
887 ///
888 /// # Complexity
889 ///
890 /// O(1)
891 ///
892 /// # Examples
893 ///
894 /// ```no_run
895 /// # use fyrox_core::color::Color;
896 /// # use fyrox_core::sstorage::ImmutableString;
897 /// # use fyrox_material::Material;
898 ///
899 /// let mut material = Material::standard();
900 ///
901 /// let color = material
902 /// .property_group_mut("properties")
903 /// .unwrap()
904 /// .set_property("diffuseColor", Color::RED);
905 /// ```
906 pub fn property_group_mut(
907 &mut self,
908 name: impl Into<ImmutableString>,
909 ) -> Option<&mut MaterialPropertyGroup> {
910 self.binding_mut(name).and_then(|binding| match binding {
911 MaterialResourceBinding::Texture { .. } => None,
912 MaterialResourceBinding::PropertyGroup(group) => Some(group),
913 })
914 }
915
916 /// Tries to find a property group with the given name, creates a new group with the given name
917 /// if there's no such group.
918 pub fn try_get_or_insert_property_group(
919 &mut self,
920 name: impl Into<ImmutableString>,
921 ) -> &mut MaterialPropertyGroup {
922 let name = name.into();
923 if let MaterialResourceBinding::PropertyGroup(group) = self
924 .resource_bindings
925 .entry(name.clone())
926 .or_insert_with(|| {
927 MaterialResourceBinding::PropertyGroup(MaterialPropertyGroup::default())
928 })
929 {
930 group
931 } else {
932 panic!("There's already a material resource binding with {name}!");
933 }
934 }
935
936 /// Sets new value of the resource binding with given name.
937 ///
938 /// # Type checking
939 ///
940 /// A new value must have the same type as in shader, otherwise an error will be generated at
941 /// attempt to render something with this material.
942 ///
943 /// # Example
944 ///
945 /// ```no_run
946 /// # use fyrox_material::{Material, MaterialProperty};
947 /// # use fyrox_core::color::Color;
948 /// # use fyrox_core::sstorage::ImmutableString;
949 ///
950 /// let mut material = Material::standard();
951 ///
952 /// material.set_property("diffuseColor", Color::WHITE);
953 /// ```
954 pub fn bind(
955 &mut self,
956 name: impl Into<ImmutableString>,
957 new_value: impl Into<MaterialResourceBinding>,
958 ) {
959 self.resource_bindings.insert(name.into(), new_value.into());
960 }
961
962 /// Tries to remove a resource bound to the given name.
963 pub fn unbind(&mut self, name: impl Into<ImmutableString>) -> Option<MaterialResourceBinding> {
964 self.resource_bindings.remove(&name.into())
965 }
966
967 /// Sets new value of the property with given name to the property group with `properties` name.
968 /// It is a standard property group, that could be used to store pretty much any values.
969 ///
970 /// # Type checking
971 ///
972 /// A new value must have the same type as in shader, otherwise an error will be generated at
973 /// attempt to render something with this material.
974 ///
975 /// # Example
976 ///
977 /// ```no_run
978 /// # use fyrox_material::{Material, MaterialProperty};
979 /// # use fyrox_core::color::Color;
980 /// # use fyrox_core::sstorage::ImmutableString;
981 ///
982 /// let mut material = Material::standard();
983 ///
984 /// material.set_property("diffuseColor", Color::WHITE);
985 ///
986 /// // A full equivalent of the above:
987 /// material.try_get_or_insert_property_group("properties")
988 /// .set_property("diffuseColor", Color::WHITE);
989 /// ```
990 pub fn set_property(
991 &mut self,
992 name: impl Into<ImmutableString>,
993 new_value: impl Into<MaterialProperty>,
994 ) {
995 self.try_get_or_insert_property_group("properties")
996 .set_property(name, new_value);
997 }
998
999 /// Returns a reference to current shader.
1000 pub fn shader(&self) -> &ShaderResource {
1001 &self.shader
1002 }
1003
1004 /// Returns immutable reference to internal property storage.
1005 pub fn bindings(&self) -> &FxHashMap<ImmutableString, MaterialResourceBinding> {
1006 &self.resource_bindings
1007 }
1008
1009 /// Tries to find a sampler with the given name and returns its texture (if any).
1010 pub fn texture(&self, name: impl Into<ImmutableString>) -> Option<TextureResource> {
1011 self.resource_bindings.get(&name.into()).and_then(|v| {
1012 if let MaterialResourceBinding::Texture(binding) = v {
1013 binding.value.clone()
1014 } else {
1015 None
1016 }
1017 })
1018 }
1019}
1020
1021/// Material resource is a material instance that can be used across multiple objects. It is useful
1022/// when you need to have multiple objects that have the same material.
1023///
1024/// Material resource is also tells a renderer that this material can be used for efficient rendering -
1025/// the renderer will be able to optimize rendering when it knows that multiple objects share the
1026/// same material.
1027pub type MaterialResource = Resource<Material>;
1028
1029/// Extension methods for material resource.
1030pub trait MaterialResourceExtension {
1031 /// Creates a new material resource.
1032 ///
1033 /// # Hot Reloading
1034 ///
1035 /// You must use this method to create materials, if you want hot reloading to be reliable and
1036 /// prevent random crashes. Unlike [`Resource::new_ok`], this method ensures that correct vtable
1037 /// is used.
1038 fn new(material: Material) -> Self;
1039
1040 /// Creates a deep copy of the material resource.
1041 fn deep_copy(&self) -> MaterialResource;
1042
1043 /// Creates a deep copy of the material resource and marks it as procedural.
1044 fn deep_copy_as_embedded(&self) -> MaterialResource {
1045 let material = self.deep_copy();
1046 let mut header = material.header();
1047 header.kind.make_embedded();
1048 drop(header);
1049 material
1050 }
1051}
1052
1053impl MaterialResourceExtension for MaterialResource {
1054 #[inline(never)] // Prevents vtable mismatch when doing hot reloading.
1055 fn new(material: Material) -> Self {
1056 Self::new_ok(Uuid::new_v4(), ResourceKind::Embedded, material)
1057 }
1058
1059 fn deep_copy(&self) -> MaterialResource {
1060 let material_state = self.header();
1061 match material_state.state {
1062 ResourceState::Unloaded => self.resource_uuid().into(),
1063 ResourceState::Pending { .. } => {
1064 MaterialResource::new_pending(self.resource_uuid(), ResourceKind::External)
1065 }
1066 ResourceState::LoadError {
1067 ref error,
1068 ref path,
1069 } => MaterialResource::new_load_error(
1070 ResourceKind::External,
1071 path.clone(),
1072 error.clone(),
1073 ),
1074 ResourceState::Ok { ref data } => MaterialResource::new_ok(
1075 Uuid::new_v4(),
1076 ResourceKind::Embedded,
1077 (&**data as &dyn Any)
1078 .downcast_ref::<Material>()
1079 .unwrap()
1080 .clone(),
1081 ),
1082 }
1083 }
1084}
1085
1086/// Standard PBR material. Keep in mind that this material is global, any modification
1087/// of it will reflect on every other usage of it.
1088pub static STANDARD: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1089 BuiltInResource::new_no_source(
1090 "__StandardMaterial",
1091 MaterialResource::new_ok(
1092 uuid!("fac37721-d1b8-422e-ae0c-83196ecd0a26"),
1093 ResourceKind::External,
1094 Material::from_shader(ShaderResource::standard()),
1095 ),
1096 )
1097});
1098
1099/// Standard 2D material. Keep in mind that this material is global, any modification
1100/// of it will reflect on every other usage of it.
1101pub static STANDARD_2D: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1102 BuiltInResource::new_no_source(
1103 "__Standard2DMaterial",
1104 MaterialResource::new_ok(
1105 uuid!("fe78a0d0-d059-4156-bc63-c3d2e36ad4b6"),
1106 ResourceKind::External,
1107 Material::from_shader(ShaderResource::standard_2d()),
1108 ),
1109 )
1110});
1111
1112/// Standard particle system material. Keep in mind that this material is global, any modification
1113/// of it will reflect on every other usage of it.
1114pub static STANDARD_PARTICLE_SYSTEM: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1115 BuiltInResource::new_no_source(
1116 "__StandardParticleSystemMaterial",
1117 MaterialResource::new_ok(
1118 uuid!("5bebe6e5-4aeb-496f-88f6-abe2b1ac798b"),
1119 ResourceKind::External,
1120 Material::from_shader(ShaderResource::standard_particle_system()),
1121 ),
1122 )
1123});
1124
1125/// Standard sprite material. Keep in mind that this material is global, any modification
1126/// of it will reflect on every other usage of it.
1127pub static STANDARD_SPRITE: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1128 BuiltInResource::new_no_source(
1129 "__StandardSpriteMaterial",
1130 MaterialResource::new_ok(
1131 uuid!("3e331786-baae-412b-9d99-7370174bca43"),
1132 ResourceKind::External,
1133 Material::from_shader(ShaderResource::standard_sprite()),
1134 ),
1135 )
1136});
1137
1138/// Standard terrain material. Keep in mind that this material is global, any modification
1139/// of it will reflect on every other usage of it.
1140pub static STANDARD_TERRAIN: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1141 BuiltInResource::new_no_source(
1142 "__StandardTerrainMaterial",
1143 MaterialResource::new_ok(
1144 uuid!("0e407e22-41ad-4763-9adb-9d2e86351ece"),
1145 ResourceKind::External,
1146 Material::from_shader(ShaderResource::standard_terrain()),
1147 ),
1148 )
1149});
1150
1151/// Standard two-sided material. Keep in mind that this material is global, any modification
1152/// of it will reflect on every other usage of it.
1153pub static STANDARD_TWOSIDES: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1154 BuiltInResource::new_no_source(
1155 "__StandardTwoSidesMaterial",
1156 MaterialResource::new_ok(
1157 uuid!("24115321-7766-495c-bc3a-75db2f73d26d"),
1158 ResourceKind::External,
1159 Material::from_shader(ShaderResource::standard_twosides()),
1160 ),
1161 )
1162});
1163
1164/// Standard widget material. Keep in mind that this material is global, any modification
1165/// of it will reflect on every other usage of it.
1166pub static STANDARD_WIDGET: LazyLock<BuiltInResource<Material>> = LazyLock::new(|| {
1167 BuiltInResource::new_no_source(
1168 "__StandardWidgetMaterial",
1169 MaterialResource::new_ok(
1170 uuid!("e5d61a6f-5c94-4137-b303-1ae29cfff6e7"),
1171 ResourceKind::External,
1172 Material::from_shader(ShaderResource::standard_widget()),
1173 ),
1174 )
1175});