From 0c5993694b9bc0d36ac6bfe6c8845b83409d217a Mon Sep 17 00:00:00 2001 From: Gabriel D Date: Thu, 16 Dec 2021 09:33:28 -0500 Subject: [PATCH] interleaving wip --- addons/io_scene_gltf2/__init__.py | 14 +++ ...tf2_blender_gather_primitive_attributes.py | 117 +++++++++++++----- .../exp/gltf2_blender_gltf2_exporter.py | 6 +- .../io/exp/gltf2_io_binary_data.py | 9 +- .../io_scene_gltf2/io/exp/gltf2_io_buffer.py | 2 +- 5 files changed, 116 insertions(+), 32 deletions(-) diff --git a/addons/io_scene_gltf2/__init__.py b/addons/io_scene_gltf2/__init__.py index 069596fa9..c52453cc6 100644 --- a/addons/io_scene_gltf2/__init__.py +++ b/addons/io_scene_gltf2/__init__.py @@ -255,6 +255,16 @@ def __init__(self): default=False ) + export_attributes_interleaving: EnumProperty( + name='Interleaving', + items=(('PACKED', 'Packed', + 'Vertex attributes are tightly packed and each has its own buffer view'), + ('INTERLEAVED', 'Interleaved', + 'Vertex attributes are stored as a Array-Of-Structures using a single buffer view')), + description='Specify how the vertex attributes should be packed', + default='PACKED' + ) + export_materials: EnumProperty( name='Materials', items=(('EXPORT', 'Export', @@ -549,6 +559,8 @@ def execute(self, context): else: export_settings['gltf_draco_mesh_compression'] = False + export_settings['gltf_attributes_interleaving'] = self.export_attributes_interleaving + export_settings['gltf_materials'] = self.export_materials export_settings['gltf_colors'] = self.export_colors export_settings['gltf_cameras'] = self.export_cameras @@ -765,6 +777,8 @@ def draw(self, context): col.prop(operator, 'use_mesh_edges') col.prop(operator, 'use_mesh_vertices') + layout.prop(operator, 'export_attributes_interleaving') + layout.prop(operator, 'export_materials') col = layout.column() col.active = operator.export_materials == "EXPORT" diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py index 126037ab5..2c43dd85d 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gather_primitive_attributes.py @@ -19,6 +19,7 @@ from io_scene_gltf2.io.com import gltf2_io_constants from io_scene_gltf2.io.com import gltf2_io_debug from io_scene_gltf2.io.exp import gltf2_io_binary_data +from io_scene_gltf2.io.com.gltf2_io_debug import print_console def gather_primitive_attributes(blender_primitive, export_settings): @@ -27,17 +28,26 @@ def gather_primitive_attributes(blender_primitive, export_settings): :return: a dictionary of attributes """ + + interleaving_info = None + if export_settings['gltf_attributes_interleaving'] == "INTERLEAVED": + interleaving_info = {"names": [], "data": []} + attributes = {} - attributes.update(__gather_position(blender_primitive, export_settings)) - attributes.update(__gather_normal(blender_primitive, export_settings)) - attributes.update(__gather_tangent(blender_primitive, export_settings)) - attributes.update(__gather_texcoord(blender_primitive, export_settings)) - attributes.update(__gather_colors(blender_primitive, export_settings)) - attributes.update(__gather_skins(blender_primitive, export_settings)) + attributes.update(__gather_position(blender_primitive, interleaving_info, export_settings)) + attributes.update(__gather_normal(blender_primitive, interleaving_info, export_settings)) + attributes.update(__gather_tangent(blender_primitive, interleaving_info, export_settings)) + attributes.update(__gather_texcoord(blender_primitive, interleaving_info, export_settings)) + attributes.update(__gather_colors(blender_primitive, interleaving_info, export_settings)) + attributes.update(__gather_skins(blender_primitive, interleaving_info, export_settings)) + + if interleaving_info: + __interleave_data(attributes, interleaving_info) + return attributes -def array_to_accessor(array, component_type, data_type, include_max_and_min=False): +def array_to_accessor(array, component_type, data_type, include_max_and_min=False, interleaving_info=None): dtype = gltf2_io_constants.ComponentType.to_numpy_dtype(component_type) num_elems = gltf2_io_constants.DataType.num_elements(data_type) @@ -54,8 +64,14 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals amax = np.amax(array, axis=0).tolist() amin = np.amin(array, axis=0).tolist() + buffer_view = None + if interleaving_info is None: + buffer_view = gltf2_io_binary_data.BinaryData(array.tobytes()) + else: + interleaving_info["data"].append(array) + return gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData(array.tobytes()), + buffer_view=buffer_view, byte_offset=None, component_type=component_type, count=len(array), @@ -70,66 +86,86 @@ def array_to_accessor(array, component_type, data_type, include_max_and_min=Fals ) -def __gather_position(blender_primitive, export_settings): +def __gather_position(blender_primitive, interleaving_info, export_settings): position = blender_primitive["attributes"]["POSITION"] + + if interleaving_info: + interleaving_info["names"].append("POSITION") + return { "POSITION": array_to_accessor( position, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Vec3, - include_max_and_min=True + include_max_and_min=True, + interleaving_info=interleaving_info, ) } -def __gather_normal(blender_primitive, export_settings): +def __gather_normal(blender_primitive, interleaving_info, export_settings): if not export_settings[gltf2_blender_export_keys.NORMALS]: return {} if 'NORMAL' not in blender_primitive["attributes"]: return {} normal = blender_primitive["attributes"]['NORMAL'] + + if interleaving_info: + interleaving_info["names"].append("NORMAL") + return { "NORMAL": array_to_accessor( normal, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Vec3, + interleaving_info=interleaving_info, ) } -def __gather_tangent(blender_primitive, export_settings): +def __gather_tangent(blender_primitive, interleaving_info, export_settings): if not export_settings[gltf2_blender_export_keys.TANGENTS]: return {} if 'TANGENT' not in blender_primitive["attributes"]: return {} tangent = blender_primitive["attributes"]['TANGENT'] + + if interleaving_info: + interleaving_info["names"].append("TANGENT") + return { "TANGENT": array_to_accessor( tangent, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Vec4, + interleaving_info=interleaving_info, ) } -def __gather_texcoord(blender_primitive, export_settings): +def __gather_texcoord(blender_primitive, interleaving_info, export_settings): attributes = {} if export_settings[gltf2_blender_export_keys.TEX_COORDS]: tex_coord_index = 0 tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) while blender_primitive["attributes"].get(tex_coord_id) is not None: tex_coord = blender_primitive["attributes"][tex_coord_id] + + if interleaving_info: + interleaving_info["names"].append(tex_coord_id) + attributes[tex_coord_id] = array_to_accessor( tex_coord, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Vec2, + interleaving_info=interleaving_info, ) tex_coord_index += 1 tex_coord_id = 'TEXCOORD_' + str(tex_coord_index) return attributes -def __gather_colors(blender_primitive, export_settings): +def __gather_colors(blender_primitive, interleaving_info, export_settings): attributes = {} if export_settings[gltf2_blender_export_keys.COLORS]: color_index = 0 @@ -146,19 +182,14 @@ def __gather_colors(blender_primitive, export_settings): colors += 0.5 # bias for rounding colors = colors.astype(np.uint16) - attributes[color_id] = gltf2_io.Accessor( - buffer_view=gltf2_io_binary_data.BinaryData(colors.tobytes()), - byte_offset=None, - component_type=gltf2_io_constants.ComponentType.UnsignedShort, - count=len(colors), - extensions=None, - extras=None, - max=None, - min=None, - name=None, - normalized=True, - sparse=None, - type=gltf2_io_constants.DataType.Vec4, + if interleaving_info: + interleaving_info["names"].append(color_id) + + attributes[color_id] = array_to_accessor( + colors, + gltf2_io_constants.ComponentType.UnsignedShort, + gltf2_io_constants.DataType.Vec4, + interleaving_info=interleaving_info, ) color_index += 1 @@ -166,7 +197,7 @@ def __gather_colors(blender_primitive, export_settings): return attributes -def __gather_skins(blender_primitive, export_settings): +def __gather_skins(blender_primitive, interleaving_info, export_settings): attributes = {} if export_settings[gltf2_blender_export_keys.SKINS]: bone_set_index = 0 @@ -178,8 +209,12 @@ def __gather_skins(blender_primitive, export_settings): gltf2_io_debug.print_console("WARNING", "There are more than 4 joint vertex influences." "The 4 with highest weight will be used (and normalized).") break + # joints + if interleaving_info: + interleaving_info["names"].append(joint_id) + internal_joint = blender_primitive["attributes"][joint_id] component_type = gltf2_io_constants.ComponentType.UnsignedShort if max(internal_joint) < 256: @@ -188,10 +223,14 @@ def __gather_skins(blender_primitive, export_settings): internal_joint, component_type, data_type=gltf2_io_constants.DataType.Vec4, + interleaving_info=interleaving_info, ) attributes[joint_id] = joint # weights + if interleaving_info: + interleaving_info["names"].append(weight_id) + internal_weight = blender_primitive["attributes"][weight_id] # normalize first 4 weights, when not exporting all influences if not export_settings['gltf_all_vertex_influences']: @@ -206,6 +245,7 @@ def __gather_skins(blender_primitive, export_settings): internal_weight, component_type=gltf2_io_constants.ComponentType.Float, data_type=gltf2_io_constants.DataType.Vec4, + interleaving_info=interleaving_info, ) attributes[weight_id] = weight @@ -213,3 +253,24 @@ def __gather_skins(blender_primitive, export_settings): joint_id = 'JOINTS_' + str(bone_set_index) weight_id = 'WEIGHTS_' + str(bone_set_index) return attributes + + +def __interleave_data(attributes, interleaving_info): + + # Compute the view byte_stride + view_stride = 0 + for name in interleaving_info["names"]: + attr = attributes[name] + attr.byte_offset = view_stride + view_stride += gltf2_io_constants.ComponentType.get_size(attr.component_type) * gltf2_io_constants.DataType.num_elements(attr.type) + + # Build the view bytearray + view_bytearray = bytearray() + for idx in range(0, attributes["POSITION"].count): + for data in interleaving_info["data"]: + view_bytearray.extend(data[idx].tobytes()) + + # Store the view + view = gltf2_io_binary_data.BinaryData(bytes(view_bytearray), view_stride) + for name in interleaving_info["names"]: + attributes[name].buffer_view = view diff --git a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py index 77ea0c579..1ac28fa03 100644 --- a/addons/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py +++ b/addons/io_scene_gltf2/blender/exp/gltf2_blender_gltf2_exporter.py @@ -24,6 +24,7 @@ from io_scene_gltf2.io.exp import gltf2_io_image_data from io_scene_gltf2.blender.exp import gltf2_blender_export_keys from io_scene_gltf2.io.exp.gltf2_io_user_extensions import export_user_extensions +from io_scene_gltf2.io.com.gltf2_io_debug import print_console class GlTF2Exporter: @@ -306,8 +307,9 @@ def __traverse_property(node): # binary data needs to be moved to a buffer and referenced with a buffer view if isinstance(node, gltf2_io_binary_data.BinaryData): - buffer_view = self.__buffer.add_and_get_view(node) - return self.__to_reference(buffer_view) + if node.buffer_view is None: + node.buffer_view = self.__buffer.add_and_get_view(node) + return self.__to_reference(node.buffer_view) # image data needs to be saved to file if isinstance(node, gltf2_io_image_data.ImageData): diff --git a/addons/io_scene_gltf2/io/exp/gltf2_io_binary_data.py b/addons/io_scene_gltf2/io/exp/gltf2_io_binary_data.py index 958374811..14b5df6f2 100644 --- a/addons/io_scene_gltf2/io/exp/gltf2_io_binary_data.py +++ b/addons/io_scene_gltf2/io/exp/gltf2_io_binary_data.py @@ -20,11 +20,18 @@ class BinaryData: """Store for gltf binary data that can later be stored in a buffer.""" - def __init__(self, data: bytes): + def __init__(self, data: bytes, byte_stride=None): if not isinstance(data, bytes): raise TypeError("Data is not a bytes array") self.data = data + # Byte stride to set when the data is converted to a BufferView + self.byte_stride = byte_stride + + # Set to the generated buffer view if the data is shared between multiple source + # Ex: When accessor data is interleaved + self.buffer_view = None + def __eq__(self, other): return self.data == other.data diff --git a/addons/io_scene_gltf2/io/exp/gltf2_io_buffer.py b/addons/io_scene_gltf2/io/exp/gltf2_io_buffer.py index c859f64cb..5b05dd778 100644 --- a/addons/io_scene_gltf2/io/exp/gltf2_io_buffer.py +++ b/addons/io_scene_gltf2/io/exp/gltf2_io_buffer.py @@ -40,7 +40,7 @@ def add_and_get_view(self, binary_data: gltf2_io_binary_data.BinaryData) -> gltf buffer=self.__buffer_index, byte_length=length, byte_offset=offset, - byte_stride=None, + byte_stride=binary_data.byte_stride, extensions=None, extras=None, name=None,