glTF exporter: Texture: Use default value when merging channels
[blender-addons.git] / io_scene_gltf2 / blender / exp / material / gltf2_blender_gather_materials.py
blob9762ed9d5dfa4f71d025377bd9bf852f348fde7b
1 # SPDX-FileCopyrightText: 2018-2022 The glTF-Blender-IO authors
3 # SPDX-License-Identifier: Apache-2.0
5 from copy import deepcopy
6 import bpy
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
28 @cached
29 def get_material_cache_key(blender_material, active_uvmap_index, export_settings):
30 # Use id of material
31 # Do not use bpy.types that can be unhashable
32 # Do not use material name, that can be not unique (when linked)
33 return (
34 (id(blender_material),),
35 (active_uvmap_index,)
38 @cached_by_key(key=get_material_cache_key)
39 def gather_material(blender_material, active_uvmap_index, export_settings):
40 """
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
46 """
47 if not __filter_material(blender_material, export_settings):
48 return None
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)
53 return mat_unlit
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
86 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)
147 return material
148 # material = blender_primitive['material']
150 # if get_material_requires_texcoords(glTF, material) and not export_settings['gltf_texcoords']:
151 # material = -1
153 # if get_material_requires_normals(glTF, material) and not export_settings['gltf_normals']:
154 # material = -1
156 # # Meshes/primitives without material are allowed.
157 # if material >= 0:
158 # primitive.material = material
159 # else:
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):
165 if node is None:
166 return
167 if callable(node) is True:
168 return
169 if node.__str__().startswith('__'):
170 return
171 if type(node) in [gltf2_io.TextureInfo, gltf2_io.MaterialOcclusionTextureInfoClass, gltf2_io.MaterialNormalTextureInfoClass]:
172 node.index = base.index
173 else:
174 if hasattr(node, '__dict__'):
175 for attr, value in node.__dict__.items():
176 __get_new_material_texture_shared(getattr(base, attr), value)
177 else:
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
190 return None
193 def __gather_alpha_mode(blender_material, export_settings):
194 if blender_material.blend_method == 'CLIP':
195 return 'MASK'
196 elif blender_material.blend_method in ['BLEND', 'HASHED']:
197 return 'BLEND'
198 return None
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:
205 return False
207 if not blender_material.use_backface_culling:
208 return True
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:
214 return True
215 return None
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):
226 extensions = {}
228 # KHR_materials_clearcoat
229 actives_uvmaps = []
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)
252 if volume_extension:
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)
264 if sheen_extension:
265 extensions["KHR_materials_sheen"] = sheen_extension
266 actives_uvmaps.extend(use_actives_uvmap_sheen)
268 # KHR_materials_ior
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)
271 if ior_extension:
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)
280 return None
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")
289 if normal is None:
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(
292 normal,
293 (normal,),
294 export_settings)
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):
306 return None, None
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)
314 default_sockets = ()
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)
320 elif not hasMetal:
321 result = (occlusion, roughness_socket)
322 default_sockets = (metallic_socket,)
323 elif not hasRough:
324 result = (occlusion, metallic_socket)
325 default_sockets = (roughness_socket,)
326 else:
327 result = (occlusion, roughness_socket, metallic_socket)
328 default_sockets = ()
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)")
334 return None, ()
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)
338 if info is None:
339 return None, ()
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(
348 occlusion,
349 orm_texture or (occlusion,),
350 default_sockets,
351 export_settings)
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(
357 blender_material,
358 orm_texture,
359 export_settings)
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)
365 if info is None:
366 return None
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,
379 normal_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,
385 metallic_factor=0.0,
386 roughness_factor=0.9,
387 metallic_roughness_texture=None,
388 extensions=None,
389 extras=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)
403 else:
404 material = base_material
406 export_user_extensions('gather_material_unlit_hook', export_settings, material, blender_material)
408 return material