glTF exporter: Draco compression
Note that blender must use this patch to use Draco compression: https://developer.blender.org/D4501 Currenlty, this patch is not merged yet into master
This commit is contained in:
parent
8521155924
commit
0a26f6cce9
|
@ -14,7 +14,7 @@
|
|||
|
||||
bl_info = {
|
||||
'name': 'glTF 2.0 format',
|
||||
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen',
|
||||
'author': 'Julien Duroure, Norbert Nopper, Urs Hanselmann, Moritz Becher, Benjamin Schmithüsen, Jim Eckerlein',
|
||||
"version": (0, 0, 1),
|
||||
'blender': (2, 80, 0),
|
||||
'location': 'File > Import-Export',
|
||||
|
@ -26,6 +26,7 @@ bl_info = {
|
|||
'category': 'Import-Export',
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Script reloading (if the user calls 'Reload Scripts' from Blender)
|
||||
#
|
||||
|
@ -33,6 +34,7 @@ bl_info = {
|
|||
def reload_package(module_dict_main):
|
||||
import importlib
|
||||
from pathlib import Path
|
||||
|
||||
def reload_package_recursive(current_dir, module_dict):
|
||||
for path in current_dir.iterdir():
|
||||
if "__init__" in str(path) or path.stem not in module_dict:
|
||||
|
@ -56,6 +58,7 @@ from bpy.props import (StringProperty,
|
|||
IntProperty)
|
||||
from bpy.types import Operator
|
||||
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||
from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
|
||||
|
||||
|
||||
#
|
||||
|
@ -64,9 +67,11 @@ from bpy_extras.io_utils import ImportHelper, ExportHelper
|
|||
|
||||
|
||||
class ExportGLTF2_Base:
|
||||
|
||||
# TODO: refactor to avoid boilerplate
|
||||
|
||||
def __init__(self):
|
||||
self.is_draco_available = gltf2_io_draco_compression_extension.dll_exists()
|
||||
|
||||
bl_options = {'UNDO', 'PRESET'}
|
||||
|
||||
export_format: EnumProperty(
|
||||
|
@ -114,6 +119,44 @@ class ExportGLTF2_Base:
|
|||
default=True
|
||||
)
|
||||
|
||||
export_draco_mesh_compression_enable: BoolProperty(
|
||||
name='Draco mesh compression',
|
||||
description='Compress mesh using Draco',
|
||||
default=False
|
||||
)
|
||||
|
||||
export_draco_mesh_compression_level: IntProperty(
|
||||
name='Compression level',
|
||||
description='Compression level (0 = most speed, 6 = most compression, higher values currently not supported)',
|
||||
default=6,
|
||||
min=0,
|
||||
max=6
|
||||
)
|
||||
|
||||
export_draco_position_quantization: IntProperty(
|
||||
name='Position quantization bits',
|
||||
description='Quantization bits for position values (0 = no quantization)',
|
||||
default=14,
|
||||
min=0,
|
||||
max=30
|
||||
)
|
||||
|
||||
export_draco_normal_quantization: IntProperty(
|
||||
name='Normal quantization bits',
|
||||
description='Quantization bits for normal values (0 = no quantization)',
|
||||
default=10,
|
||||
min=0,
|
||||
max=30
|
||||
)
|
||||
|
||||
export_draco_texcoord_quantization: IntProperty(
|
||||
name='Texcoord quantization bits',
|
||||
description='Quantization bits for texture coordinate values (0 = no quantization)',
|
||||
default=12,
|
||||
min=0,
|
||||
max=30
|
||||
)
|
||||
|
||||
export_tangents: BoolProperty(
|
||||
name='Tangents',
|
||||
description='Export vertex tangents with meshes',
|
||||
|
@ -309,11 +352,21 @@ class ExportGLTF2_Base:
|
|||
export_settings['gltf_texcoords'] = self.export_texcoords
|
||||
export_settings['gltf_normals'] = self.export_normals
|
||||
export_settings['gltf_tangents'] = self.export_tangents and self.export_normals
|
||||
|
||||
if self.is_draco_available:
|
||||
export_settings['gltf_draco_mesh_compression'] = self.export_draco_mesh_compression_enable
|
||||
export_settings['gltf_draco_mesh_compression_level'] = self.export_draco_mesh_compression_level
|
||||
export_settings['gltf_draco_position_quantization'] = self.export_draco_position_quantization
|
||||
export_settings['gltf_draco_normal_quantization'] = self.export_draco_normal_quantization
|
||||
export_settings['gltf_draco_texcoord_quantization'] = self.export_draco_texcoord_quantization
|
||||
else:
|
||||
export_settings['gltf_draco_mesh_compression'] = False
|
||||
|
||||
export_settings['gltf_materials'] = self.export_materials
|
||||
export_settings['gltf_colors'] = self.export_colors
|
||||
export_settings['gltf_cameras'] = self.export_cameras
|
||||
export_settings['gltf_selected'] = self.export_selected
|
||||
export_settings['gltf_layers'] = True #self.export_layers
|
||||
export_settings['gltf_layers'] = True # self.export_layers
|
||||
export_settings['gltf_extras'] = self.export_extras
|
||||
export_settings['gltf_yup'] = self.export_yup
|
||||
export_settings['gltf_apply'] = self.export_apply
|
||||
|
@ -385,6 +438,17 @@ class ExportGLTF2_Base:
|
|||
col.prop(self, 'export_colors')
|
||||
col.prop(self, 'export_materials')
|
||||
|
||||
# Add Draco compression option only if the DLL could be found.
|
||||
if self.is_draco_available:
|
||||
col.prop(self, 'export_draco_mesh_compression_enable')
|
||||
|
||||
# Display options when Draco compression is enabled.
|
||||
if self.export_draco_mesh_compression_enable:
|
||||
col.prop(self, 'export_draco_mesh_compression_level')
|
||||
col.prop(self, 'export_draco_position_quantization')
|
||||
col.prop(self, 'export_draco_normal_quantization')
|
||||
col.prop(self, 'export_draco_texcoord_quantization')
|
||||
|
||||
def draw_object_settings(self):
|
||||
col = self.layout.box().column()
|
||||
col.prop(self, 'export_cameras')
|
||||
|
|
|
@ -23,6 +23,7 @@ from io_scene_gltf2.blender.exp import gltf2_blender_gather
|
|||
from io_scene_gltf2.blender.exp.gltf2_blender_gltf2_exporter import GlTF2Exporter
|
||||
from io_scene_gltf2.io.com.gltf2_io_debug import print_console, print_newline
|
||||
from io_scene_gltf2.io.exp import gltf2_io_export
|
||||
from io_scene_gltf2.io.exp import gltf2_io_draco_compression_extension
|
||||
|
||||
|
||||
def save(context, export_settings):
|
||||
|
@ -66,6 +67,11 @@ def __get_copyright(export_settings):
|
|||
|
||||
def __gather_gltf(exporter, export_settings):
|
||||
scenes, animations = gltf2_blender_gather.gather_gltf2(export_settings)
|
||||
|
||||
if export_settings['gltf_draco_mesh_compression']:
|
||||
gltf2_io_draco_compression_extension.compress_scene_primitives(scenes, export_settings)
|
||||
exporter.add_draco_extension()
|
||||
|
||||
for scene in scenes:
|
||||
exporter.add_scene(scene)
|
||||
for animation in animations:
|
||||
|
|
|
@ -135,6 +135,15 @@ class GlTF2Exporter:
|
|||
if is_glb:
|
||||
return self.__buffer.to_bytes()
|
||||
|
||||
def add_draco_extension(self):
|
||||
"""
|
||||
Register Draco extension as *used* and *required*.
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.__gltf.extensions_required.append('KHR_draco_mesh_compression')
|
||||
self.__gltf.extensions_used.append('KHR_draco_mesh_compression')
|
||||
|
||||
def finalize_images(self, output_path):
|
||||
"""
|
||||
Write all images.
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
import bpy
|
||||
import sys
|
||||
from ctypes import *
|
||||
from pathlib import Path
|
||||
|
||||
from io_scene_gltf2.io.exp.gltf2_io_binary_data import BinaryData
|
||||
|
||||
|
||||
def dll_path() -> Path:
|
||||
"""
|
||||
Get the DLL path depending on the underlying platform.
|
||||
:return: DLL path.
|
||||
"""
|
||||
lib_name = 'extern_draco'
|
||||
blender_root = Path(bpy.app.binary_path).parent
|
||||
python_lib = Path('2.80/python/lib')
|
||||
paths = {
|
||||
'win32': blender_root/python_lib/'site-packages'/'{}.dll'.format(lib_name),
|
||||
'linux': blender_root/python_lib/'python3.7'/'site-packages'/'lib{}.so'.format(lib_name),
|
||||
'darwin': blender_root.parent/'Resources'/python_lib/'python3.7'/'site-packages'/'lib{}.dylib'.format(lib_name)
|
||||
}
|
||||
|
||||
path = paths.get(sys.platform)
|
||||
return path if path is not None else ''
|
||||
|
||||
|
||||
def dll_exists() -> bool:
|
||||
"""
|
||||
Checks whether the DLL path exists.
|
||||
:return: True if the DLL exists.
|
||||
"""
|
||||
exists = dll_path().exists()
|
||||
print("'{}' ".format(dll_path().absolute()) + ("exists, draco mesh compression is available" if exists else
|
||||
"does not exist, draco mesh compression not available"))
|
||||
return exists
|
||||
|
||||
|
||||
def compress_scene_primitives(scenes, export_settings):
|
||||
"""
|
||||
Handles draco compression.
|
||||
Invoked after data has been gathered, but before scenes get traversed.
|
||||
Moves position, normal and texture coordinate attributes into a Draco compressed buffer.
|
||||
"""
|
||||
|
||||
# Load DLL and setup function signatures.
|
||||
# Nearly all functions take the compressor as the first argument.
|
||||
dll = cdll.LoadLibrary(str(dll_path().resolve()))
|
||||
|
||||
dll.createCompressor.restype = c_void_p
|
||||
dll.createCompressor.argtypes = []
|
||||
|
||||
dll.setCompressionLevel.restype = None
|
||||
dll.setCompressionLevel.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setPositionQuantizationBits.restype = None
|
||||
dll.setPositionQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setNormalQuantizationBits.restype = None
|
||||
dll.setNormalQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setTexCoordQuantizationBits.restype = None
|
||||
dll.setTexCoordQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.compress.restype = c_bool
|
||||
dll.compress.argtypes = [c_void_p]
|
||||
|
||||
dll.compressedSize.restype = c_uint64
|
||||
dll.compressedSize.argtypes = [c_void_p]
|
||||
|
||||
dll.disposeCompressor.restype = None
|
||||
dll.disposeCompressor.argtypes = [c_void_p]
|
||||
|
||||
dll.setFaces.restype = None
|
||||
dll.setFaces.argtypes = [c_void_p, c_uint32, c_uint32, c_void_p]
|
||||
|
||||
dll.addPositionAttribute.restype = None
|
||||
dll.addPositionAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
|
||||
|
||||
dll.addNormalAttribute.restype = None
|
||||
dll.addNormalAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
|
||||
|
||||
dll.addTexCoordAttribute.restype = None
|
||||
dll.addTexCoordAttribute.argtypes = [c_void_p, c_uint32, c_char_p]
|
||||
|
||||
dll.copyToBytes.restype = None
|
||||
dll.copyToBytes.argtypes = [c_void_p, c_char_p]
|
||||
|
||||
dll.getTexCoordAttributeIdCount.restype = c_uint32
|
||||
dll.getTexCoordAttributeIdCount.argtypes = [c_void_p]
|
||||
|
||||
dll.getTexCoordAttributeId.restype = c_uint32
|
||||
dll.getTexCoordAttributeId.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.getPositionAttributeId.restype = c_uint32
|
||||
dll.getPositionAttributeId.argtypes = [c_void_p]
|
||||
|
||||
dll.getNormalAttributeId.restype = c_uint32
|
||||
dll.getNormalAttributeId.argtypes = [c_void_p]
|
||||
|
||||
dll.setCompressionLevel.restype = None
|
||||
dll.setCompressionLevel.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setPositionQuantizationBits.restype = None
|
||||
dll.setPositionQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setNormalQuantizationBits.restype = None
|
||||
dll.setNormalQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
dll.setTexCoordQuantizationBits.restype = None
|
||||
dll.setTexCoordQuantizationBits.argtypes = [c_void_p, c_uint32]
|
||||
|
||||
for scene in scenes:
|
||||
for node in scene.nodes:
|
||||
__traverse_node(node, dll, export_settings)
|
||||
|
||||
|
||||
def __traverse_node(node, dll, export_settings):
|
||||
if not (node.mesh is None):
|
||||
print("Compressing mesh " + node.name)
|
||||
for primitive in node.mesh.primitives:
|
||||
__compress_primitive(primitive, dll, export_settings)
|
||||
|
||||
if not (node.children is None):
|
||||
for child in node.children:
|
||||
__traverse_node(child, dll, export_settings)
|
||||
|
||||
|
||||
def __compress_primitive(primitive, dll, export_settings):
|
||||
attributes = primitive.attributes
|
||||
|
||||
# Begin mesh.
|
||||
compressor = dll.createCompressor()
|
||||
|
||||
# Process position attributes.
|
||||
dll.addPositionAttribute(compressor, attributes['POSITION'].count, attributes['POSITION'].buffer_view.data)
|
||||
|
||||
# Process normal attributes.
|
||||
dll.addNormalAttribute(compressor, attributes['NORMAL'].count, attributes['NORMAL'].buffer_view.data)
|
||||
|
||||
# Process texture coordinate attributes.
|
||||
for attribute in [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]:
|
||||
dll.addTexCoordAttribute(compressor, attribute.count, attribute.buffer_view.data)
|
||||
|
||||
# Process faces.
|
||||
index_byte_length = {
|
||||
'Byte': 1,
|
||||
'UnsignedByte': 1,
|
||||
'Short': 2,
|
||||
'UnsignedShort': 2,
|
||||
'UnsignedInt': 4,
|
||||
}
|
||||
indices = primitive.indices
|
||||
dll.setFaces(compressor, indices.count, index_byte_length[indices.component_type.name], indices.buffer_view.data)
|
||||
indices.buffer_view = None
|
||||
|
||||
# Set compression parameters.
|
||||
dll.setCompressionLevel(compressor, export_settings['gltf_draco_mesh_compression_level'])
|
||||
dll.setPositionQuantizationBits(compressor, export_settings['gltf_draco_position_quantization'])
|
||||
dll.setNormalQuantizationBits(compressor, export_settings['gltf_draco_normal_quantization'])
|
||||
dll.setTexCoordQuantizationBits(compressor, export_settings['gltf_draco_texcoord_quantization'])
|
||||
|
||||
# After all point and connectivity data has been written to the compressor,
|
||||
# it can finally be compressed.
|
||||
if dll.compress(compressor):
|
||||
|
||||
# Compression was successfull.
|
||||
# Move compressed data into a bytes object,
|
||||
# which is referenced by a 'gltf2_io_binary_data.BinaryData':
|
||||
#
|
||||
# "KHR_draco_mesh_compression": {
|
||||
# ....
|
||||
# "buffer_view": Compressed data inside a 'gltf2_io_binary_data.BinaryData'.
|
||||
# }
|
||||
|
||||
# Query size necessary to hold all the compressed data.
|
||||
compression_size = dll.compressedSize(compressor)
|
||||
|
||||
# Allocate byte buffer and write compressed data to it.
|
||||
compressed_data = bytes(compression_size)
|
||||
dll.copyToBytes(compressor, compressed_data)
|
||||
|
||||
if primitive.extensions is None:
|
||||
primitive.extensions = {}
|
||||
|
||||
tex_coord_ids = {}
|
||||
for id in range(0, dll.getTexCoordAttributeIdCount(compressor)):
|
||||
tex_coord_ids["TEXCOORD_" + str(id)] = dll.getTexCoordAttributeId(compressor, id)
|
||||
|
||||
# Register draco compression extension into primitive.
|
||||
primitive.extensions["KHR_draco_mesh_compression"] = {
|
||||
'bufferView': BinaryData(compressed_data),
|
||||
'attributes': {
|
||||
'POSITION': dll.getPositionAttributeId(compressor),
|
||||
'NORMAL': dll.getNormalAttributeId(compressor),
|
||||
**tex_coord_ids,
|
||||
}
|
||||
}
|
||||
|
||||
# Set to triangle list mode.
|
||||
primitive.mode = 4
|
||||
|
||||
# Remove buffers from attribute, since the data now resides inside the compressed Draco buffer.
|
||||
attributes['POSITION'].buffer_view = None
|
||||
attributes['NORMAL'].buffer_view = None
|
||||
for attribute in [attributes[attr] for attr in attributes if attr.startswith('TEXCOORD_')]:
|
||||
attribute.buffer_view = None
|
||||
|
||||
# Afterwards, the compressor can be released.
|
||||
dll.disposeCompressor(compressor)
|
||||
|
||||
pass
|
||||
|
Loading…
Reference in New Issue