Node Wrangler: add back exposure compensation for Preview Node #105136

Closed
Damien Picard wants to merge 4 commits from pioverfour/blender-addons:dp_nw_exposure_compensation into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
2 changed files with 61 additions and 1 deletions
Showing only changes of commit ff9e38f1e8 - Show all commits

View File

@ -491,6 +491,7 @@ class NWPreviewNode(Operator, NWBase):
def __init__(self): def __init__(self):
self.shader_output_type = "" self.shader_output_type = ""
self.shader_output_ident = "" self.shader_output_ident = ""
self.shader_viewer_ident = ""
@classmethod @classmethod
def poll(cls, context): def poll(cls, context):
@ -513,10 +514,12 @@ class NWPreviewNode(Operator, NWBase):
else: else:
self.shader_output_type = "OUTPUT_MATERIAL" self.shader_output_type = "OUTPUT_MATERIAL"
self.shader_output_ident = "ShaderNodeOutputMaterial" self.shader_output_ident = "ShaderNodeOutputMaterial"
self.shader_viewer_ident = "ShaderNodeEmission"
elif shader_type == 'WORLD': elif shader_type == 'WORLD':
self.shader_output_type = "OUTPUT_WORLD" self.shader_output_type = "OUTPUT_WORLD"
self.shader_output_ident = "ShaderNodeOutputWorld" self.shader_output_ident = "ShaderNodeOutputWorld"
self.shader_viewer_ident = "ShaderNodeBackground"
def ensure_viewer_socket(self, node_tree, socket_type, connect_socket=None): def ensure_viewer_socket(self, node_tree, socket_type, connect_socket=None):
"""Check if a viewer output already exists in a node group, otherwise create it""" """Check if a viewer output already exists in a node group, otherwise create it"""
@ -559,6 +562,44 @@ class NWPreviewNode(Operator, NWBase):
groupout.is_active_output = True groupout.is_active_output = True
return groupout return groupout
@staticmethod
def get_scene_intensity(context):
"""Calculate intensity compensation based on scene exposure"""
intensity = 1.0
if context.scene.render.engine == 'CYCLES' and hasattr(context.scene, 'cycles'):
# Film exposure is a multiplier
intensity /= context.scene.cycles.film_exposure
# CM exposure is measured in stops/EVs (2^x)
intensity /= 2.0 ** context.scene.view_settings.exposure
return intensity
@staticmethod
def get_emission_node(base_node_tree):
for node in base_node_tree.nodes:
if "Emission Viewer" in node.name:
return node
def ensure_emission_node(self, context, base_node_tree, output_node):
emission_node = self.get_emission_node(base_node_tree)
if emission_node is None:
emission_node = base_node_tree.nodes.new(self.shader_viewer_ident)
emission_node.hide = True
emission_node.location = output_node.location + Vector((0, 40))
emission_node.label = "Viewer"
emission_node.name = "Emission Viewer"
emission_node.use_custom_color = True
emission_node.color = (0.6, 0.5, 0.4)
emission_node.select = False
# If Viewer is connected to output by user, don't change those connections (patch by gandalf3)
if (len(emission_node.outputs[0].links) == 0
or emission_node.outputs[0].links[0].to_node != output_node):
connect_sockets(emission_node.outputs[0], output_node.inputs[0])
# Set brightness of viewer to compensate for Film and CM exposure
emission_node.inputs[1].default_value = self.get_scene_intensity(context)
return emission_node
@classmethod @classmethod
def search_sockets(cls, node, sockets, index=None): def search_sockets(cls, node, sockets, index=None):
"""Recursively scan nodes for viewer sockets and store them in a list""" """Recursively scan nodes for viewer sockets and store them in a list"""
@ -759,11 +800,24 @@ class NWPreviewNode(Operator, NWBase):
active_node_socket_index = self.get_output_index( active_node_socket_index = self.get_output_index(
active, output_node, base_node_tree == active_tree, 'SHADER' active, output_node, base_node_tree == active_tree, 'SHADER'
) )
if active.outputs[active_node_socket_index].name == "Volume":
node_output = active.outputs[active_node_socket_index]
if node_output.name == "Volume":
output_node_socket_index = 1 output_node_socket_index = 1
else: else:
output_node_socket_index = 0 output_node_socket_index = 0
# Use an emission node if needed to compensate for scene exposure
settings = context.preferences.addons[__package__].preferences
if (settings.use_viewer_exposure_compensation
and self.get_scene_intensity(context) != 1.0
and node_output.type != 'SHADER'):
output_node = self.ensure_emission_node(context, base_node_tree, output_node)
output_node_socket_index = 0
else:
if (emission_node := self.get_emission_node(base_node_tree)) is not None:
base_node_tree.nodes.remove(emission_node)
# If there are no nested node groups, the link starts at the active node # If there are no nested node groups, the link starts at the active node
node_output = active.outputs[active_node_socket_index] node_output = active.outputs[active_node_socket_index]
if len(path) > 1: if len(path) > 1:

View File

@ -85,6 +85,11 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
), ),
default='CENTER', default='CENTER',
description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes") description="When merging nodes with the Ctrl+Numpad0 hotkey (and similar) specify the position of the new nodes")
use_viewer_exposure_compensation: BoolProperty(
name="Use Viewer Exposure Compensation",
default=False,
description="When using the viewer to preview colors in the shader editor, take the scene exposure into account to display the original color"
)
show_hotkey_list: BoolProperty( show_hotkey_list: BoolProperty(
name="Show Hotkey List", name="Show Hotkey List",
@ -109,6 +114,7 @@ class NWNodeWrangler(bpy.types.AddonPreferences):
col = layout.column() col = layout.column()
col.prop(self, "merge_position") col.prop(self, "merge_position")
col.prop(self, "merge_hide") col.prop(self, "merge_hide")
col.prop(self, "use_viewer_exposure_compensation")
box = layout.box() box = layout.box()
col = box.column(align=True) col = box.column(align=True)