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