glTF importer/exporter: Export lights using correcct units
This commit is contained in:
parent
8a2443844d
commit
9d903a93f0
Notes:
blender-bot
2023-02-14 18:32:31 +01:00
Referenced by issue #91035, glTF light intensity requires unit conversion
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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,11 +42,33 @@ 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
|
||||
|
|
Loading…
Reference in New Issue