diff options
author | Julien Duroure <julien.duroure@gmail.com> | 2022-10-21 19:47:53 +0300 |
---|---|---|
committer | Julien Duroure <julien.duroure@gmail.com> | 2022-10-21 19:47:53 +0300 |
commit | 9d903a93f03b11ede3abaef14052869f50d650d3 (patch) | |
tree | bf0bc1c352733d36cbcd425936033fd480f4a456 | |
parent | 8a2443844daf7bd32b3aafac53370966f2d330f4 (diff) |
glTF importer/exporter: Export lights using correcct units
-rwxr-xr-x | io_scene_gltf2/__init__.py | 47 | ||||
-rwxr-xr-x | io_scene_gltf2/blender/com/gltf2_blender_conversion.py | 3 | ||||
-rw-r--r-- | io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py | 24 | ||||
-rw-r--r-- | io_scene_gltf2/blender/imp/gltf2_blender_light.py | 37 |
4 files changed, 99 insertions, 12 deletions
diff --git a/io_scene_gltf2/__init__.py b/io_scene_gltf2/__init__.py index 2c61a9ed..08260263 100755 --- a/io_scene_gltf2/__init__.py +++ b/io_scene_gltf2/__init__.py @@ -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, 4, 39), + "version": (3, 4, 40), 'blender': (3, 3, 0), 'location': 'File > Import-Export', 'description': 'Import-Export as glTF 2.0', @@ -98,7 +98,21 @@ def on_export_format_changed(self, context): ) -class ExportGLTF2_Base: +class ConvertGLTF2_Base: + """Base class containing options that should be exposed during both import and export.""" + + convert_lighting_mode: EnumProperty( + name='Lighting Mode', + items=( + ('SPEC', 'Standard', 'Physically-based glTF lighting units (cd, lx, nt)'), + ('COMPAT', 'Unitless', 'Non-physical, unitless lighting. Useful when exposure controls are not available'), + ('RAW', 'Raw (Deprecated)', 'Blender lighting strengths with no conversion'), + ), + description='Optional backwards compatibility for non-standard render engines. Applies to lights',# TODO: and emissive materials', + default='SPEC' + ) + +class ExportGLTF2_Base(ConvertGLTF2_Base): # TODO: refactor to avoid boilerplate def __init__(self): @@ -643,6 +657,7 @@ class ExportGLTF2_Base: export_settings['gltf_morph_tangent'] = False export_settings['gltf_lights'] = self.export_lights + export_settings['gltf_lighting_mode'] = self.convert_lighting_mode export_settings['gltf_binary'] = bytearray() export_settings['gltf_binaryfilename'] = ( @@ -778,7 +793,7 @@ class GLTF_PT_export_transform(bpy.types.Panel): class GLTF_PT_export_geometry(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' bl_region_type = 'TOOL_PROPS' - bl_label = "Geometry" + bl_label = "Data" bl_parent_id = "FILE_PT_operator" bl_options = {'DEFAULT_CLOSED'} @@ -876,6 +891,28 @@ class GLTF_PT_export_geometry_original_pbr(bpy.types.Panel): layout.prop(operator, 'export_original_specular') +class GLTF_PT_export_geometry_lighting(bpy.types.Panel): + bl_space_type = 'FILE_BROWSER' + bl_region_type = 'TOOL_PROPS' + bl_label = "Lighting" + bl_parent_id = "GLTF_PT_export_geometry" + bl_options = {'DEFAULT_CLOSED'} + + @classmethod + def poll(cls, context): + sfile = context.space_data + operator = sfile.active_operator + return operator.bl_idname == "EXPORT_SCENE_OT_gltf" + + def draw(self, context): + layout = self.layout + layout.use_property_split = True + layout.use_property_decorate = False # No animation. + + sfile = context.space_data + operator = sfile.active_operator + + layout.prop(operator, 'convert_lighting_mode') class GLTF_PT_export_geometry_compression(bpy.types.Panel): bl_space_type = 'FILE_BROWSER' @@ -1106,7 +1143,7 @@ def menu_func_export(self, context): self.layout.operator(ExportGLTF2.bl_idname, text='glTF 2.0 (.glb/.gltf)') -class ImportGLTF2(Operator, ImportHelper): +class ImportGLTF2(Operator, ConvertGLTF2_Base, ImportHelper): """Load a glTF 2.0 file""" bl_idname = 'import_scene.gltf' bl_label = 'Import glTF 2.0' @@ -1189,6 +1226,7 @@ class ImportGLTF2(Operator, ImportHelper): layout.prop(self, 'import_shading') layout.prop(self, 'guess_original_bind_pose') layout.prop(self, 'bone_heuristic') + layout.prop(self, 'convert_lighting_mode') def invoke(self, context, event): import sys @@ -1320,6 +1358,7 @@ classes = ( GLTF_PT_export_geometry_mesh, GLTF_PT_export_geometry_material, GLTF_PT_export_geometry_original_pbr, + GLTF_PT_export_geometry_lighting, GLTF_PT_export_geometry_compression, GLTF_PT_export_animation, GLTF_PT_export_animation_export, diff --git a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py index 85ab654a..8b1e5452 100755 --- a/io_scene_gltf2/blender/com/gltf2_blender_conversion.py +++ b/io_scene_gltf2/blender/com/gltf2_blender_conversion.py @@ -5,6 +5,9 @@ from math import sin, cos import numpy as np from io_scene_gltf2.io.com import gltf2_io_constants +PBR_WATTS_TO_LUMENS = 683 +# Industry convention, biological peak at 555nm, scientific standard as part of SI candela definition. + def texture_transform_blender_to_gltf(mapping_transform): """ Converts the offset/rotation/scale from a Mapping node applied in Blender's diff --git a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py index 615bd01b..587535df 100644 --- a/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py +++ b/io_scene_gltf2/blender/exp/gltf2_blender_gather_lights.py @@ -7,6 +7,7 @@ from typing import Optional, List, Dict, Any from io_scene_gltf2.blender.exp.gltf2_blender_gather_cache import cached from ..com.gltf2_blender_extras import generate_extras +from ..com.gltf2_blender_conversion import PBR_WATTS_TO_LUMENS from io_scene_gltf2.io.com import gltf2_io_lights_punctual from io_scene_gltf2.io.com import gltf2_io_debug @@ -50,7 +51,7 @@ def __gather_color(blender_lamp, export_settings) -> Optional[List[float]]: return list(blender_lamp.color) -def __gather_intensity(blender_lamp, _) -> Optional[float]: +def __gather_intensity(blender_lamp, export_settings) -> Optional[float]: emission_node = __get_cycles_emission_node(blender_lamp) if emission_node is not None: if blender_lamp.type != 'SUN': @@ -68,9 +69,26 @@ def __gather_intensity(blender_lamp, _) -> Optional[float]: emission_strength = blender_lamp.energy else: emission_strength = emission_node.inputs["Strength"].default_value + else: + emission_strength = blender_lamp.energy + if export_settings['gltf_lighting_mode'] == 'RAW': return emission_strength - - return blender_lamp.energy + else: + # Assume at this point the computed strength is still in the appropriate watt-related SI unit, which if everything up to here was done with physical basis it hopefully should be. + if blender_lamp.type == 'SUN': # W/m^2 in Blender to lm/m^2 for GLTF/KHR_lights_punctual. + emission_luminous = emission_strength + else: + # Other than directional, only point and spot lamps are supported by GLTF. + # In Blender, points are omnidirectional W, and spots are specified as if they're points. + # Point and spot should both be lm/r^2 in GLTF. + emission_luminous = emission_strength / (4*math.pi) + if export_settings['gltf_lighting_mode'] == 'SPEC': + emission_luminous *= PBR_WATTS_TO_LUMENS + elif export_settings['gltf_lighting_mode'] == 'COMPAT': + pass # Just so we have an exhaustive tree to catch bugged values. + else: + raise ValueError(export_settings['gltf_lighting_mode']) + return emission_luminous def __gather_spot(blender_lamp, export_settings) -> Optional[gltf2_io_lights_punctual.LightSpot]: diff --git a/io_scene_gltf2/blender/imp/gltf2_blender_light.py b/io_scene_gltf2/blender/imp/gltf2_blender_light.py index fb060598..034d41ab 100644 --- a/io_scene_gltf2/blender/imp/gltf2_blender_light.py +++ b/io_scene_gltf2/blender/imp/gltf2_blender_light.py @@ -6,6 +6,7 @@ from math import pi from ..com.gltf2_blender_extras import set_extras from io_scene_gltf2.io.imp.gltf2_io_user_extensions import import_user_extensions +from ..com.gltf2_blender_conversion import PBR_WATTS_TO_LUMENS class BlenderLight(): @@ -21,7 +22,7 @@ class BlenderLight(): import_user_extensions('gather_import_light_before_hook', gltf, vnode, pylight) if pylight['type'] == "directional": - light = BlenderLight.create_directional(gltf, light_id) + light = BlenderLight.create_directional(gltf, light_id) # ...Why not pass the pylight? elif pylight['type'] == "point": light = BlenderLight.create_point(gltf, light_id) elif pylight['type'] == "spot": @@ -30,9 +31,6 @@ class BlenderLight(): if 'color' in pylight.keys(): light.color = pylight['color'] - if 'intensity' in pylight.keys(): - light.energy = pylight['intensity'] - # TODO range set_extras(light, pylight.get('extras')) @@ -44,12 +42,34 @@ class BlenderLight(): pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] if 'name' not in pylight.keys(): - pylight['name'] = "Sun" + pylight['name'] = "Sun" # Uh... Is it okay to mutate the import data? sun = bpy.data.lights.new(name=pylight['name'], type="SUN") + + if 'intensity' in pylight.keys(): + if gltf.import_settings['convert_lighting_mode'] == 'SPEC': + sun.energy = pylight['intensity'] / PBR_WATTS_TO_LUMENS + elif gltf.import_settings['convert_lighting_mode'] == 'COMPAT': + sun.energy = pylight['intensity'] + elif gltf.import_settings['convert_lighting_mode'] == 'RAW': + sun.energy = pylight['intensity'] + else: + raise ValueError(gltf.import_settings['convert_lighting_mode']) + return sun @staticmethod + def _calc_energy_pointlike(gltf, pylight): + if gltf.import_settings['convert_lighting_mode'] == 'SPEC': + return pylight['intensity'] / PBR_WATTS_TO_LUMENS * 4 * pi + elif gltf.import_settings['convert_lighting_mode'] == 'COMPAT': + return pylight['intensity'] * 4 * pi + elif gltf.import_settings['convert_lighting_mode'] == 'RAW': + return pylight['intensity'] + else: + raise ValueError(gltf.import_settings['convert_lighting_mode']) + + @staticmethod def create_point(gltf, light_id): pylight = gltf.data.extensions['KHR_lights_punctual']['lights'][light_id] @@ -57,6 +77,10 @@ class BlenderLight(): pylight['name'] = "Point" point = bpy.data.lights.new(name=pylight['name'], type="POINT") + + if 'intensity' in pylight.keys(): + point.energy = BlenderLight._calc_energy_pointlike(gltf, pylight) + return point @staticmethod @@ -79,4 +103,7 @@ class BlenderLight(): else: spot.spot_blend = 1.0 + if 'intensity' in pylight.keys(): + spot.energy = BlenderLight._calc_energy_pointlike(gltf, pylight) + return spot |