glTF exporter: Manage active UVMap correclty, when there is no UVMap used in node tree

This commit is contained in:
Julien Duroure 2022-03-13 11:44:02 +01:00
parent 4d8b2dc95f
commit 3a29996583
6 changed files with 170 additions and 43 deletions

View File

@ -4,7 +4,7 @@
bl_info = {
'name': 'glTF 2.0 format',
'author': 'Julien Duroure, Scurest, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein, and many external contributors',
"version": (3, 2, 14),
"version": (3, 2, 15),
'blender': (3, 1, 0),
'location': 'File > Import-Export',
'description': 'Import-Export as glTF 2.0',

View File

@ -1,6 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
# Copyright 2018-2021 The glTF-Blender-IO authors.
from copy import deepcopy
import bpy
from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached, cached_by_key
@ -17,14 +18,17 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
from io_scene_gltf2.io.com.gltf2_io_debug import print_console
@cached
def get_material_cache_key(blender_material, export_settings):
def get_material_cache_key(blender_material, active_uvmap_index, export_settings):
# Use id of material
# Do not use bpy.types that can be unhashable
# Do not use material name, that can be not unique (when linked)
return ((id(blender_material),))
return (
(id(blender_material),),
(active_uvmap_index,)
)
@cached_by_key(key=get_material_cache_key)
def gather_material(blender_material, export_settings):
def gather_material(blender_material, active_uvmap_index, export_settings):
"""
Gather the material used by the blender primitive.
@ -35,26 +39,79 @@ def gather_material(blender_material, export_settings):
if not __filter_material(blender_material, export_settings):
return None
mat_unlit = __gather_material_unlit(blender_material, export_settings)
mat_unlit = __gather_material_unlit(blender_material, active_uvmap_index, export_settings)
if mat_unlit is not None:
return mat_unlit
orm_texture = __gather_orm_texture(blender_material, export_settings)
material = gltf2_io.Material(
emissive_texture, uvmap_actives_emissive_texture = __gather_emissive_texture(blender_material, export_settings)
extensions, uvmap_actives_extensions = __gather_extensions(blender_material, export_settings)
normal_texture, uvmap_actives_normal_texture = __gather_normal_texture(blender_material, export_settings)
occlusion_texture, uvmap_actives_occlusion_texture = __gather_occlusion_texture(blender_material, orm_texture, export_settings)
pbr_metallic_roughness, uvmap_actives_pbr_metallic_roughness = __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
base_material = gltf2_io.Material(
alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
alpha_mode=__gather_alpha_mode(blender_material, export_settings),
double_sided=__gather_double_sided(blender_material, export_settings),
emissive_factor=__gather_emissive_factor(blender_material, export_settings),
emissive_texture=__gather_emissive_texture(blender_material, export_settings),
extensions=__gather_extensions(blender_material, export_settings),
emissive_texture=emissive_texture,
extensions=extensions,
extras=__gather_extras(blender_material, export_settings),
name=__gather_name(blender_material, export_settings),
normal_texture=__gather_normal_texture(blender_material, export_settings),
occlusion_texture=__gather_occlusion_texture(blender_material, orm_texture, export_settings),
pbr_metallic_roughness=__gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings)
normal_texture=normal_texture,
occlusion_texture=occlusion_texture,
pbr_metallic_roughness=pbr_metallic_roughness
)
# merge all uvmap_actives
uvmap_actives = []
if uvmap_actives_emissive_texture:
uvmap_actives.extend(uvmap_actives_emissive_texture)
if uvmap_actives_extensions:
uvmap_actives.extend(uvmap_actives_extensions)
if uvmap_actives_normal_texture:
uvmap_actives.extend(uvmap_actives_normal_texture)
if uvmap_actives_occlusion_texture:
uvmap_actives.extend(uvmap_actives_occlusion_texture)
if uvmap_actives_pbr_metallic_roughness:
uvmap_actives.extend(uvmap_actives_pbr_metallic_roughness)
# Because some part of material are shared (eg pbr_metallic_roughness), we must copy the material
# Texture must be shared, but not TextureInfo
material = deepcopy(base_material)
__get_new_material_texture_shared(base_material, material)
active_uvmap_index = active_uvmap_index if active_uvmap_index != 0 else None
for tex in uvmap_actives:
if tex == "emissiveTexture":
material.emissive_texture.tex_coord = active_uvmap_index
elif tex == "normalTexture":
material.normal_texture.tex_coord = active_uvmap_index
elif tex == "occlusionTexture":
material.occlusion_texture.tex_coord = active_uvmap_index
elif tex == "baseColorTexture":
material.pbr_metallic_roughness.base_color_texture.tex_coord = active_uvmap_index
elif tex == "metallicRoughnessTexture":
material.pbr_metallic_roughness.metallic_roughness_texture.tex_coord = active_uvmap_index
elif tex == "clearcoatTexture":
material.extensions["KHR_materials_clearcoat"].extension['clearcoatTexture'].tex_coord = active_uvmap_index
elif tex == "clearcoatRoughnessTexture":
material.extensions["KHR_materials_clearcoat"].extension['clearcoatRoughnessTexture'].tex_coord = active_uvmap_index
elif tex == "clearcoatNormalTexture": #TODO not tested yet
material.extensions["KHR_materials_clearcoat"].extension['clearcoatNormalTexture'].tex_coord = active_uvmap_index
elif tex == "transmissionTexture": #TODO not tested yet
material.extensions["KHR_materials_transmission"].extension['transmissionTexture'].tex_coord = active_uvmap_index
# If material is not using active UVMap, we need to return the same material,
# Even if multiples meshes are using different active UVMap
if len(uvmap_actives) == 0 and active_uvmap_index != -1:
material = gather_material(blender_material, -1, export_settings)
# If emissive is set, from an emissive node (not PBR)
# We need to set manually default values for
# pbr_metallic_roughness.baseColor
@ -80,6 +137,25 @@ def gather_material(blender_material, export_settings):
# 'material'] + ' not found. Please assign glTF 2.0 material or enable Blinn-Phong material in export.')
def __get_new_material_texture_shared(base, node):
if node is None:
return
if callable(node) is True:
return
if node.__str__().startswith('__'):
return
if type(node) in [gltf2_io.TextureInfo, gltf2_io.MaterialOcclusionTextureInfoClass, gltf2_io.MaterialNormalTextureInfoClass]:
node.index = base.index
else:
if hasattr(node, '__dict__'):
for attr, value in node.__dict__.items():
__get_new_material_texture_shared(getattr(base, attr), value)
else:
# For extensions (on a dict)
if type(node).__name__ == 'dict':
for i in node.keys():
__get_new_material_texture_shared(base[i], node[i])
def __filter_material(blender_material, export_settings):
return export_settings[gltf2_blender_export_keys.MATERIALS]
@ -155,17 +231,20 @@ def __gather_emissive_texture(blender_material, export_settings):
emissive = gltf2_blender_get.get_socket(blender_material, "Emissive")
if emissive is None:
emissive = gltf2_blender_get.get_socket_old(blender_material, "Emissive")
return gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
emissive_texture, use_actives_uvmap_emissive = gltf2_blender_gather_texture_info.gather_texture_info(emissive, (emissive,), export_settings)
return emissive_texture, ["emissiveTexture"] if use_actives_uvmap_emissive else None
def __gather_extensions(blender_material, export_settings):
extensions = {}
# KHR_materials_clearcoat
actives_uvmaps = []
clearcoat_extension = __gather_clearcoat_extension(blender_material, export_settings)
clearcoat_extension, use_actives_uvmap_clearcoat = __gather_clearcoat_extension(blender_material, export_settings)
if clearcoat_extension:
extensions["KHR_materials_clearcoat"] = clearcoat_extension
actives_uvmaps.extend(use_actives_uvmap_clearcoat)
# KHR_materials_transmission
@ -173,7 +252,7 @@ def __gather_extensions(blender_material, export_settings):
if transmission_extension:
extensions["KHR_materials_transmission"] = transmission_extension
return extensions if extensions else None
return extensions, actives_uvmaps if extensions else None
def __gather_extras(blender_material, export_settings):
@ -190,10 +269,11 @@ def __gather_normal_texture(blender_material, export_settings):
normal = gltf2_blender_get.get_socket(blender_material, "Normal")
if normal is None:
normal = gltf2_blender_get.get_socket_old(blender_material, "Normal")
return gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
normal_texture, use_active_uvmap_normal = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
normal,
(normal,),
export_settings)
return normal_texture, ["normalTexture"] if use_active_uvmap_normal else None
def __gather_orm_texture(blender_material, export_settings):
@ -231,7 +311,7 @@ def __gather_orm_texture(blender_material, export_settings):
return None
# Double-check this will past the filter in texture_info
info = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
info, info_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(result[0], result, export_settings)
if info is None:
return None
@ -241,10 +321,11 @@ def __gather_occlusion_texture(blender_material, orm_texture, export_settings):
occlusion = gltf2_blender_get.get_socket(blender_material, "Occlusion")
if occlusion is None:
occlusion = gltf2_blender_get.get_socket_old(blender_material, "Occlusion")
return gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
occlusion_texture, use_active_uvmap_occlusion = gltf2_blender_gather_texture_info.gather_material_occlusion_texture_info_class(
occlusion,
orm_texture or (occlusion,),
export_settings)
return occlusion_texture, ["occlusionTexture"] if use_active_uvmap_occlusion else None
def __gather_pbr_metallic_roughness(blender_material, orm_texture, export_settings):
@ -284,7 +365,7 @@ def __gather_clearcoat_extension(blender_material, export_settings):
clearcoat_enabled = True
if not clearcoat_enabled:
return None
return None, None
if isinstance(clearcoat_roughness_socket, bpy.types.NodeSocket) and not clearcoat_roughness_socket.is_linked:
clearcoat_extension['clearcoatRoughnessFactor'] = clearcoat_roughness_socket.default_value
@ -302,28 +383,38 @@ def __gather_clearcoat_extension(blender_material, export_settings):
elif has_clearcoat_roughness_texture:
clearcoat_roughness_slots = (clearcoat_roughness_socket,)
use_actives_uvmaps = []
if len(clearcoat_roughness_slots) > 0:
if has_clearcoat_texture:
clearcoat_extension['clearcoatTexture'] = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_texture, clearcoat_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_socket,
clearcoat_roughness_slots,
export_settings,
)
clearcoat_extension['clearcoatTexture'] = clearcoat_texture
if clearcoat_texture_use_active_uvmap:
use_actives_uvmaps.append("clearcoatTexture")
if has_clearcoat_roughness_texture:
clearcoat_extension['clearcoatRoughnessTexture'] = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_roughness_texture, clearcoat_roughness_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
clearcoat_roughness_socket,
clearcoat_roughness_slots,
export_settings,
)
clearcoat_extension['clearcoatRoughnessTexture'] = clearcoat_roughness_texture
if clearcoat_roughness_texture_use_active_uvmap:
use_actives_uvmaps.append("clearcoatRoughnessTexture")
if __has_image_node_from_socket(clearcoat_normal_socket):
clearcoat_extension['clearcoatNormalTexture'] = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
clearcoat_normal_texture, clearcoat_normal_texture_use_active_uvmap = gltf2_blender_gather_texture_info.gather_material_normal_texture_info_class(
clearcoat_normal_socket,
(clearcoat_normal_socket,),
export_settings
)
clearcoat_extension['clearcoatNormalTexture'] = clearcoat_normal_texture
if clearcoat_normal_texture_use_active_uvmap:
use_actives_uvmaps.append("clearcoatNormalTexture")
return Extension('KHR_materials_clearcoat', clearcoat_extension, False)
return Extension('KHR_materials_clearcoat', clearcoat_extension, False), use_actives_uvmaps
def __gather_transmission_extension(blender_material, export_settings):
transmission_enabled = False
@ -350,7 +441,7 @@ def __gather_transmission_extension(blender_material, export_settings):
transmission_slots = (transmission_socket,)
if len(transmission_slots) > 0:
combined_texture = gltf2_blender_gather_texture_info.gather_texture_info(
combined_texture, use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
transmission_socket,
transmission_slots,
export_settings,
@ -358,17 +449,19 @@ def __gather_transmission_extension(blender_material, export_settings):
if has_transmission_texture:
transmission_extension['transmissionTexture'] = combined_texture
return Extension('KHR_materials_transmission', transmission_extension, False)
return Extension('KHR_materials_transmission', transmission_extension, False), ["transmissionTexture"] if use_active_uvmap else []
def __gather_material_unlit(blender_material, export_settings):
def __gather_material_unlit(blender_material, active_uvmap_index, export_settings):
gltf2_unlit = gltf2_blender_gather_materials_unlit
info = gltf2_unlit.detect_shadeless_material(blender_material, export_settings)
if info is None:
return None
material = gltf2_io.Material(
base_color_texture, use_active_uvmap = gltf2_unlit.gather_base_color_texture(info, export_settings)
base_material = gltf2_io.Material(
alpha_cutoff=__gather_alpha_cutoff(blender_material, export_settings),
alpha_mode=__gather_alpha_mode(blender_material, export_settings),
double_sided=__gather_double_sided(blender_material, export_settings),
@ -382,7 +475,7 @@ def __gather_material_unlit(blender_material, export_settings):
pbr_metallic_roughness=gltf2_io.MaterialPBRMetallicRoughness(
base_color_factor=gltf2_unlit.gather_base_color_factor(info, export_settings),
base_color_texture=gltf2_unlit.gather_base_color_texture(info, export_settings),
base_color_texture=base_color_texture,
metallic_factor=0.0,
roughness_factor=0.9,
metallic_roughness_texture=None,
@ -391,6 +484,19 @@ def __gather_material_unlit(blender_material, export_settings):
)
)
if use_active_uvmap is not None:
# Because some part of material are shared (eg pbr_metallic_roughness), we must copy the material
# Texture must be shared, but not TextureInfo
material = deepcopy(base_material)
__get_new_material_texture_shared(base_material, material)
material.pbr_metallic_roughness.base_color_texture.tex_coord = active_uvmap_index
elif use_active_uvmap is None and active_uvmap_index != -1:
# If material is not using active UVMap, we need to return the same material,
# Even if multiples meshes are using different active UVMap
material = gather_material(blender_material, -1, export_settings)
else:
material = base_material
export_user_extensions('gather_material_unlit_hook', export_settings, material, blender_material)
return material

View File

@ -13,21 +13,31 @@ from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extension
@cached
def gather_material_pbr_metallic_roughness(blender_material, orm_texture, export_settings):
if not __filter_pbr_material(blender_material, export_settings):
return None
return None, None
base_color_texture, use_active_uvmap_base_color_texture = __gather_base_color_texture(blender_material, export_settings)
metallic_roughness_texture, use_active_uvmap_metallic_roughness_texture = __gather_metallic_roughness_texture(blender_material, orm_texture, export_settings)
material = gltf2_io.MaterialPBRMetallicRoughness(
base_color_factor=__gather_base_color_factor(blender_material, export_settings),
base_color_texture=__gather_base_color_texture(blender_material, export_settings),
base_color_texture=base_color_texture,
extensions=__gather_extensions(blender_material, export_settings),
extras=__gather_extras(blender_material, export_settings),
metallic_factor=__gather_metallic_factor(blender_material, export_settings),
metallic_roughness_texture=__gather_metallic_roughness_texture(blender_material, orm_texture, export_settings),
metallic_roughness_texture=metallic_roughness_texture,
roughness_factor=__gather_roughness_factor(blender_material, export_settings)
)
# merge all use_active_uvmap infos
uvmap_actives = []
if use_active_uvmap_base_color_texture is True:
uvmap_actives.append("baseColorTexture")
if use_active_uvmap_metallic_roughness_texture is True:
uvmap_actives.append("metallicRoughnessTexture")
export_user_extensions('gather_material_pbr_metallic_roughness_hook', export_settings, material, blender_material, orm_texture)
return material
return material, uvmap_actives
def __filter_pbr_material(blender_material, export_settings):
@ -82,7 +92,7 @@ def __gather_base_color_texture(blender_material, export_settings):
if socket is not None and __has_image_node_from_socket(socket)
)
if not inputs:
return None
return None, None
return gltf2_blender_gather_texture_info.gather_texture_info(inputs[0], inputs, export_settings)
@ -118,7 +128,7 @@ def __gather_metallic_roughness_texture(blender_material, orm_texture, export_se
if not hasMetal and not hasRough:
metallic_roughness = gltf2_blender_get.get_socket_old(blender_material, "MetallicRoughness")
if metallic_roughness is None or not __has_image_node_from_socket(metallic_roughness):
return None
return None, None
texture_input = (metallic_roughness,)
elif not hasMetal:
texture_input = (roughness_socket,)

View File

@ -127,9 +127,10 @@ def gather_base_color_texture(info, export_settings):
# because gather_image determines how to pack images based on the
# names of sockets, and the names are hard-coded to a Principled
# style graph.
return gltf2_blender_gather_texture_info.gather_texture_info(
unlit_texture, unlit_use_active_uvmap = gltf2_blender_gather_texture_info.gather_texture_info(
sockets[0],
sockets,
export_settings,
)
return None
return unlit_texture, ["unlitTexture"] if unlit_use_active_uvmap else None
return None, None

View File

@ -57,6 +57,13 @@ def gather_primitives(
"""
primitives = []
# retrieve active render UVMap
active_uvmap_idx = 0
for i in range(len(blender_mesh.uv_layers)):
if blender_mesh.uv_layers[i].active_render is True:
active_uvmap_idx = i
break
blender_primitives = __gather_cache_primitives(blender_mesh, uuid_for_skined_data,
vertex_groups, modifiers, export_settings)
@ -73,7 +80,8 @@ def gather_primitives(
if mat is not None:
material = gltf2_blender_gather_materials.gather_material(
mat,
export_settings,
active_uvmap_idx,
export_settings
)
primitive = gltf2_io.MeshPrimitive(

View File

@ -36,15 +36,15 @@ def __gather_texture_info_helper(
kind: str,
export_settings):
if not __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
return None
return None, None
tex_transform, tex_coord = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
tex_transform, tex_coord, use_active_uvmap = __gather_texture_transform_and_tex_coord(primary_socket, export_settings)
fields = {
'extensions': __gather_extensions(tex_transform, export_settings),
'extras': __gather_extras(blender_shader_sockets, export_settings),
'index': __gather_index(blender_shader_sockets, export_settings),
'tex_coord': tex_coord,
'tex_coord': tex_coord
}
if kind == 'DEFAULT':
@ -59,11 +59,11 @@ def __gather_texture_info_helper(
texture_info = gltf2_io.MaterialOcclusionTextureInfoClass(**fields)
if texture_info.index is None:
return None
return None, None
export_user_extensions('gather_texture_info_hook', export_settings, texture_info, blender_shader_sockets)
return texture_info
return texture_info, use_active_uvmap
def __filter_texture_info(primary_socket, blender_shader_sockets, export_settings):
@ -151,15 +151,17 @@ def __gather_texture_transform_and_tex_coord(primary_socket, export_settings):
node = previous_node(node.inputs['Vector'])
texcoord_idx = 0
use_active_uvmap = True
if node and node.type == 'UVMAP' and node.uv_map:
# Try to gather map index.
for blender_mesh in bpy.data.meshes:
i = blender_mesh.uv_layers.find(node.uv_map)
if i >= 0:
texcoord_idx = i
use_active_uvmap = False
break
return texture_transform, texcoord_idx or None
return texture_transform, texcoord_idx or None, use_active_uvmap
def __get_tex_from_socket(socket):