1 # SPDX-FileCopyrightText: 2018-2022 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
5 from copy
import deepcopy
8 from ....io
.com
import gltf2_io
9 from ....io
.com
.gltf2_io_extensions
import Extension
10 from ....io
.exp
.gltf2_io_user_extensions
import export_user_extensions
11 from ....io
.com
.gltf2_io_debug
import print_console
12 from ...com
.gltf2_blender_extras
import generate_extras
13 from ...exp
import gltf2_blender_get
14 from ..gltf2_blender_gather_cache
import cached
, cached_by_key
15 from . import gltf2_blender_gather_materials_unlit
16 from . import gltf2_blender_gather_texture_info
17 from . import gltf2_blender_gather_materials_pbr_metallic_roughness
18 from .extensions
.gltf2_blender_gather_materials_volume
import export_volume
19 from .extensions
.gltf2_blender_gather_materials_emission
import export_emission_factor
, \
20 export_emission_texture
, export_emission_strength_extension
21 from .extensions
.gltf2_blender_gather_materials_sheen
import export_sheen
22 from .extensions
.gltf2_blender_gather_materials_specular
import export_specular
23 from .extensions
.gltf2_blender_gather_materials_transmission
import export_transmission
24 from .extensions
.gltf2_blender_gather_materials_clearcoat
import export_clearcoat
25 from .extensions
.gltf2_blender_gather_materials_ior
import export_ior
29 def get_material_cache_key(blender_material
, active_uvmap_index
, export_settings
):
31 # Do not use bpy.types that can be unhashable
32 # Do not use material name, that can be not unique (when linked)
34 (id(blender_material
),),
38 @cached_by_key(key
=get_material_cache_key
)
39 def gather_material(blender_material
, active_uvmap_index
, export_settings
):
41 Gather the material used by the blender primitive.
43 :param blender_material: the blender material used in the glTF primitive
44 :param export_settings:
45 :return: a glTF material
47 if not __filter_material(blender_material
, export_settings
):
50 mat_unlit
= __export_unlit(blender_material
, active_uvmap_index
, export_settings
)
51 if mat_unlit
is not None:
52 export_user_extensions('gather_material_hook', export_settings
, mat_unlit
, blender_material
)
55 orm_texture
, default_sockets
= __gather_orm_texture(blender_material
, export_settings
)
57 emissive_factor
= __gather_emissive_factor(blender_material
, export_settings
)
58 emissive_texture
, uvmap_actives_emissive_texture
= __gather_emissive_texture(blender_material
, export_settings
)
59 extensions
, uvmap_actives_extensions
= __gather_extensions(blender_material
, emissive_factor
, export_settings
)
60 normal_texture
, uvmap_actives_normal_texture
= __gather_normal_texture(blender_material
, export_settings
)
61 occlusion_texture
, uvmap_actives_occlusion_texture
= __gather_occlusion_texture(blender_material
, orm_texture
, default_sockets
, export_settings
)
62 pbr_metallic_roughness
, uvmap_actives_pbr_metallic_roughness
= __gather_pbr_metallic_roughness(blender_material
, orm_texture
, export_settings
)
64 if any([i
>1.0 for i
in emissive_factor
or []]) is True:
65 # Strength is set on extension
66 emission_strength
= max(emissive_factor
)
67 emissive_factor
= [f
/ emission_strength
for f
in emissive_factor
]
70 base_material
= gltf2_io
.Material(
71 alpha_cutoff
=__gather_alpha_cutoff(blender_material
, export_settings
),
72 alpha_mode
=__gather_alpha_mode(blender_material
, export_settings
),
73 double_sided
=__gather_double_sided(blender_material
, extensions
, export_settings
),
74 emissive_factor
=emissive_factor
,
75 emissive_texture
=emissive_texture
,
76 extensions
=extensions
,
77 extras
=__gather_extras(blender_material
, export_settings
),
78 name
=__gather_name(blender_material
, export_settings
),
79 normal_texture
=normal_texture
,
80 occlusion_texture
=occlusion_texture
,
81 pbr_metallic_roughness
=pbr_metallic_roughness
85 # merge all uvmap_actives
87 if uvmap_actives_emissive_texture
:
88 uvmap_actives
.extend(uvmap_actives_emissive_texture
)
89 if uvmap_actives_extensions
:
90 uvmap_actives
.extend(uvmap_actives_extensions
)
91 if uvmap_actives_normal_texture
:
92 uvmap_actives
.extend(uvmap_actives_normal_texture
)
93 if uvmap_actives_occlusion_texture
:
94 uvmap_actives
.extend(uvmap_actives_occlusion_texture
)
95 if uvmap_actives_pbr_metallic_roughness
:
96 uvmap_actives
.extend(uvmap_actives_pbr_metallic_roughness
)
98 # Because some part of material are shared (eg pbr_metallic_roughness), we must copy the material
99 # Texture must be shared, but not TextureInfo
100 material
= deepcopy(base_material
)
101 __get_new_material_texture_shared(base_material
, material
)
103 active_uvmap_index
= active_uvmap_index
if active_uvmap_index
!= 0 else None
105 for tex
in uvmap_actives
:
106 if tex
== "emissiveTexture":
107 material
.emissive_texture
.tex_coord
= active_uvmap_index
108 elif tex
== "normalTexture":
109 material
.normal_texture
.tex_coord
= active_uvmap_index
110 elif tex
== "occlusionTexture":
111 material
.occlusion_texture
.tex_coord
= active_uvmap_index
112 elif tex
== "baseColorTexture":
113 material
.pbr_metallic_roughness
.base_color_texture
.tex_coord
= active_uvmap_index
114 elif tex
== "metallicRoughnessTexture":
115 material
.pbr_metallic_roughness
.metallic_roughness_texture
.tex_coord
= active_uvmap_index
116 elif tex
== "clearcoatTexture":
117 material
.extensions
["KHR_materials_clearcoat"].extension
['clearcoatTexture'].tex_coord
= active_uvmap_index
118 elif tex
== "clearcoatRoughnessTexture":
119 material
.extensions
["KHR_materials_clearcoat"].extension
['clearcoatRoughnessTexture'].tex_coord
= active_uvmap_index
120 elif tex
== "clearcoatNormalTexture": #TODO not tested yet
121 material
.extensions
["KHR_materials_clearcoat"].extension
['clearcoatNormalTexture'].tex_coord
= active_uvmap_index
122 elif tex
== "transmissionTexture": #TODO not tested yet
123 material
.extensions
["KHR_materials_transmission"].extension
['transmissionTexture'].tex_coord
= active_uvmap_index
124 elif tex
== "specularTexture":
125 material
.extensions
["KHR_materials_specular"].extension
['specularTexture'].tex_coord
= active_uvmap_index
126 elif tex
== "specularColorTexture":
127 material
.extensions
["KHR_materials_specular"].extension
['specularColorTexture'].tex_coord
= active_uvmap_index
128 elif tex
== "sheenColorTexture":
129 material
.extensions
["KHR_materials_sheen"].extension
['sheenColorTexture'].tex_coord
= active_uvmap_index
130 elif tex
== "sheenRoughnessTexture":
131 material
.extensions
["KHR_materials_sheen"].extension
['sheenRoughnessTexture'].tex_coord
= active_uvmap_index
133 # If material is not using active UVMap, we need to return the same material,
134 # Even if multiples meshes are using different active UVMap
135 if len(uvmap_actives
) == 0 and active_uvmap_index
!= -1:
136 material
= gather_material(blender_material
, -1, export_settings
)
139 # If emissive is set, from an emissive node (not PBR)
140 # We need to set manually default values for
141 # pbr_metallic_roughness.baseColor
142 if material
.emissive_factor
is not None and gltf2_blender_get
.get_node_socket(blender_material
, bpy
.types
.ShaderNodeBsdfPrincipled
, "Base Color") is None:
143 material
.pbr_metallic_roughness
= gltf2_blender_gather_materials_pbr_metallic_roughness
.get_default_pbr_for_emissive_node()
145 export_user_extensions('gather_material_hook', export_settings
, material
, blender_material
)
148 # material = blender_primitive['material']
150 # if get_material_requires_texcoords(glTF, material) and not export_settings['gltf_texcoords']:
153 # if get_material_requires_normals(glTF, material) and not export_settings['gltf_normals']:
156 # # Meshes/primitives without material are allowed.
158 # primitive.material = material
160 # print_console('WARNING', 'Material ' + internal_primitive[
161 # 'material'] + ' not found. Please assign glTF 2.0 material or enable Blinn-Phong material in export.')
164 def __get_new_material_texture_shared(base
, node
):
167 if callable(node
) is True:
169 if node
.__str
__().startswith('__'):
171 if type(node
) in [gltf2_io
.TextureInfo
, gltf2_io
.MaterialOcclusionTextureInfoClass
, gltf2_io
.MaterialNormalTextureInfoClass
]:
172 node
.index
= base
.index
174 if hasattr(node
, '__dict__'):
175 for attr
, value
in node
.__dict
__.items():
176 __get_new_material_texture_shared(getattr(base
, attr
), value
)
178 # For extensions (on a dict)
179 if type(node
).__name
__ == 'dict':
180 for i
in node
.keys():
181 __get_new_material_texture_shared(base
[i
], node
[i
])
183 def __filter_material(blender_material
, export_settings
):
184 return export_settings
['gltf_materials']
187 def __gather_alpha_cutoff(blender_material
, export_settings
):
188 if blender_material
.blend_method
== 'CLIP':
189 return blender_material
.alpha_threshold
193 def __gather_alpha_mode(blender_material
, export_settings
):
194 if blender_material
.blend_method
== 'CLIP':
196 elif blender_material
.blend_method
in ['BLEND', 'HASHED']:
201 def __gather_double_sided(blender_material
, extensions
, export_settings
):
203 # If user create a volume extension, we force double sided to False
204 if 'KHR_materials_volume' in extensions
:
207 if not blender_material
.use_backface_culling
:
210 old_double_sided_socket
= gltf2_blender_get
.get_socket_old(blender_material
, "DoubleSided")
211 if old_double_sided_socket
is not None and\
212 not old_double_sided_socket
.is_linked
and\
213 old_double_sided_socket
.default_value
> 0.5:
218 def __gather_emissive_factor(blender_material
, export_settings
):
219 return export_emission_factor(blender_material
, export_settings
)
221 def __gather_emissive_texture(blender_material
, export_settings
):
222 return export_emission_texture(blender_material
, export_settings
)
225 def __gather_extensions(blender_material
, emissive_factor
, export_settings
):
228 # KHR_materials_clearcoat
231 clearcoat_extension
, use_actives_uvmap_clearcoat
= export_clearcoat(blender_material
, export_settings
)
232 if clearcoat_extension
:
233 extensions
["KHR_materials_clearcoat"] = clearcoat_extension
234 actives_uvmaps
.extend(use_actives_uvmap_clearcoat
)
236 # KHR_materials_transmission
238 transmission_extension
, use_actives_uvmap_transmission
= export_transmission(blender_material
, export_settings
)
239 if transmission_extension
:
240 extensions
["KHR_materials_transmission"] = transmission_extension
241 actives_uvmaps
.extend(use_actives_uvmap_transmission
)
243 # KHR_materials_emissive_strength
244 if any([i
>1.0 for i
in emissive_factor
or []]):
245 emissive_strength_extension
= export_emission_strength_extension(emissive_factor
, export_settings
)
246 if emissive_strength_extension
:
247 extensions
["KHR_materials_emissive_strength"] = emissive_strength_extension
249 # KHR_materials_volume
251 volume_extension
, use_actives_uvmap_volume_thickness
= export_volume(blender_material
, export_settings
)
253 extensions
["KHR_materials_volume"] = volume_extension
254 actives_uvmaps
.extend(use_actives_uvmap_volume_thickness
)
256 # KHR_materials_specular
257 specular_extension
, use_actives_uvmap_specular
= export_specular(blender_material
, export_settings
)
258 if specular_extension
:
259 extensions
["KHR_materials_specular"] = specular_extension
260 actives_uvmaps
.extend(use_actives_uvmap_specular
)
262 # KHR_materials_sheen
263 sheen_extension
, use_actives_uvmap_sheen
= export_sheen(blender_material
, export_settings
)
265 extensions
["KHR_materials_sheen"] = sheen_extension
266 actives_uvmaps
.extend(use_actives_uvmap_sheen
)
269 # Keep this extension at the end, because we export it only if some others are exported
270 ior_extension
= export_ior(blender_material
, extensions
, export_settings
)
272 extensions
["KHR_materials_ior"] = ior_extension
274 return extensions
, actives_uvmaps
if extensions
else None
277 def __gather_extras(blender_material
, export_settings
):
278 if export_settings
['gltf_extras']:
279 return generate_extras(blender_material
)
283 def __gather_name(blender_material
, export_settings
):
284 return blender_material
.name
287 def __gather_normal_texture(blender_material
, export_settings
):
288 normal
= gltf2_blender_get
.get_socket(blender_material
, "Normal")
290 normal
= gltf2_blender_get
.get_socket_old(blender_material
, "Normal")
291 normal_texture
, use_active_uvmap_normal
, _
= gltf2_blender_gather_texture_info
.gather_material_normal_texture_info_class(
295 return normal_texture
, ["normalTexture"] if use_active_uvmap_normal
else None
298 def __gather_orm_texture(blender_material
, export_settings
):
299 # Check for the presence of Occlusion, Roughness, Metallic sharing a single image.
300 # If not fully shared, return None, so the images will be cached and processed separately.
302 occlusion
= gltf2_blender_get
.get_socket(blender_material
, "Occlusion")
303 if occlusion
is None or not gltf2_blender_get
.has_image_node_from_socket(occlusion
):
304 occlusion
= gltf2_blender_get
.get_socket_old(blender_material
, "Occlusion")
305 if occlusion
is None or not gltf2_blender_get
.has_image_node_from_socket(occlusion
):
308 metallic_socket
= gltf2_blender_get
.get_socket(blender_material
, "Metallic")
309 roughness_socket
= gltf2_blender_get
.get_socket(blender_material
, "Roughness")
311 hasMetal
= metallic_socket
is not None and gltf2_blender_get
.has_image_node_from_socket(metallic_socket
)
312 hasRough
= roughness_socket
is not None and gltf2_blender_get
.has_image_node_from_socket(roughness_socket
)
315 if not hasMetal
and not hasRough
:
316 metallic_roughness
= gltf2_blender_get
.get_socket_old(blender_material
, "MetallicRoughness")
317 if metallic_roughness
is None or not gltf2_blender_get
.has_image_node_from_socket(metallic_roughness
):
318 return None, default_sockets
319 result
= (occlusion
, metallic_roughness
)
321 result
= (occlusion
, roughness_socket
)
322 default_sockets
= (metallic_socket
,)
324 result
= (occlusion
, metallic_socket
)
325 default_sockets
= (roughness_socket
,)
327 result
= (occlusion
, roughness_socket
, metallic_socket
)
330 if not gltf2_blender_gather_texture_info
.check_same_size_images(result
):
331 print_console("INFO",
332 "Occlusion and metal-roughness texture will be exported separately "
333 "(use same-sized images if you want them combined)")
336 # Double-check this will past the filter in texture_info
337 info
, info_use_active_uvmap
, _
= gltf2_blender_gather_texture_info
.gather_texture_info(result
[0], result
, default_sockets
, export_settings
)
341 return result
, default_sockets
343 def __gather_occlusion_texture(blender_material
, orm_texture
, default_sockets
, export_settings
):
344 occlusion
= gltf2_blender_get
.get_socket(blender_material
, "Occlusion")
345 if occlusion
is None:
346 occlusion
= gltf2_blender_get
.get_socket_old(blender_material
, "Occlusion")
347 occlusion_texture
, use_active_uvmap_occlusion
, _
= gltf2_blender_gather_texture_info
.gather_material_occlusion_texture_info_class(
349 orm_texture
or (occlusion
,),
352 return occlusion_texture
, ["occlusionTexture"] if use_active_uvmap_occlusion
else None
355 def __gather_pbr_metallic_roughness(blender_material
, orm_texture
, export_settings
):
356 return gltf2_blender_gather_materials_pbr_metallic_roughness
.gather_material_pbr_metallic_roughness(
361 def __export_unlit(blender_material
, active_uvmap_index
, export_settings
):
362 gltf2_unlit
= gltf2_blender_gather_materials_unlit
364 info
= gltf2_unlit
.detect_shadeless_material(blender_material
, export_settings
)
368 base_color_texture
, use_active_uvmap
= gltf2_unlit
.gather_base_color_texture(info
, export_settings
)
370 base_material
= gltf2_io
.Material(
371 alpha_cutoff
=__gather_alpha_cutoff(blender_material
, export_settings
),
372 alpha_mode
=__gather_alpha_mode(blender_material
, export_settings
),
373 double_sided
=__gather_double_sided(blender_material
, {}, export_settings
),
374 extensions
={"KHR_materials_unlit": Extension("KHR_materials_unlit", {}, required
=False)},
375 extras
=__gather_extras(blender_material
, export_settings
),
376 name
=__gather_name(blender_material
, export_settings
),
377 emissive_factor
=None,
378 emissive_texture
=None,
380 occlusion_texture
=None,
382 pbr_metallic_roughness
=gltf2_io
.MaterialPBRMetallicRoughness(
383 base_color_factor
=gltf2_unlit
.gather_base_color_factor(info
, export_settings
),
384 base_color_texture
=base_color_texture
,
386 roughness_factor
=0.9,
387 metallic_roughness_texture
=None,
393 if use_active_uvmap
is not None:
394 # Because some part of material are shared (eg pbr_metallic_roughness), we must copy the material
395 # Texture must be shared, but not TextureInfo
396 material
= deepcopy(base_material
)
397 __get_new_material_texture_shared(base_material
, material
)
398 material
.pbr_metallic_roughness
.base_color_texture
.tex_coord
= active_uvmap_index
399 elif use_active_uvmap
is None and active_uvmap_index
!= -1:
400 # If material is not using active UVMap, we need to return the same material,
401 # Even if multiples meshes are using different active UVMap
402 material
= gather_material(blender_material
, -1, export_settings
)
404 material
= base_material
406 export_user_extensions('gather_material_unlit_hook', export_settings
, material
, blender_material
)