diff --git a/Changes.md b/Changes.md index d9545fc515c..e4bdc4a0024 100644 --- a/Changes.md +++ b/Changes.md @@ -5,6 +5,7 @@ Features -------- - AttributeEditor, LightEditor, RenderPassEditor : Added drag and drop editing. Edits can be created or updated by dropping a value into a cell. Cells representing a set expression or string array can be modified by holding Shift to append to an existing edit, or Control may be held to remove from an existing edit. +- USD : Added Cycles-specific light parameters to USD Lux lights. Fixes ----- diff --git a/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp b/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp index 69e58fedd4a..1103b68bbb6 100644 --- a/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp +++ b/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp @@ -946,6 +946,8 @@ const InternedString g_widthParameter( "width" ); const InternedString g_wrapSParameter( "wrapS" ); const InternedString g_wrapTParameter( "wrapT" ); +const string g_cyclesNamespace( "cycles:" ); + void transferUSDLightParameters( ShaderNetwork *network, InternedString shaderHandle, const Shader *usdShader, Shader *shader ) { Color3f color = parameterValue( usdShader, g_colorParameter, Color3f( 1 ) ); @@ -967,6 +969,14 @@ void transferUSDLightParameters( ShaderNetwork *network, InternedString shaderHa shader->parameters()[g_useGlossyParameter] = new BoolData( specular > 0.0f ); shader->parameters()[g_useMISParameter] = new BoolData( true ); + + for( const auto &[name, value] : usdShader->parameters() ) + { + if( boost::starts_with( name.string(), g_cyclesNamespace ) ) + { + shader->parameters()[name.string().substr(g_cyclesNamespace.size())] = value; + } + } } void transferUSDShapingParameters( ShaderNetwork *network, InternedString shaderHandle, const Shader *usdShader, Shader *shader ) diff --git a/startup/GafferUSD/cyclesLights.py b/startup/GafferUSD/cyclesLights.py new file mode 100644 index 00000000000..8c0664affc5 --- /dev/null +++ b/startup/GafferUSD/cyclesLights.py @@ -0,0 +1,53 @@ +########################################################################## +# +# Copyright (c) 2025, Alex Fuller. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import pathlib + +from pxr import Plug + +# Register a USD plugin that adds Cycles-specific auto-apply schemas for +# UsdLux lights. We deliberately don't add this to the `PXR_PLUGINPATH_NAME` +# search path because we don't want it to be loaded in any third-party +# applications that Gaffer might launch as subprocessses. So instead we +# register it manually with `RegisterPlugins`. See `GafferCycles.usda` +# for more details. + +try : + import GafferCycles + Plug.Registry().RegisterPlugins( str( pathlib.Path( GafferCycles.__file__ ).parents[2] / "plugin" / "GafferCycles" / "plugInfo.json" ) ) +except ImportError : + # GafferCycles not available + pass diff --git a/startup/gui/lightEditor.py b/startup/gui/lightEditor.py index 584070579ec..f6c992a86f9 100644 --- a/startup/gui/lightEditor.py +++ b/startup/gui/lightEditor.py @@ -40,6 +40,44 @@ import Gaffer import GafferSceneUI +# UsdLux lights + +Gaffer.Metadata.registerValue( GafferSceneUI.LightEditor.Settings, "attribute", "preset:USD", "light" ) + +GafferSceneUI.LightEditor.registerParameter( "light", "color" ) +GafferSceneUI.LightEditor.registerParameter( "light", "intensity" ) +GafferSceneUI.LightEditor.registerParameter( "light", "exposure" ) +GafferSceneUI.LightEditor.registerParameter( "light", "colorTemperature" ) +GafferSceneUI.LightEditor.registerParameter( "light", "enableColorTemperature" ) +GafferSceneUI.LightEditor.registerParameter( "light", "normalize" ) +GafferSceneUI.LightEditor.registerParameter( "light", "diffuse" ) +GafferSceneUI.LightEditor.registerParameter( "light", "specular" ) + +GafferSceneUI.LightEditor.registerParameter( "light", "width", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "height", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "radius", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "treatAsPoint", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "length", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "treatAsLine", "Geometry" ) +GafferSceneUI.LightEditor.registerParameter( "light", "angle", "Geometry" ) + +GafferSceneUI.LightEditor.registerParameter( "light", "texture:file", "Texture" ) +GafferSceneUI.LightEditor.registerParameter( "light", "texture:format", "Texture" ) + +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:cone:angle", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:cone:softness", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:focus", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:focusTint", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:file", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:angleScale", "Shaping" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:normalize", "Shaping" ) + +GafferSceneUI.LightEditor.registerParameter( "light", "shadow:enable", "Shadow" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shadow:color", "Shadow" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shadow:distance", "Shadow" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shadow:falloff", "Shadow" ) +GafferSceneUI.LightEditor.registerParameter( "light", "shadow:falloffGamma", "Shadow" ) + if os.environ.get( "CYCLES_ROOT" ) and os.environ.get( "GAFFERCYCLES_HIDE_UI", "" ) != "1" : Gaffer.Metadata.registerValue( GafferSceneUI.LightEditor.Settings, "attribute", "preset:Cycles", "cycles:light" ) @@ -65,6 +103,18 @@ Gaffer.Metadata.registerValue( GafferSceneUI.LightEditor.Settings, "attribute", "userDefault", "cycles:light" ) + # Register Cycles-specific parameters for USD lights. + for parameter in [ + "lightgroup", + "use_mis", "use_camera", "use_diffuse", "use_glossy", "use_transmission", "use_scatter", "use_caustics", + "spread", "map_resolution", "max_bounces" + ] : + GafferSceneUI.LightEditor.registerParameter( + "light", f"cycles:{parameter}", "Cycles", + columnName = parameter.replace( "cycles:", "" ) + ) + + with IECore.IgnoredExceptions( ImportError ) : # This import appears unused, but it is intentional; it prevents us from @@ -92,44 +142,6 @@ Gaffer.Metadata.registerValue( GafferSceneUI.LightEditor.Settings, "attribute", "userDefault", "osl:light" ) -# UsdLux lights - -Gaffer.Metadata.registerValue( GafferSceneUI.LightEditor.Settings, "attribute", "preset:USD", "light" ) - -GafferSceneUI.LightEditor.registerParameter( "light", "color" ) -GafferSceneUI.LightEditor.registerParameter( "light", "intensity" ) -GafferSceneUI.LightEditor.registerParameter( "light", "exposure" ) -GafferSceneUI.LightEditor.registerParameter( "light", "colorTemperature" ) -GafferSceneUI.LightEditor.registerParameter( "light", "enableColorTemperature" ) -GafferSceneUI.LightEditor.registerParameter( "light", "normalize" ) -GafferSceneUI.LightEditor.registerParameter( "light", "diffuse" ) -GafferSceneUI.LightEditor.registerParameter( "light", "specular" ) - -GafferSceneUI.LightEditor.registerParameter( "light", "width", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "height", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "radius", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "treatAsPoint", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "length", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "treatAsLine", "Geometry" ) -GafferSceneUI.LightEditor.registerParameter( "light", "angle", "Geometry" ) - -GafferSceneUI.LightEditor.registerParameter( "light", "texture:file", "Texture" ) -GafferSceneUI.LightEditor.registerParameter( "light", "texture:format", "Texture" ) - -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:cone:angle", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:cone:softness", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:focus", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:focusTint", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:file", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:angleScale", "Shaping" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shaping:ies:normalize", "Shaping" ) - -GafferSceneUI.LightEditor.registerParameter( "light", "shadow:enable", "Shadow" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shadow:color", "Shadow" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shadow:distance", "Shadow" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shadow:falloff", "Shadow" ) -GafferSceneUI.LightEditor.registerParameter( "light", "shadow:falloffGamma", "Shadow" ) - # Arnold lights with IECore.IgnoredExceptions( ImportError ) : diff --git a/startup/gui/usd.py b/startup/gui/usd.py index 8b0de997aaf..3b864938328 100644 --- a/startup/gui/usd.py +++ b/startup/gui/usd.py @@ -35,6 +35,7 @@ # ########################################################################## +import os import Gaffer import GafferUSD @@ -54,3 +55,15 @@ "samples", "volume_samples", "resolution" ] ) : Gaffer.Metadata.registerValue( GafferUSD.USDLight, f"parameters.arnold:{parameter}", "layout:index", 1000 + i ) + +# Change Cycles ordering. +for i, parameter in enumerate( [ + "lightgroup", + "use_mis", "use_camera", "use_diffuse", "use_glossy", "use_transmission", "use_scatter", "use_caustics", + "spread", "map_resolution", "max_bounces" +] ) : + Gaffer.Metadata.registerValue( GafferUSD.USDLight, f"parameters.cycles:{parameter}", "layout:index", 2000 + i ) + +# Only show the Cycles parameters if Cycles exists and not hidden +Gaffer.Metadata.registerValue( GafferUSD.USDLight, "parameters", "layout:activator:cyclesUIEnabled", lambda x : os.environ.get( "CYCLES_ROOT" ) and os.environ.get( "GAFFERCYCLES_HIDE_UI", "" ) != "1" ) +Gaffer.Metadata.registerValue( GafferUSD.USDLight, "parameters.cycles:*", "layout:visibilityActivator", "cyclesUIEnabled" ) diff --git a/usdSchemas/GafferCycles.usda b/usdSchemas/GafferCycles.usda new file mode 100644 index 00000000000..ae0d00ac3ed --- /dev/null +++ b/usdSchemas/GafferCycles.usda @@ -0,0 +1,130 @@ +#usda 1.0 +( + subLayers = [ + @usdLux/schema.usda@, + @usd/schema.usda@ + ] +) + +over "GLOBAL" ( + customData = { + string libraryName = "GafferCycles" + bool skipCodeGeneration = 1 + bool useLiteralIdentifier = 1 + } +) +{ +} + +# Here we define a bunch of codeless auto-apply API schemas for extending the +# standard UsdLux lights with inputs specific to Cycles. This approach is +# modelled on the one used by UsdRiPxr to add RenderMan-specific inputs, and +# we believe is the one Pixar intends everyone to use. + +class "GafferCyclesLightAPI" ( + customData = { + token[] apiSchemaAutoApplyTo = [ "DistantLight", "DiskLight", "DomeLight", "RectLight", "SphereLight" ] + string apiSchemaType = "singleApply" + string className = "GafferCyclesLightAPI" + } + inherits = +) +{ + + string inputs:cycles:lightgroup = "" ( + displayGroup = "Basic" + displayName = "Light Group (Cycles)" + ) + + bool inputs:cycles:use_mis = true ( + displayGroup = "Refine" + displayName = "MIS (Cycles)" + ) + + bool inputs:cycles:use_camera = true ( + displayGroup = "Refine" + displayName = "Camera (Cycles)" + ) + + bool inputs:cycles:use_diffuse = true ( + displayGroup = "Refine" + displayName = "Diffuse (Cycles)" + ) + + bool inputs:cycles:use_glossy = true ( + displayGroup = "Refine" + displayName = "Glossy (Cycles)" + ) + + bool inputs:cycles:use_transmission = true ( + displayGroup = "Refine" + displayName = "Transmission (Cycles)" + ) + + bool inputs:cycles:use_scatter = true ( + displayGroup = "Refine" + displayName = "Volume Scatter (Cycles)" + ) + + bool inputs:cycles:use_caustics = false ( + displayGroup = "Refine" + displayName = "Shadow Caustics (Cycles)" + ) + + int inputs:cycles:max_bounces = 1024 ( + displayGroup = "Refine" + displayName = "Max Bounces (Cycles)" + ) + +} + +class "GafferCyclesDiskLightAPI" ( + customData = { + token[] apiSchemaAutoApplyTo = ["DiskLight"] + string apiSchemaType = "singleApply" + string className = "GafferCyclesDiskLightAPI" + } + inherits = +) +{ + + float inputs:cycles:spread = 180.0 ( + displayGroup = "Geometry" + displayName = "Spread (Cycles)" + ) + +} + +class "GafferCyclesQuadLightAPI" ( + customData = { + token[] apiSchemaAutoApplyTo = ["RectLight"] + string apiSchemaType = "singleApply" + string className = "GafferCyclesQuadLightAPI" + } + inherits = +) +{ + + float inputs:cycles:spread = 180.0 ( + displayGroup = "Geometry" + displayName = "Spread (Cycles)" + ) + +} + +class "GafferCyclesBackgroundLightAPI" ( + customData = { + token[] apiSchemaAutoApplyTo = ["DomeLight"] + string apiSchemaType = "singleApply" + string className = "GafferCyclesBackgroundLightAPI" + } + inherits = +) +{ + + int inputs:cycles:map_resolution = 1024 ( + displayGroup = "Sampling" + displayName = "Map Resolution (Cycles)" + ) + +}