Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vertex color only when used #2017

Merged
merged 18 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions addons/io_scene_gltf2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,8 @@ def __init__(self):
)

export_colors: BoolProperty(
name='Vertex Colors',
description='Export vertex colors with meshes',
name='dummy',
description='Keep for compatibility only',
default=True
)

Expand Down Expand Up @@ -820,7 +820,6 @@ def execute(self, context):
export_settings['gltf_draco_mesh_compression'] = False

export_settings['gltf_materials'] = self.export_materials
export_settings['gltf_colors'] = self.export_colors
export_settings['gltf_attributes'] = self.export_attributes
export_settings['gltf_cameras'] = self.export_cameras

Expand Down Expand Up @@ -1107,7 +1106,6 @@ def draw(self, context):
col = layout.column()
col.active = operator.export_normals
col.prop(operator, 'export_tangents')
layout.prop(operator, 'export_colors')
layout.prop(operator, 'export_attributes')

col = layout.column()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from ...io.exp.gltf2_io_user_extensions import export_user_extensions
from ...io.com import gltf2_io_constants
from ..com import gltf2_blender_conversion
from .material.gltf2_blender_gather_materials import get_base_material
from .material.gltf2_blender_gather_materials import get_base_material, get_material_from_idx
from . import gltf2_blender_gather_skins


Expand All @@ -34,7 +34,7 @@ def extract_primitives(materials, blender_mesh, uuid_for_skined_data, blender_ve
primitive_creator.create_dots_data_structure()
primitive_creator.populate_dots_data()
primitive_creator.primitive_split()
primitive_creator.material_uvmap_attribute_add()
primitive_creator.manage_material_info() # UVMap & Vertex Color
return primitive_creator.primitive_creation()

class PrimitiveCreator:
Expand Down Expand Up @@ -152,7 +152,7 @@ def __init__(self, attr_name):
self.attr_name = attr_name
self.keep = attr_name.startswith("_")

# Manage attributes + COLOR_0
# Manage attributes
for blender_attribute_index, blender_attribute in enumerate(self.blender_mesh.attributes):

attr = {}
Expand All @@ -172,42 +172,27 @@ def __init__(self, attr_name):

continue

if self.blender_mesh.color_attributes.find(blender_attribute.name) == self.blender_mesh.color_attributes.render_color_index \
and self.blender_mesh.color_attributes.render_color_index != -1:

if self.export_settings['gltf_colors'] is False:
continue
attr['gltf_attribute_name'] = 'COLOR_0'
attr['get'] = self.get_function()

# Seems we sometime can have name collision about attributes
# Avoid crash and ignoring one of duplicated attribute name
if 'COLOR_0' in [a['gltf_attribute_name'] for a in self.blender_attributes]:
print_console('WARNING', 'Attribute (vertex color) collision name: ' + blender_attribute.name + ", ignoring one of them")
continue

else:
# Custom attributes
# Keep only attributes that starts with _
# As Blender create lots of attributes that are internal / not needed are as duplicated of standard glTF accessors (position, uv, material_index...)
if self.export_settings['gltf_attributes'] is False:
continue
# Check if there is an extension that want to keep this attribute, or change the exported name
keep_attribute = KeepAttribute(blender_attribute.name)
# Custom attributes
# Keep only attributes that starts with _
# As Blender create lots of attributes that are internal / not needed are as duplicated of standard glTF accessors (position, uv, material_index...)
if self.export_settings['gltf_attributes'] is False:
continue
# Check if there is an extension that want to keep this attribute, or change the exported name
keep_attribute = KeepAttribute(blender_attribute.name)

export_user_extensions('gather_attribute_keep', self.export_settings, keep_attribute)
export_user_extensions('gather_attribute_keep', self.export_settings, keep_attribute)

if keep_attribute.keep is False:
continue
if keep_attribute.keep is False:
continue

attr['gltf_attribute_name'] = keep_attribute.attr_name.upper()
attr['get'] = self.get_function()
attr['gltf_attribute_name'] = keep_attribute.attr_name.upper()
attr['get'] = self.get_function()

# Seems we sometime can have name collision about attributes
# Avoid crash and ignoring one of duplicated attribute name
if attr['gltf_attribute_name'] in [a['gltf_attribute_name'] for a in self.blender_attributes]:
print_console('WARNING', 'Attribute collision name: ' + blender_attribute.name + ", ignoring one of them")
continue
# Seems we sometime can have name collision about attributes
# Avoid crash and ignoring one of duplicated attribute name
if attr['gltf_attribute_name'] in [a['gltf_attribute_name'] for a in self.blender_attributes]:
print_console('WARNING', 'Attribute collision name: ' + blender_attribute.name + ", ignoring one of them")
continue

self.blender_attributes.append(attr)

Expand Down Expand Up @@ -398,12 +383,18 @@ def primitive_split(self):
for material_idx in unique_material_idxs:
self.prim_indices[material_idx] = loop_indices[loop_material_idxs == material_idx]

def material_uvmap_attribute_add(self):
def manage_material_info(self):
# If user defined UVMap as a custom attribute, we need to add it/them in the dots structure and populate data
# So we need to get, for each material, what are these custom attribute
# No choice : We need to retrieve materials here. Anyway, this will be baked, and next call will be quick
# We also need to shuffle Vertex Color data if needed

materials_use_vc = None
warning_already_displayed = False
for material_idx in self.prim_indices.keys():
_, material_info = get_base_material(material_idx, self.materials, self.export_settings)

# UVMaps
self.uvmap_attribute_list = list(set([i['value'] for i in material_info["uv_info"].values() if 'type' in i.keys() and i['type'] == "Attribute" ]))

additional_fields = []
Expand All @@ -412,10 +403,11 @@ def material_uvmap_attribute_add(self):
additional_fields.append((attr + str(0), gltf2_blender_conversion.get_numpy_type('FLOAT2')))
additional_fields.append((attr + str(1), gltf2_blender_conversion.get_numpy_type('FLOAT2')))

new_dt = np.dtype(self.dots.dtype.descr + additional_fields)
dots = np.zeros(self.dots.shape, dtype=new_dt)
for f in self.dots.dtype.names:
dots[f] = self.dots[f]
if len(additional_fields) > 0:
new_dt = np.dtype(self.dots.dtype.descr + additional_fields)
dots = np.zeros(self.dots.shape, dtype=new_dt)
for f in self.dots.dtype.names:
dots[f] = self.dots[f]

# Now we need to get data and populate
for attr in self.uvmap_attribute_list:
Expand All @@ -434,7 +426,57 @@ def material_uvmap_attribute_add(self):
dots[attr + '1'] = data[:, 1]
del data

self.dots = dots
if len(additional_fields) > 0:
self.dots = dots

# There are multiple case to take into account for VC

# The simplier test is when no vertex color are used
if material_info['vc_info']['color_type'] is None and material_info['vc_info']['alpha_type'] is None:
# Nothing to do
continue

if material_info['vc_info']['color_type'] is None and material_info['vc_info']['alpha_type'] is not None:
print_console('WARNING', 'We are not managing this case (Vertex Color alpha without color)')
continue

vc_color_name = None
vc_alpha_name = None
if material_info['vc_info']['color_type'] == "name":
vc_color_name = material_info['vc_info']['color']
elif material_info['vc_info']['color_type'] == "active":
# Get active (render) Vertex Color
vc_color_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name

if material_info['vc_info']['alpha_type'] == "name":
vc_alpha_name = material_info['vc_info']['alpha']
elif material_info['vc_info']['alpha_type'] == "active":
# Get active (render) Vertex Color
vc_alpha_name = self.blender_mesh.color_attributes[self.blender_mesh.color_attributes.render_color_index].name

if vc_color_name is not None:

vc_key = ""
vc_key += vc_color_name if vc_color_name is not None else ""
vc_key += vc_alpha_name if vc_alpha_name is not None else ""

if materials_use_vc is not None and materials_use_vc != vc_key:
if warning_already_displayed is False:
print_console('WARNING', 'glTF specification does not allow this case (multiple materials with different Vertex Color)')
warning_already_displayed = True
materials_use_vc = vc_key
continue
elif materials_use_vc is None:
materials_use_vc = vc_key

# We need to check if we need to add alpha
add_alpha = vc_alpha_name is not None
mat = get_material_from_idx(material_idx, self.materials, self.export_settings)
add_alpha = add_alpha and not (mat.blend_method is None or mat.blend_method == 'OPAQUE')
# Manage Vertex Color (RGB and Alpha if needed)
self.__manage_color_attribute(vc_color_name, vc_alpha_name if add_alpha else None)
else:
pass # Using the same Vertex Color

def primitive_creation(self):
primitives = []
Expand Down Expand Up @@ -638,9 +680,7 @@ def __get_positions(self):
def get_function(self):

def getting_function(attr):
if attr['gltf_attribute_name'] == "COLOR_0":
self.__get_color_attribute(attr)
elif attr['gltf_attribute_name'].startswith("_"):
if attr['gltf_attribute_name'].startswith("_"):
self.__get_layer_attribute(attr)
elif attr['gltf_attribute_name'].startswith("TEXCOORD_"):
self.__get_uvs_attribute(int(attr['gltf_attribute_name'].split("_")[-1]), attr)
Expand All @@ -652,34 +692,93 @@ def getting_function(attr):
return getting_function


def __get_color_attribute(self, attr):
blender_color_idx = self.blender_mesh.color_attributes.render_color_index
def __manage_color_attribute(self, attr_name, attr_name_alpha):
blender_color_idx = self.blender_mesh.color_attributes.find(attr_name)
if blender_color_idx < 0:
return None

# Add COLOR_0 in dots data

attr = self.blender_mesh.color_attributes[blender_color_idx]

# Get data
data_dots, data_dots_edges, data_dots_points = self.__get_color_attribute_data(attr)

# Get data for alpha if needed
if attr_name_alpha is not None and attr_name_alpha != attr_name:
blender_alpha_idx = self.blender_mesh.color_attributes.find(attr_name_alpha)
if blender_alpha_idx >= 0:
attr_alpha = self.blender_mesh.color_attributes[blender_alpha_idx]
data_dots_alpha, data_dots_edges_alpha, data_dots_points_alpha = self.__get_color_attribute_data(attr_alpha)
# Merging data
data_dots[:, 3] = data_dots_alpha[:, 3]
if data_dots_edges is not None:
data_dots_edges[:, 3] = data_dots_edges_alpha[:, 3]
if data_dots_points is not None:
data_dots_points[:, 3] = data_dots_points_alpha[:, 3]

# Check if we need to get alpha (the 4th channel) here
max_index = 4 if attr_name_alpha is not None else 3

# Add this data to dots structure
additional_fields = []
for i in range(max_index):
# Must calculate the type of the field : FLOAT_COLOR or BYTE_COLOR
additional_fields.append(('COLOR_0' + str(i), gltf2_blender_conversion.get_numpy_type('FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR')))

# Keep the existing custom attribute
# Data will be exported twice, one for COLOR_O, one for the custom attribute
new_dt = np.dtype(self.dots.dtype.descr + additional_fields)
dots = np.zeros(self.dots.shape, dtype=new_dt)
for f in self.dots.dtype.names:
dots[f] = self.dots[f]

self.dots = dots

if attr['blender_domain'] == "POINT":
# colors are already linear, no need to switch color space
for i in range(max_index):
self.dots['COLOR_0' +str(i)] = data_dots[:, i]
if self.export_settings['gltf_loose_edges'] and attr.domain == "POINT":
self.dots_edges['COLOR_0' + str(i)] = data_dots_edges[:, i]
if self.export_settings['gltf_loose_points'] and attr['blender_domain'] == "POINT":
self.dots_points['COLOR_0' + str(i)] = data_dots_points[:, i]

# Add COLOR_0 in attribute list
attr_color_0 = {}
attr_color_0['blender_data_type'] = 'FLOAT_COLOR' if max_index == 3 else 'BYTE_COLOR'
attr_color_0['blender_domain'] = attr.domain
attr_color_0['gltf_attribute_name'] = 'COLOR_0'
attr_color_0['len'] = max_index # 3 or 4, depending if we have alpha
attr_color_0['type'] = gltf2_blender_conversion.get_numpy_type(attr_color_0['blender_data_type'])
attr_color_0['component_type'] = gltf2_blender_conversion.get_component_type(attr_color_0['blender_data_type'])
attr_color_0['data_type'] = gltf2_io_constants.DataType.Vec3 if max_index == 3 else gltf2_io_constants.DataType.Vec4

self.blender_attributes.append(attr_color_0)

def __get_color_attribute_data(self, attr):
data_dots_edges = None
data_dots_points = None

if attr.domain == "POINT":
colors = np.empty(len(self.blender_mesh.vertices) * 4, dtype=np.float32)
elif attr['blender_domain'] == "CORNER":
elif attr.domain == "CORNER":
colors = np.empty(len(self.blender_mesh.loops) * 4, dtype=np.float32)
self.blender_mesh.color_attributes[blender_color_idx].data.foreach_get('color', colors)
if attr['blender_domain'] == "POINT":
attr.data.foreach_get('color', colors)
if attr.domain == "POINT":
colors = colors.reshape(-1, 4)
data_dots = colors[self.dots['vertex_index']]
if self.export_settings['gltf_loose_edges']:
data_dots_edges = colors[self.dots_edges['vertex_index']]
if self.export_settings['gltf_loose_points']:
data_dots_points = colors[self.dots_points['vertex_index']]

elif attr['blender_domain'] == "CORNER":
elif attr.domain == "CORNER":
colors = colors.reshape(-1, 4)
data_dots = colors

del colors
# colors are already linear, no need to switch color space
for i in range(4):
self.dots[attr['gltf_attribute_name'] + str(i)] = data_dots[:, i]
if self.export_settings['gltf_loose_edges'] and attr['blender_domain'] == "POINT":
self.dots_edges[attr['gltf_attribute_name'] + str(i)] = data_dots_edges[:, i]
if self.export_settings['gltf_loose_points'] and attr['blender_domain'] == "POINT":
self.dots_points[attr['gltf_attribute_name'] + str(i)] = data_dots_points[:, i]

return data_dots, data_dots_edges, data_dots_points

def __get_layer_attribute(self, attr):
if attr['blender_domain'] in ['CORNER']:
Expand Down Expand Up @@ -979,15 +1078,19 @@ def __set_regular_attribute(self, attr):
res[:, i] = self.prim_dots[attr['gltf_attribute_name'] + str(i)]
self.attributes[attr['gltf_attribute_name']] = {}
self.attributes[attr['gltf_attribute_name']]["data"] = res
if 'gltf_attribute_name' == "NORMAL":
if attr['gltf_attribute_name'] == "NORMAL":
self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float
self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec3
elif 'gltf_attribute_name' == "TANGENT":
elif attr['gltf_attribute_name'] == "TANGENT":
self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float
self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec4
elif attr['gltf_attribute_name'].startswith('TEXCOORD_'):
self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_io_constants.ComponentType.Float
self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_io_constants.DataType.Vec2
elif attr['gltf_attribute_name'].startswith('COLOR_'):
# This is already managed, we only have to copy
self.attributes[attr['gltf_attribute_name']]["component_type"] = attr['component_type']
self.attributes[attr['gltf_attribute_name']]["data_type"] = attr['data_type']
else:
self.attributes[attr['gltf_attribute_name']]["component_type"] = gltf2_blender_conversion.get_component_type(attr['blender_data_type'])
self.attributes[attr['gltf_attribute_name']]["data_type"] = gltf2_blender_conversion.get_data_type(attr['blender_data_type'])
Loading