From bec2f1e7d4dd3ca54359a8a647f7f3a525ad8576 Mon Sep 17 00:00:00 2001 From: Alex Fuller Date: Tue, 24 Mar 2026 16:13:32 -0600 Subject: [PATCH] Cycles : Adds opacityMode support for convertUSDShaders, allowing transmission. --- Changes.md | 1 + .../ShaderNetworkAlgoTest.py | 154 ++++++++++++------ .../IECoreCyclesPreview/ShaderNetworkAlgo.cpp | 28 +++- 3 files changed, 127 insertions(+), 56 deletions(-) diff --git a/Changes.md b/Changes.md index a827df64b4b..ca0558e059c 100644 --- a/Changes.md +++ b/Changes.md @@ -16,6 +16,7 @@ Improvements ------------ - StandardLightVisualiser : Added surface texture visualisation for inputs to the `color` parameter of USD lights when GafferArnold is used (#6651). +- Cycles : Added support for USDPreviewSurface's `opacityMode` which allows transmission surfaces. Fixes ----- diff --git a/python/GafferCyclesTest/IECoreCyclesPreviewTest/ShaderNetworkAlgoTest.py b/python/GafferCyclesTest/IECoreCyclesPreviewTest/ShaderNetworkAlgoTest.py index 7cc6e9e034d..582c3633617 100644 --- a/python/GafferCyclesTest/IECoreCyclesPreviewTest/ShaderNetworkAlgoTest.py +++ b/python/GafferCyclesTest/IECoreCyclesPreviewTest/ShaderNetworkAlgoTest.py @@ -504,75 +504,123 @@ def testConvertUSDOpacity( self ) : for opacity in ( 0.25, 1.0, None ) : for opacityThreshold in ( 0.0, 0.5, None ) : + for opacityMode in ( "transparent", "presence", None ) : - parameters = {} - if opacity is not None : - parameters["opacity"] = IECore.FloatData( opacity ) - if opacityThreshold is not None : - parameters["opacityThreshold"] = IECore.FloatData( opacityThreshold ) + parameters = {} + if opacity is not None : + parameters["opacity"] = IECore.FloatData( opacity ) + if opacityMode is not None : + parameters["opacityMode"] = IECore.StringData( opacityMode ) + if opacityThreshold is not None : + parameters["opacityThreshold"] = IECore.FloatData( opacityThreshold ) - network = IECoreScene.ShaderNetwork( - shaders = { - "previewSurface" : IECoreScene.Shader( - "UsdPreviewSurface", "surface", parameters + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", parameters + ) + }, + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreCycles.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + convertedShader = convertedNetwork.getShader( "previewSurface" ) + expectedOpacity = opacity if opacity is not None else 1.0 + if opacityThreshold is not None : + expectedOpacity = expectedOpacity if expectedOpacity > opacityThreshold else 0 + + if opacityMode == "presence" : + self.assertEqual( + convertedShader.parameters["alpha"].value, + expectedOpacity ) - }, - output = "previewSurface", - ) + self.assertNotIn( "transmission_weight", convertedShader.parameters ) + elif opacityMode == "transparent" or opacityMode == None : + expectedOpacity = opacity if opacity is not None else 1.0 + self.assertEqual( + convertedShader.parameters["transmission_weight"].value, + 1.0 - expectedOpacity + ) + # If there isn't any opacityThreshold, alpha should not be authored. + if opacityThreshold == 0.0 or opacityThreshold is None : + self.assertNotIn( "alpha", convertedShader.parameters ) + + # Repeat, but with an input connection as well as the parameter value - convertedNetwork = network.copy() - IECoreCycles.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) + network.addConnection( ( ( "texture", "a" ), ( "previewSurface", "opacity" ) ) ) - convertedShader = convertedNetwork.getShader( "previewSurface" ) - expectedOpacity = opacity if opacity is not None else 1.0 - if opacityThreshold is not None : - expectedOpacity = expectedOpacity if expectedOpacity > opacityThreshold else 0 + convertedNetwork = network.copy() + IECoreCycles.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) - self.assertEqual( - convertedShader.parameters["alpha"].value, - expectedOpacity - ) + if opacityThreshold : - # Repeat, but with an input connection as well as the parameter value + if opacityMode == "presence" : + self.assertEqual( len( convertedNetwork ), 4 ) + else : + self.assertEqual( len( convertedNetwork ), 5 ) + opacityInput = convertedNetwork.input( ( "previewSurface", "alpha" ) ) + self.assertEqual( opacityInput, ( "previewSurfaceOpacityMultiply", "value" ) ) + self.assertEqual( convertedNetwork.getShader( opacityInput.shader ).name, "math" ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "value1" ) ), + ( "texture", "alpha" ) + ) - network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) - network.addConnection( ( ( "texture", "a" ), ( "previewSurface", "opacity" ) ) ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "value2" ) ), + ( "previewSurfaceOpacityCompare", "value" ) + ) - convertedNetwork = network.copy() - IECoreCycles.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityCompare", "value1" ) ), + ( "texture", "alpha" ) + ) - if opacityThreshold : + compareShader = convertedNetwork.getShader( "previewSurfaceOpacityCompare" ) + self.assertEqual( compareShader.parameters["math_type"].value, "greater_than" ) + self.assertEqual( compareShader.parameters["value2"].value, opacityThreshold ) - self.assertEqual( len( convertedNetwork ), 4 ) - opacityInput = convertedNetwork.input( ( "previewSurface", "alpha" ) ) - self.assertEqual( opacityInput, ( "previewSurfaceOpacityMultiply", "value" ) ) - self.assertEqual( convertedNetwork.getShader( opacityInput.shader ).name, "math" ) - self.assertEqual( - convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "value1" ) ), - ( "texture", "alpha" ) - ) + if opacityMode == "transparency" or opacityMode == None : - self.assertEqual( - convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "value2" ) ), - ( "previewSurfaceOpacityCompare", "value" ) - ) + invertShader = convertedNetwork.getShader( "previewSurfaceOpacityInvert" ) + self.assertEqual( invertShader.parameters["math_type"].value, "subtract" ) + self.assertEqual( invertShader.parameters["value1"].value, 1.0 ) + self.assertEqual( invertShader.parameters["use_clamp"].value, True ) + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "transmission_weight" ) ), + ( "previewSurfaceOpacityInvert", "value" ) + ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityInvert", "value2" ) ), + ( "texture", "alpha" ) + ) - self.assertEqual( - convertedNetwork.input( ( "previewSurfaceOpacityCompare", "value1" ) ), - ( "texture", "alpha" ) - ) + elif opacityMode == "presence" : - compareShader = convertedNetwork.getShader( "previewSurfaceOpacityCompare" ) - self.assertEqual( compareShader.parameters["math_type"].value, "greater_than" ) - self.assertEqual( compareShader.parameters["value2"].value, opacityThreshold ) + self.assertEqual( len( convertedNetwork ), 2 ) + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "alpha" ) ), + ( "texture", "alpha" ) + ) - else : + elif opacityMode == "transparency" or opacityMode == None : - self.assertEqual( len( convertedNetwork ), 2 ) - self.assertEqual( - convertedNetwork.input( ( "previewSurface", "alpha" ) ), - ( "texture", "alpha" ) - ) + self.assertEqual( len( convertedNetwork ), 3 ) + invertShader = convertedNetwork.getShader( "previewSurfaceOpacityInvert" ) + self.assertEqual( invertShader.parameters["math_type"].value, "subtract" ) + self.assertEqual( invertShader.parameters["value1"].value, 1.0 ) + self.assertEqual( invertShader.parameters["use_clamp"].value, True ) + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "transmission_weight" ) ), + ( "previewSurfaceOpacityInvert", "value" ) + ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityInvert", "value2" ) ), + ( "texture", "alpha" ) + ) def testConvertUSDSpecular( self ) : diff --git a/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp b/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp index 8b0863d2d9c..fa3db168165 100644 --- a/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp +++ b/src/GafferCycles/IECoreCyclesPreview/ShaderNetworkAlgo.cpp @@ -875,6 +875,7 @@ const InternedString g_normalParameter( "normal" ); const InternedString g_normalizeParameter( "normalize" ); const InternedString g_occlusionParameter( "occlusion" ); const InternedString g_opacityParameter( "opacity" ); +const InternedString g_opacityModeParameter( "opacityMode" ); const InternedString g_opacityThresholdParameter( "opacityThreshold" ); const InternedString g_parametricParameter( "parametric" ); const InternedString g_positionParameter( "position"); @@ -905,9 +906,11 @@ const InternedString g_texMappingScaleParameter( "tex_mapping__scale" ); const InternedString g_texMappingYMappingParameter( "tex_mapping__y_mapping" ); const InternedString g_texMappingZMappingParameter( "tex_mapping__z_mapping" ); const InternedString g_translationParameter( "translation" ); +const InternedString g_transmissionWeightParameter( "transmission_weight" ); const InternedString g_treatAsPointParameter( "treatAsPoint" ); const InternedString g_useDiffuseParameter( "use_diffuse" ); const InternedString g_useGlossyParameter( "use_glossy" ); +const InternedString g_useClampParameter( "use_clamp" ); const InternedString g_useMISParameter( "use_mis" ); const InternedString g_useSpecularWorkflowParameter( "useSpecularWorkflow" ); const InternedString g_UVParameter( "UV" ); @@ -1291,6 +1294,7 @@ void IECoreCycles::ShaderNetworkAlgo::convertUSDShaders( ShaderNetwork *shaderNe // with a little compare/multiply network. float opacity = parameterValue( shader.get(), g_opacityParameter, 1.0f ); + const string opacityMode = parameterValue( shader.get(), g_opacityModeParameter, string( "transparent" ) ); const float opacityThreshold = parameterValue( shader.get(), g_opacityThresholdParameter, 0.0f ); if( const ShaderNetwork::Parameter opacityInput = shaderNetwork->input( { handle, g_opacityParameter } ) ) { @@ -1309,18 +1313,36 @@ void IECoreCycles::ShaderNetworkAlgo::convertUSDShaders( ShaderNetwork *shaderNe shaderNetwork->removeConnection( ShaderNetwork::Connection( opacityInput, { handle, g_opacityParameter } ) ); shaderNetwork->addConnection( ShaderNetwork::Connection( { multiplyHandle, g_valueParameter }, { handle, g_alphaParameter } ) ); } - else + else if( opacityMode == string( "presence" ) ) { transferUSDParameter( shaderNetwork, handle, shader.get(), g_opacityParameter, newShader.get(), g_alphaParameter, 1.0f ); } + else + { + shaderNetwork->removeConnection( ShaderNetwork::Connection( opacityInput, { handle, g_opacityParameter } ) ); + } + + if( opacityMode == string( "transparent" ) ) + { + ShaderPtr invertShader = new Shader( "math", "cycles:shader" ); + invertShader->parameters()[g_value1Parameter] = new FloatData( 1.0f ); + invertShader->parameters()[g_mathTypeParameter] = new StringData( "subtract" ); + invertShader->parameters()[g_useClampParameter] = new BoolData( true ); + const InternedString invertHandle = shaderNetwork->addShader( handle.string() + "OpacityInvert", std::move( invertShader ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( opacityInput, { invertHandle, g_value2Parameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { invertHandle, g_valueParameter }, { handle, g_transmissionWeightParameter } ) ); + } + } + else if( opacityMode == string( "transparent" ) ) + { + newShader->parameters()[g_transmissionWeightParameter] = new FloatData( 1.0f - opacity ); } else { opacity = opacity > opacityThreshold ? opacity : 0.0f; + newShader->parameters()[g_alphaParameter] = new FloatData( opacity ); } - newShader->parameters()[g_alphaParameter] = new FloatData( opacity ); - // Normal. /// \todo Convert normal parameters once we have a solution for Cycles' /// need for tangents to be provided for the correct use of normal maps.