Skip to main content

bevy_gltf/loader/gltf_ext/
material.rs

1use bevy_math::Affine2;
2use bevy_pbr::UvChannel;
3use bevy_render::alpha::AlphaMode;
4
5use gltf::{json::texture::Info, Material};
6
7use serde_json::value;
8
9use crate::GltfAssetLabel;
10
11use super::texture::texture_transform_to_affine2;
12
13#[cfg(any(
14    feature = "pbr_anisotropy_texture",
15    feature = "pbr_specular_textures",
16    feature = "pbr_multi_layer_material_textures"
17))]
18use {
19    bevy_asset::{AssetPath, Handle},
20    bevy_image::Image,
21    serde_json::{Map, Value},
22};
23
24/// Parses a texture that's part of a material extension block and returns its
25/// UV channel and image reference.
26#[cfg(any(
27    feature = "pbr_anisotropy_texture",
28    feature = "pbr_specular_textures",
29    feature = "pbr_multi_layer_material_textures"
30))]
31pub(crate) fn parse_material_extension_texture(
32    material: &Material,
33    extension: &Map<String, Value>,
34    texture_name: &str,
35    texture_kind: &str,
36    textures: &[Handle<Image>],
37    asset_path: AssetPath<'_>,
38) -> (UvChannel, Option<Handle<Image>>) {
39    match extension
40        .get(texture_name)
41        .and_then(|value| value::from_value::<Info>(value.clone()).ok())
42    {
43        Some(json_info) => (
44            uv_channel(material, texture_kind, json_info.tex_coord),
45            Some({
46                match textures.get(json_info.index.value()).cloned() {
47                    None => {
48                        tracing::warn!("Gltf at path \"{asset_path}\" contains invalid texture index <{}> for texture {texture_name}. Using default image.", json_info.index.value());
49                        Handle::default()
50                    }
51                    Some(handle) => handle,
52                }
53            }),
54        ),
55        None => (UvChannel::default(), None),
56    }
57}
58
59pub(crate) fn uv_channel(material: &Material, texture_kind: &str, tex_coord: u32) -> UvChannel {
60    match tex_coord {
61        0 => UvChannel::Uv0,
62        1 => UvChannel::Uv1,
63        _ => {
64            let material_name = material
65                .name()
66                .map(|n| format!("the material \"{n}\""))
67                .unwrap_or_else(|| "an unnamed material".to_string());
68            let material_index = material
69                .index()
70                .map(|i| format!("index {i}"))
71                .unwrap_or_else(|| "default".to_string());
72            tracing::warn!(
73                    "Only 2 UV Channels are supported, but {material_name} ({material_index}) \
74                    has the TEXCOORD attribute {} on texture kind {texture_kind}, which will fallback to 0.",
75                    tex_coord,
76                );
77            UvChannel::Uv0
78        }
79    }
80}
81
82pub(crate) fn alpha_mode(material: &Material) -> AlphaMode {
83    match material.alpha_mode() {
84        gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
85        gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
86        gltf::material::AlphaMode::Blend => AlphaMode::Blend,
87    }
88}
89
90/// Returns the index (within the `textures` array) of the texture with the
91/// given field name in the data for the material extension with the given name,
92/// if there is one.
93pub(crate) fn extension_texture_index(
94    material: &Material,
95    extension_name: &str,
96    texture_field_name: &str,
97) -> Option<usize> {
98    Some(
99        value::from_value::<Info>(
100            material
101                .extensions()?
102                .get(extension_name)?
103                .as_object()?
104                .get(texture_field_name)?
105                .clone(),
106        )
107        .ok()?
108        .index
109        .value(),
110    )
111}
112
113/// Returns true if the material needs mesh tangents in order to be successfully
114/// rendered.
115///
116/// We generate them if this function returns true.
117pub(crate) fn needs_tangents(material: &Material) -> bool {
118    [
119        material.normal_texture().is_some(),
120        #[cfg(feature = "pbr_multi_layer_material_textures")]
121        extension_texture_index(
122            material,
123            "KHR_materials_clearcoat",
124            "clearcoatNormalTexture",
125        )
126        .is_some(),
127    ]
128    .into_iter()
129    .reduce(|a, b| a || b)
130    .unwrap_or(false)
131}
132
133pub(crate) fn warn_on_differing_texture_transforms(
134    material: &Material,
135    info: &gltf::texture::Info,
136    texture_transform: Affine2,
137    texture_kind: &str,
138) {
139    let has_differing_texture_transform = info
140        .texture_transform()
141        .map(texture_transform_to_affine2)
142        .is_some_and(|t| t != texture_transform);
143    if has_differing_texture_transform {
144        let material_name = material
145            .name()
146            .map(|n| format!("the material \"{n}\""))
147            .unwrap_or_else(|| "an unnamed material".to_string());
148        let texture_name = info
149            .texture()
150            .name()
151            .map(|n| format!("its {texture_kind} texture \"{n}\""))
152            .unwrap_or_else(|| format!("its unnamed {texture_kind} texture"));
153        let material_index = material
154            .index()
155            .map(|i| format!("index {i}"))
156            .unwrap_or_else(|| "default".to_string());
157        tracing::warn!(
158            "Only texture transforms on base color textures are supported, but {material_name} ({material_index}) \
159            has a texture transform on {texture_name} (index {}), which will be ignored.", info.texture().index()
160        );
161    }
162}
163
164pub(crate) fn material_label(material: &Material, is_scale_inverted: bool) -> GltfAssetLabel {
165    if let Some(index) = material.index() {
166        GltfAssetLabel::Material {
167            index,
168            is_scale_inverted,
169        }
170    } else {
171        GltfAssetLabel::DefaultMaterial
172    }
173}