diff --git a/Changes.md b/Changes.md index 3a3b7e48bb0..1b99edf8cdd 100644 --- a/Changes.md +++ b/Changes.md @@ -18,12 +18,14 @@ Improvements - AttributeTweaks, CameraTweaks, ShaderTweaks, OptionTweaks, PrimitiveVariableTweaks : - Added support for a `{source}` token which is substituted with the original value when tweaking a string in `Replace` mode. - Added tooltips documenting the tweak modes. +- 3Delight : Added automatic render-time translation of UsdPreviewSurface shaders to 3Delight. Fixes ----- - Viewer : Fixed Cycles shader balls. - TweakPlug : Fixed incorrect results and potential crashes in list modes. +- USD : Fixed `Unsupported value type "StringData" for parameter "input"` warning when converting `UsdTransform2d` shaders with no `in` connections to Arnold. API --- diff --git a/SConstruct b/SConstruct index 4ec8a88f385..a38e60a589d 100644 --- a/SConstruct +++ b/SConstruct @@ -1315,6 +1315,7 @@ libraries = { ], }, "pythonEnvAppends" : { + "CPPPATH" : [ "$DELIGHT_ROOT/include" ], "LIBS" : [ "IECoreScene$CORTEX_LIB_SUFFIX", "IECoreDelight" ], }, "requiredOptions" : [ "DELIGHT_ROOT" ], diff --git a/include/IECoreDelight/ShaderNetworkAlgo.h b/include/IECoreDelight/ShaderNetworkAlgo.h index b8350bcbf72..204ae1f549a 100644 --- a/include/IECoreDelight/ShaderNetworkAlgo.h +++ b/include/IECoreDelight/ShaderNetworkAlgo.h @@ -40,6 +40,8 @@ #include "IECoreScene/ShaderNetwork.h" +#include + namespace IECoreDelight { @@ -54,6 +56,11 @@ const char *lightGeometryType( const IECoreScene::ShaderNetwork *shaderNetwork ) /// `state` is used to store the current state and avoid unnecessary edits. void updateLightGeometry( const IECoreScene::ShaderNetwork *shaderNetwork, NSIContext_t context, const char *handle, IECore::MurmurHash &state ); +/// Converts any UsdPreviewSurface shaders and UsdLuxLights into native 3Delight shaders. This conversion +/// is performed automatically by `preprocessedNetwork()` and is mainly just exposed for the unit +/// tests. +IECOREDELIGHT_API void convertUSDShaders( IECoreScene::ShaderNetwork *shaderNetwork ); + } // namespace ShaderNetworkAlgo } // namespace IECoreDelight \ No newline at end of file diff --git a/python/IECoreDelight/__init__.py b/python/IECoreDelight/__init__.py index 987be32722e..9f9ff52a17b 100644 --- a/python/IECoreDelight/__init__.py +++ b/python/IECoreDelight/__init__.py @@ -42,18 +42,3 @@ del os, pathlib # Don't pollute the namespace from ._IECoreDelight import * - -# The IECoreDelight module does not need any symbols from IECoreDelight, so MSVC tries -# to be helpful and not load IECoreDelight.dll. Skipping that means that -# 3Delight is never registered as a renderer, so we import / register it manually. - -import os - -if os.name == "nt" : - - import ctypes - - try : - ctypes.CDLL( "IECoreDelight.dll" ) - except : - raise ImportError diff --git a/python/IECoreDelightTest/ShaderNetworkAlgoTest.py b/python/IECoreDelightTest/ShaderNetworkAlgoTest.py new file mode 100644 index 00000000000..80d3168ab0a --- /dev/null +++ b/python/IECoreDelightTest/ShaderNetworkAlgoTest.py @@ -0,0 +1,559 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. 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 Alex Fuller 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 math +import unittest + +import imath + +import IECore +import IECoreScene + +import IECoreDelight + +class ShaderNetworkAlgoTest( unittest.TestCase ) : + + def testConvertUSDPreviewSurfaceEmission( self ) : + + for emissiveColor in ( imath.Color3f( 1 ), imath.Color3f( 0 ), None ) : + + parameters = {} + if emissiveColor is not None : + parameters["emissiveColor"] = IECore.Color3fData( emissiveColor ) + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", parameters + ) + }, + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + convertedShader = convertedNetwork.getShader( "previewSurface" ) + self.assertEqual( convertedShader.name, "dlStandard" ) + self.assertEqual( + convertedShader.parameters["emission_color"].value, + emissiveColor if emissiveColor is not None else imath.Color3f( 0 ) + ) + + if emissiveColor is not None and emissiveColor != imath.Color3f( 0 ) : + self.assertEqual( convertedShader.parameters["emission_w"], IECore.FloatData( 1 ) ) + else : + self.assertEqual( convertedShader.parameters["emission_w"], IECore.FloatData( 0 ) ) + + # Repeat, but with an input connection as well as the parameter value + + network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) + network.addConnection( ( ( "texture", "rgb" ), ( "previewSurface", "emissiveColor" ) ) ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + convertedShader = convertedNetwork.getShader( "previewSurface" ) + self.assertEqual( convertedShader.name, "dlStandard" ) + + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "emission_color" ) ), + ( "texture", "rgb" ), + ) + self.assertEqual( convertedShader.parameters["emission_w"], IECore.FloatData( 1 ) ) + + def testConvertUSDFloat3ToColor3f( self ) : + + # Although UsdPreviewSurface parameters are defined to be `color3f` in the spec, + # some USD files seem to provide `float3` values instead. For example : + # + # https://github.com/usd-wg/assets/blob/64ebce19c9a6c795862548066bc1070bf0f7f955/test_assets/AlphaBlendModeTest/AlphaBlendModeTest.usd#L27 + # + # Make sure that we convert these to colours for consumption by 3Delight. + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", + { + "diffuseColor" : imath.V3f( 0, 0.25, 0.5 ), + } + ) + }, + output = "previewSurface", + ) + + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( network ) + self.assertEqual( + network.getShader( "previewSurface" ).parameters["base_color"], + IECore.Color3fData( imath.Color3f( 0, 0.25, 0.5 ) ) + ) + + def testConvertUSDOpacity( self ) : + + # The results of this type of conversion may also be verified visually using + # the AlphaBlendModeTest asset found here : + # + # https://github.com/usd-wg/assets/tree/main/test_assets/AlphaBlendModeTest + # + # > Note : the "cutoff" positions are incorrect, because 3Delight's colorspace + # > transformation of the sRGB PNG textures also affects their alpha channel. + # > Loading these textures with `sourceColorSpace`=`raw` results in the correct + # > cutoff positions. + + for opacity in ( 0.25, 1.0, None ) : + for opacityThreshold in ( 0.0, 0.5, None ) : + + parameters = {} + if opacity is not None : + parameters["opacity"] = IECore.FloatData( opacity ) + if opacityThreshold is not None : + parameters["opacityThreshold"] = IECore.FloatData( opacityThreshold ) + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", parameters + ) + }, + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.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 + + self.assertEqual( + convertedShader.parameters["opacity"].value, + imath.Color3f( expectedOpacity ) + ) + + # Repeat, but with an input connection as well as the parameter value + + network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) + network.addConnection( ( ( "texture", "a" ), ( "previewSurface", "opacity" ) ) ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + if opacityThreshold : + + self.assertEqual( len( convertedNetwork ), 4 ) + opacityInput = convertedNetwork.input( ( "previewSurface", "opacity" ) ) + self.assertEqual( opacityInput, ( "previewSurfaceOpacityMultiply", "o_output" ) ) + self.assertEqual( convertedNetwork.getShader( opacityInput.shader ).name, "multiplyDivide" ) + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "input1" ) ), + ( "texture", "a" ) + ) + for c in "XYZ" : + multiplyInput = convertedNetwork.input( ( "previewSurfaceOpacityMultiply", "input2{}".format( c ) ) ) + self.assertEqual( multiplyInput, ( "previewSurfaceOpacityCompare", "success" ) ) + + self.assertEqual( + convertedNetwork.input( ( "previewSurfaceOpacityCompare", "a" ) ), + ( "texture", "a" ) + ) + + compareShader = convertedNetwork.getShader( "previewSurfaceOpacityCompare" ) + self.assertEqual( compareShader.parameters["condition"].value, 2 ) + self.assertEqual( compareShader.parameters["b"].value, opacityThreshold ) + + else : + + self.assertEqual( len( convertedNetwork ), 2 ) + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "opacity" ) ), + ( "texture", "a" ) + ) + + def testConvertUSDSpecular( self ) : + + for useSpecularWorkflow in ( True, False ) : + for specularColor in ( imath.Color3f( 0, 0.25, 0.5 ), None ) : + with self.subTest( useSpecularWorkflow = useSpecularWorkflow, specularColor = specularColor ) : + + parameters = { + "metallic" : 0.5, + "useSpecularWorkflow" : int( useSpecularWorkflow ), + } + if specularColor is not None : + parameters["specularColor"] = specularColor + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", parameters + ) + }, + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + convertedShader = convertedNetwork.getShader( "previewSurface" ) + + if useSpecularWorkflow : + self.assertEqual( + convertedShader.parameters["specular_color"].value, + specularColor if specularColor is not None else imath.Color3f( 0 ) + ) + self.assertNotIn( "metallic", convertedShader.parameters ) + else : + self.assertEqual( convertedShader.parameters["metalness"].value, 0.5 ) + self.assertNotIn( "specular_color", convertedShader.parameters ) + + # Repeat with input connections + + network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) + network.addConnection( ( ( "texture", "rgb" ), ( "previewSurface", "specularColor" ) ) ) + network.addConnection( ( ( "texture", "r" ), ( "previewSurface", "metallic" ) ) ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + if useSpecularWorkflow : + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "specular_color" ) ), + ( "texture", "rgb" ), + ) + self.assertFalse( convertedNetwork.input( ( "previewSurface", "metalness" ) ) ) + else : + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "metalness" ) ), + ( "texture", "r" ), + ) + self.assertFalse( convertedNetwork.input( ( "previewSurface", "specular_color" ) ) ) + + self.assertFalse( convertedNetwork.input( ( "previewSurface", "specularColor" ) ) ) + + def testConvertUSDClearcoat( self ) : + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( + "UsdPreviewSurface", "surface", + { + "clearcoat" : 0.75, + "clearcoatRoughness" : 0.25, + } + ) + }, + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + convertedShader = convertedNetwork.getShader( "previewSurface" ) + + self.assertEqual( convertedShader.parameters["coat"].value, 0.75 ) + self.assertEqual( convertedShader.parameters["coat_roughness"].value, 0.25 ) + + # Repeat with input connections + + network.addShader( "texture", IECoreScene.Shader( "UsdUVTexture" ) ) + network.addConnection( ( ( "texture", "r" ), ( "previewSurface", "clearcoat" ) ) ) + network.addConnection( ( ( "texture", "g" ), ( "previewSurface", "clearcoatRoughness" ) ) ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "coat" ) ), + ( "texture", "r" ), + ) + self.assertEqual( + convertedNetwork.input( ( "previewSurface", "coat_roughness" ) ), + ( "texture", "g" ), + ) + + def testConvertSimpleUSDUVTexture( self ) : + + for uvPrimvar in ( "st", "customUV" ) : + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( "UsdPreviewSurface" ), + "texture" : IECoreScene.Shader( + "UsdUVTexture", "shader", + { + "file" : "test.png", + "wrapS" : "useMetadata", + "wrapT" : "repeat", + "sourceColorSpace" : "auto", + } + ), + "uvReader" : IECoreScene.Shader( + "UsdPrimvarReader_float2", "shader", + { + "varname" : uvPrimvar, + } + ), + }, + connections = [ + ( ( "uvReader", "result" ), ( "texture", "st" ) ), + ( ( "texture", "rgb" ), ( "previewSurface", "diffuseColor" ) ), + ], + output = "previewSurface", + ) + + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( network ) + + self.assertEqual( network.input( ( "previewSurface", "base_color" ) ), ( "texture", "rgb" ) ) + + texture = network.getShader( "texture" ) + self.assertEqual( texture.name, "__usd/__usdUVTexture" ) + self.assertEqual( texture.parameters["file"].value, "test.png" ) + self.assertEqual( texture.parameters["wrapS"].value, "useMetadata" ) + self.assertEqual( texture.parameters["wrapT"].value, "repeat" ) + self.assertEqual( texture.parameters["file_meta_colorspace"].value, "auto" ) + + def testConvertSimpleUSDNormalTexture( self ) : + + for uvPrimvar in ( "st", "customUV" ) : + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( "UsdPreviewSurface" ), + "normalTexture" : IECoreScene.Shader( + "UsdUVTexture", "shader", + { + "file" : "test.png", + "wrapS" : "useMetadata", + "wrapT" : "repeat", + "sourceColorSpace" : "raw", + } + ), + "uvReader" : IECoreScene.Shader( + "UsdPrimvarReader_float2", "shader", + { + "varname" : uvPrimvar, + } + ), + }, + connections = [ + ( ( "uvReader", "result" ), ( "normalTexture", "st" ) ), + ( ( "normalTexture", "rgb" ), ( "previewSurface", "normal" ) ), + ], + output = "previewSurface", + ) + + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( network ) + + bump2d = network.getShader( "previewSurfaceNormal" ) + self.assertEqual( bump2d.name, "bump2d" ) + self.assertEqual( bump2d.parameters["bumpInterp"].value, 1 ) + + self.assertEqual( network.input( ( "previewSurface", "input_normal" ) ), ( "previewSurfaceNormal", "outNormal" ) ) + # We insert a shader to convert UsdPreviewSurface's expectation of signed normals back into color for the bump2d shader. + self.assertEqual( network.input( ( "previewSurfaceNormal", "bumpNormal" ) ), ( "previewSurfaceSignedToColor", "out" ) ) + # The bump2d shader requires the same uv coordinates as the normal texture. + self.assertEqual( network.input( ( "previewSurfaceNormal", "uvCoord" ) ), network.input( ( "normalTexture", "uvCoord" ) ) ) + + texture = network.getShader( "normalTexture" ) + self.assertEqual( texture.name, "__usd/__usdUVTexture" ) + self.assertEqual( texture.parameters["file"].value, "test.png" ) + self.assertEqual( texture.parameters["wrapS"].value, "useMetadata" ) + self.assertEqual( texture.parameters["wrapT"].value, "repeat" ) + self.assertEqual( texture.parameters["file_meta_colorspace"].value, "raw" ) + + def testConvertTransformedUSDUVTexture( self ) : + + # The results of this type of conversion may also be verified visually using + # the TextureTransformTest asset found here : + # + # https://github.com/usd-wg/assets/tree/main/test_assets/TextureTransformTest + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( "UsdPreviewSurface" ), + "texture" : IECoreScene.Shader( + "UsdUVTexture", "shader", + { + "file" : "test.png", + "wrapS" : "repeat", + "wrapT" : "repeat", + "sourceColorSpace" : "auto", + } + ), + "transform" : IECoreScene.Shader( + "UsdTransform2d", "shader", + { + "rotation" : 90.0, + } + ), + "uvReader" : IECoreScene.Shader( + "UsdPrimvarReader_float2", "shader", + { + "varname" : "st", + } + ), + }, + connections = [ + ( ( "uvReader", "result" ), ( "transform", "in" ) ), + ( ( "transform", "result" ), ( "texture", "st" ) ), + ( ( "texture", "rgb" ), ( "previewSurface", "diffuseColor" ) ), + ], + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + texture = convertedNetwork.getShader( "texture" ) + self.assertEqual( texture.name, "__usd/__usdUVTexture" ) + self.assertEqual( texture.parameters["file"].value, "test.png" ) + self.assertEqual( texture.parameters["wrapS"].value, "repeat" ) + self.assertEqual( texture.parameters["wrapT"].value, "repeat" ) + self.assertEqual( texture.parameters["file_meta_colorspace"].value, "auto" ) + + uvReader = convertedNetwork.getShader( "uvReader" ) + self.assertEqual( uvReader.name, "dlPrimitiveAttribute" ) + + transform = convertedNetwork.getShader( "transform" ) + self.assertEqual( transform.name, "__usd/__matrixTransformUV" ) + self.assertEqual( transform.parameters["m"].value, imath.M44f().rotate( imath.V3f( 0, 0, math.radians( 90 ) ) ) ) + + self.assertEqual( convertedNetwork.input( ( "transform", "uvCoord" ) ), ( "uvReader", "o_uv" ) ) + + def testConvertUSDUVTextureColor4Parameters( self ) : + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( "UsdPreviewSurface" ), + "texture" : IECoreScene.Shader( + "UsdUVTexture", "shader", + { + "fallback" : imath.Color4f( 1, 2, 3, 4 ), + "scale" : imath.Color4f( 0.1, 0.2, 0.3, 0.4 ), + "bias" : imath.Color4f( 0.2, 0.4, 0.6, 0.8 ), + } + ), + }, + connections = [ + ( ( "texture", "rgb" ), ( "previewSurface", "diffuseColor" ) ), + ( ( "texture", "a" ), ( "previewSurface", "roughness" ) ), + ( ( "texture", "r" ), ( "previewSurface", "metallic" ) ), + ], + output = "previewSurface", + ) + + convertedNetwork = network.copy() + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( convertedNetwork ) + + texture = convertedNetwork.getShader( "texture" ) + self.assertEqual( texture.name, "__usd/__usdUVTexture" ) + self.assertEqual( texture.parameters["fallback"].value, imath.Color4f( 1, 2, 3, 4 ) ) + self.assertEqual( texture.parameters["scale"].value, imath.Color4f( 0.1, 0.2, 0.3, 0.4 ) ) + self.assertEqual( texture.parameters["bias"].value, imath.Color4f( 0.2, 0.4, 0.6, 0.8 ) ) + + def testConvertUSDPrimvarReader( self ) : + + for usdDataType, convertedShaderType, usdFallback, convertedDefault in [ + ( "float", "ObjectProcessing/InFloat", 2.0, 2.0 ), + ( "float2", "dlPrimitiveAttribute", imath.V2f( 1, 2 ), imath.Color3f( 1, 2, 0 ) ), + ( "float3", "ObjectProcessing/InColor", imath.V3f( 1, 2, 3 ), imath.Color3f( 1, 2, 3 ) ), + ( "normal", "ObjectProcessing/InColor", imath.V3f( 1, 2, 3 ), imath.Color3f( 1, 2, 3 ) ), + ( "point", "ObjectProcessing/InColor", imath.V3f( 1, 2, 3 ), imath.Color3f( 1, 2, 3 ) ), + ( "vector", "ObjectProcessing/InColor", imath.V3f( 1, 2, 3 ), imath.Color3f( 1, 2, 3 ) ), + ( "int", "ObjectProcessing/InInt", 10, 10 ), + ( "string", "ObjectProcessing/InString", "hi", "hi" ), + ] : + + network = IECoreScene.ShaderNetwork( + shaders = { + "reader" : IECoreScene.Shader( + "UsdPrimvarReader_{}".format( usdDataType ), "shader", + { + "varname" : "test", + "fallback" : usdFallback, + } + ), + }, + output = "reader", + ) + + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( network ) + + reader = network.getShader( "reader" ) + self.assertEqual( reader.name, convertedShaderType ) + self.assertEqual( len( reader.parameters ), 3 if convertedShaderType == "dlPrimitiveAttribute" else 2 ) + self.assertEqual( reader.parameters["attribute_name" if convertedShaderType == "dlPrimitiveAttribute" else "name"].value, "test" ) + self.assertEqual( reader.parameters["fallback_value" if convertedShaderType == "dlPrimitiveAttribute" else "defaultValue"].value, convertedDefault ) + + def testConvertUSDUVTextureUDIM( self ) : + + network = IECoreScene.ShaderNetwork( + shaders = { + "previewSurface" : IECoreScene.Shader( "UsdPreviewSurface" ), + "texture" : IECoreScene.Shader( + "UsdUVTexture", "shader", + { + "file" : "test..png", + "wrapS" : "useMetadata", + "wrapT" : "repeat", + "sourceColorSpace" : "sRGB", + } + ), + "uvReader" : IECoreScene.Shader( + "UsdPrimvarReader_float2", "shader", + { + "varname" : "st", + } + ), + }, + connections = [ + ( ( "uvReader", "result" ), ( "texture", "st" ) ), + ( ( "texture", "rgb" ), ( "previewSurface", "diffuseColor" ) ), + ], + output = "previewSurface", + ) + + IECoreDelight.ShaderNetworkAlgo.convertUSDShaders( network ) + + self.assertEqual( network.input( ( "previewSurface", "base_color" ) ), ( "texture", "rgb" ) ) + + texture = network.getShader( "texture" ) + self.assertEqual( texture.name, "__usd/__usdUVTexture" ) + self.assertEqual( texture.parameters["file"].value, "test.UDIM.png" ) + self.assertEqual( texture.parameters["file_meta_colorspace"].value, "sRGB" ) + +if __name__ == "__main__": + unittest.main() diff --git a/python/IECoreDelightTest/__init__.py b/python/IECoreDelightTest/__init__.py index 5820c50be58..a0f3fbaa7fa 100644 --- a/python/IECoreDelightTest/__init__.py +++ b/python/IECoreDelightTest/__init__.py @@ -35,6 +35,7 @@ ########################################################################## from .RendererTest import RendererTest +from .ShaderNetworkAlgoTest import ShaderNetworkAlgoTest if __name__ == "__main__": import unittest diff --git a/shaders/__usd/__matrixTransformUV.osl b/shaders/__usd/__matrixTransformUV.osl new file mode 100644 index 00000000000..3468ff7665f --- /dev/null +++ b/shaders/__usd/__matrixTransformUV.osl @@ -0,0 +1,47 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. 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. +// +////////////////////////////////////////////////////////////////////////// + +shader __matrixTransformUV +( + float uvCoord[2] = { 0, 0 }, + matrix m = 1, + output float outUV[2] = { 0, 0 } [[ string correspondingInput = "uvCoord" ]] +) +{ + point transformedUV = transform( m, point( uvCoord[0], uvCoord[1], 0 ) ); + outUV[0] = transformedUV.x; + outUV[1] = transformedUV.y; +} diff --git a/shaders/__usd/__signedToColor.osl b/shaders/__usd/__signedToColor.osl new file mode 100644 index 00000000000..96c9f754369 --- /dev/null +++ b/shaders/__usd/__signedToColor.osl @@ -0,0 +1,44 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. 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. +// +////////////////////////////////////////////////////////////////////////// + +shader __signedToColor +( + color in = 0, + output color out = 0 [[ string correspondingInput = "in" ]], +) +{ + out = in * 0.5 + 0.5; +} diff --git a/shaders/__usd/__usdUVTexture.osl b/shaders/__usd/__usdUVTexture.osl new file mode 100644 index 00000000000..22aeeb4fffc --- /dev/null +++ b/shaders/__usd/__usdUVTexture.osl @@ -0,0 +1,80 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2024, Cinesite VFX Ltd. 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. +// +////////////////////////////////////////////////////////////////////////// + +string convertWrap( string wrap ) +{ + if( wrap == "useMetadata" ) + { + return "default"; + } + if( wrap == "repeat" ) + { + return "periodic"; + } + + return wrap; +} + +shader __usdUVTexture +( + string file = "", + string file_meta_colorspace = "", + float uvCoord[2] = {0, 0}, + string wrapS = "default", + string wrapT = "default", + float fallback[4] = {0, 0, 0, 1}, + float scale[4] = {1, 1, 1, 1}, + float bias[4] = {0, 0, 0, 0}, + + output color rgb = color(0), + output float r = 0, + output float g = 0, + output float b = 0, + output float a = 0, +) +{ + rgb = texture( + file, uvCoord[0], uvCoord[1], + "missingcolor", color( fallback[0], fallback[1], fallback[2] ), "missingalpha", fallback[3], + "swrap", convertWrap( wrapS ), "twrap", convertWrap( wrapT ), "alpha", a + ); + + rgb = rgb * color( scale[0], scale[1], scale[2] ) + color( bias[0], bias[1], bias[2] ); + r = rgb[0]; + g = rgb[1]; + b = rgb[2]; + a = a * scale[3] + bias[3]; +} diff --git a/src/IECoreArnold/ShaderNetworkAlgo.cpp b/src/IECoreArnold/ShaderNetworkAlgo.cpp index f8aa41461b9..f74b62bd7e4 100644 --- a/src/IECoreArnold/ShaderNetworkAlgo.cpp +++ b/src/IECoreArnold/ShaderNetworkAlgo.cpp @@ -998,7 +998,7 @@ void IECoreArnold::ShaderNetworkAlgo::convertUSDShaders( ShaderNetwork *shaderNe else if( shader->getName() == "UsdTransform2d" ) { newShader = new Shader( "matrix_multiply_vector" ); - transferUSDParameter( shaderNetwork, handle, shader.get(), g_inParameter, newShader.get(), g_inputParameter, string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_inParameter, newShader.get(), g_inputParameter, Color3f( 0 ) ); const V2f t = parameterValue( shader.get(), g_translationParameter, V2f( 0 ) ); const float r = parameterValue( shader.get(), g_rotationParameter, 0.0f ); const V2f s = parameterValue( shader.get(), g_scaleParameter, V2f( 1 ) ); diff --git a/src/IECoreDelight/ParameterList.cpp b/src/IECoreDelight/ParameterList.cpp index ece3d312d3b..25842c4f462 100644 --- a/src/IECoreDelight/ParameterList.cpp +++ b/src/IECoreDelight/ParameterList.cpp @@ -197,6 +197,13 @@ NSIParam_t ParameterList::parameter( const char *name, const IECore::Data *value result.data = static_cast( value )->baseReadable(); result.count = 1; break; + case Color4fDataTypeId : + result.type = NSITypeFloat; + result.arraylength = 4; + result.flags |= NSIParamIsArray; + result.data = static_cast( value )->baseReadable(); + result.count = 1; + break; case StringDataTypeId : { result.type = NSITypeString; @@ -206,6 +213,11 @@ NSIParam_t ParameterList::parameter( const char *name, const IECore::Data *value result.count = 1; break; } + case M44fDataTypeId : + result.type = NSITypeMatrix; + result.data = static_cast( value )->baseReadable(); + result.count = 1; + break; // Vector case IntVectorDataTypeId : { diff --git a/src/IECoreDelight/Renderer.cpp b/src/IECoreDelight/Renderer.cpp index 0fcb6972169..50d6a8f488d 100644 --- a/src/IECoreDelight/Renderer.cpp +++ b/src/IECoreDelight/Renderer.cpp @@ -650,6 +650,7 @@ std::array g_surfaceShaderAttributeNames = { "osl:light", "light std::array g_volumeShaderAttributeNames = { "osl:volume", "volume" }; std::array g_displacementShaderAttributeNames = { "osl:displacement", "displacement" }; const InternedString g_USDLightAttributeName = "light"; +const InternedString g_USDSurfaceAttributeName = "surface"; IECore::InternedString g_setsAttributeName( "sets" ); @@ -727,10 +728,10 @@ class DelightAttributes : public IECoreScenePreview::Renderer::AttributesInterfa params.add( m.first.c_str(), d, true ); } } - else if( boost::contains( m.first.string(), ":" ) || m.first == g_USDLightAttributeName ) + else if( boost::contains( m.first.string(), ":" ) || m.first == g_USDLightAttributeName || m.first == g_USDSurfaceAttributeName ) { // Attribute for another renderer - ignore - // Or a USDLight, which we've handled above - ignore + // Or a USD light/surface, which we've handled above - ignore } else { diff --git a/src/IECoreDelight/ShaderNetworkAlgo.cpp b/src/IECoreDelight/ShaderNetworkAlgo.cpp index 07dbf2b33e0..328d4806ac3 100644 --- a/src/IECoreDelight/ShaderNetworkAlgo.cpp +++ b/src/IECoreDelight/ShaderNetworkAlgo.cpp @@ -41,6 +41,7 @@ #include "IECoreScene/ShaderNetworkAlgo.h" +#include "IECore/AngleConversion.h" #include "IECore/MessageHandler.h" #include "IECore/SearchPath.h" #include "IECore/SimpleTypedData.h" @@ -54,6 +55,7 @@ #endif #include "boost/algorithm/string/predicate.hpp" +#include "boost/algorithm/string/replace.hpp" using namespace Imath; using namespace IECore; @@ -396,32 +398,91 @@ Color3f blackbody( float kelvins ) } const InternedString g_angleParameter( "angle" ); +const InternedString g_attributeNameParameter( "attribute_name" ); +const InternedString g_attributeTypeParameter( "attribute_type" ); const InternedString g_aParameter( "a" ); const InternedString g_bParameter( "b" ); +const InternedString g_baseParameter( "base" ); +const InternedString g_baseColorParameter( "base_color" ); +const InternedString g_biasParameter( "bias" ); +const InternedString g_bumpInterpParameter( "bumpInterp" ); +const InternedString g_bumpNormalParameter( "bumpNormal" ); +const InternedString g_clearcoatParameter( "clearcoat" ); +const InternedString g_clearcoatRoughnessParameter( "clearcoatRoughness" ); +const InternedString g_coatParameter( "coat" ); +const InternedString g_coatRoughnessParameter( "coat_roughness" ); const InternedString g_colorParameter( "color" ); const InternedString g_colorTemperatureParameter( "colorTemperature" ); +const InternedString g_conditionParameter( "condition" ); const InternedString g_coneAngleParameter( "coneAngle" ); const InternedString g_penumbraAngleParameter( "penumbraAngle" ); +const InternedString g_defaultValueParameter( "defaultValue" ); const InternedString g_diffuseParameter( "diffuse" ); +const InternedString g_diffuseColorParameter( "diffuseColor" ); +const InternedString g_emissiveColorParameter( "emissiveColor" ); +const InternedString g_emissionWeightParameter( "emission_w" ); +const InternedString g_emissionColorParameter( "emission_color" ); const InternedString g_enableColorTemperatureParameter( "enableColorTemperature" ); const InternedString g_exposureParameter( "exposure" ); +const InternedString g_fallbackParameter( "fallback" ); +const InternedString g_fallbackValueParameter( "fallback_value" ); +const InternedString g_fileParameter( "file" ); +const InternedString g_fileMetaColorSpaceParameter( "file_meta_colorspace" ); const InternedString g_gParameter( "g" ); const InternedString g_heightParameter( "height" ); +const InternedString g_inParameter( "in" ); +const InternedString g_input1Parameter( "input1" ); +const InternedString g_input2XParameter( "input2X" ); +const InternedString g_input2YParameter( "input2Y" ); +const InternedString g_input2ZParameter( "input2Z" ); +const InternedString g_inputNormalParameter( "input_normal" ); const InternedString g_intensityParameter( "intensity" ); +const InternedString g_iorParameter( "ior" ); const InternedString g_lengthParameter ("length" ); const InternedString g_radiusParameter( "radius" ); +const InternedString g_mParameter( "m" ); +const InternedString g_metallicParameter( "metallic" ); +const InternedString g_metalnessParameter( "metalness" ); const InternedString g_multiplyColorParameter( "b" ); const InternedString g_multiplyInputParameter( "a" ); const InternedString g_multiplyOutputParameter( "out" ); +const InternedString g_nameParameter( "name" ); +const InternedString g_normalParameter( "normal" ); const InternedString g_normalizeParameter( "normalize" ); +const InternedString g_opacityParameter( "opacity" ); +const InternedString g_opacityThresholdParameter( "opacityThreshold" ); +const InternedString g_outParameter( "out" ); +const InternedString g_oOutputParameter( "o_output" ); +const InternedString g_oUVParameter( "o_uv" ); +const InternedString g_outUVParameter( "outUV" ); +const InternedString g_outNormalParameter( "outNormal" ); const InternedString g_rParameter( "r" ); +const InternedString g_resultParameter( "result" ); +const InternedString g_rgbParameter( "rgb" ); +const InternedString g_rotationParameter( "rotation" ); +const InternedString g_roughnessParameter( "roughness" ); +const InternedString g_scaleParameter( "scale" ); const InternedString g_shapingConeAngleParameter( "shaping:cone:angle" ); const InternedString g_shapingConeSoftnessParameter( "shaping:cone:softness" ); +const InternedString g_sourceColorSpaceParameter( "sourceColorSpace" ); const InternedString g_specularParameter( "specular" ); +const InternedString g_specularColorParameter( "specularColor" ); +const InternedString g_specularColorDelightParameter( "specular_color" ); +const InternedString g_specularIORParameter( "specular_IOR" ); +const InternedString g_specularRoughnessParameter( "specular_roughness" ); +const InternedString g_stParameter( "st" ); +const InternedString g_successParameter( "success" ); const InternedString g_textureFileParameter( "texture:file" ); const InternedString g_textureFormatParameter( "texture:format" ); const InternedString g_textureOutputParameter( "outColor" ); +const InternedString g_translationParameter( "translation" ); +const InternedString g_useSpecularWorkflowParameter( "useSpecularWorkflow" ); +const InternedString g_uvCoordParameter( "uvCoord" ); +const InternedString g_valueParameter( "value" ); +const InternedString g_varnameParameter( "varname" ); const InternedString g_widthParameter( "width" ); +const InternedString g_wrapSParameter( "wrapS" ); +const InternedString g_wrapTParameter( "wrapT" ); const InternedString g_dlColorParameter( "i_color" ); const InternedString g_dlDiffuseParameter( "diffuse_contribution" ); @@ -512,8 +573,59 @@ void transferUSDShapingParameters( ShaderNetwork *network, InternedString shader } } +template +void convertVecToColor( Shader *shader, InternedString parameterName ) +{ + const VecType v = parameterValue( shader, parameterName, VecType( 0 ) ); + ColorType c; + for( size_t i = 0; i < ColorType::dimensions(); ++i ) + { + c[i] = i < VecType::dimensions() ? v[i] : 0.0f; + } + + shader->parameters()[parameterName] = new typename DataTraits::DataType( c ); +} + +void removeInput( ShaderNetwork *network, const ShaderNetwork::Parameter ¶meter ) +{ + if( auto i = network->input( parameter ) ) + { + network->removeConnection( { i, parameter } ); + } +} + +// Map of USD shaders with `result` parameters to the output of their equivalent 3Delight shader. +const std::unordered_map g_resultParameterMap = { + { "UsdPrimvarReader_int", g_valueParameter }, + { "UsdPrimvarReader_float", g_valueParameter }, + { "UsdPrimvarReader_float2", g_oUVParameter }, + { "UsdPrimvarReader_float3", g_valueParameter }, + { "UsdPrimvarReader_float4", g_valueParameter }, + { "UsdPrimvarReader_normal", g_valueParameter }, + { "UsdPrimvarReader_point", g_valueParameter }, + { "UsdPrimvarReader_vector", g_valueParameter }, + { "UsdTransform2d", g_outUVParameter }, +}; + +const InternedString remapOutputParameterName( const InternedString name, const InternedString shaderName ) +{ + if( name == g_resultParameter ) + { + // `result` parameters are remapped based on the shader name + const auto it = g_resultParameterMap.find( shaderName ); + if( it != g_resultParameterMap.end() ) + { + return it->second; + } + } + + return InternedString(); +} + void replaceUSDShader( ShaderNetwork *network, InternedString handle, ShaderPtr &&newShader ) { + const InternedString shaderName = network->getShader( handle )->getName(); + // Replace original shader with the new. network->setShader( handle, std::move( newShader ) ); @@ -522,10 +634,10 @@ void replaceUSDShader( ShaderNetwork *network, InternedString handle, ShaderPtr std::vector outputConnections( range.begin(), range.end() ); for( auto &c : outputConnections ) { - if( c.source.name != g_rParameter && c.source.name != g_gParameter && c.source.name != g_bParameter && c.source.name != g_aParameter ) + if( c.source.name != g_rParameter && c.source.name != g_gParameter && c.source.name != g_bParameter && c.source.name != g_aParameter && c.source.name != g_rgbParameter ) { network->removeConnection( c ); - c.source.name = InternedString(); + c.source.name = remapOutputParameterName( c.source.name, shaderName ); network->addConnection( c ); } } @@ -630,6 +742,35 @@ const std::unordered_map g_shaderNameMap{ { "CylinderLight", "areaLight" } }; +void convertUSDUVTextures( ShaderNetwork *network ) +{ + for( const auto &[handle, shader] : network->shaders() ) + { + if( shader->getName() != "UsdUVTexture" ) + { + continue; + } + + ShaderPtr imageShader = new Shader( "__usd/__usdUVTexture", "osl:shader" ); + // Replace `` with 3Delight's `UDIM` convention. + std::string path = parameterValue( shader.get(), g_fileParameter, std::string() ); + boost::replace_last( path, "", "UDIM" ); + imageShader->parameters()[g_fileParameter] = new StringData( path ); + transferUSDParameter( network, handle, shader.get(), g_sourceColorSpaceParameter, imageShader.get(), g_fileMetaColorSpaceParameter, std::string( "auto" ) ); + + transferUSDParameter( network, handle, shader.get(), g_fallbackParameter, imageShader.get(), g_fallbackParameter, Color4f( 0, 0, 0, 1 ) ); + transferUSDParameter( network, handle, shader.get(), g_scaleParameter, imageShader.get(), g_scaleParameter, Color4f( 1 ) ); + transferUSDParameter( network, handle, shader.get(), g_biasParameter, imageShader.get(), g_biasParameter, Color4f( 0 ) ); + + transferUSDParameter( network, handle, shader.get(), g_wrapSParameter, imageShader.get(), g_wrapSParameter, std::string() ); + transferUSDParameter( network, handle, shader.get(), g_wrapTParameter, imageShader.get(), g_wrapTParameter, std::string() ); + + transferUSDParameter( network, handle, shader.get(), g_stParameter, imageShader.get(), g_uvCoordParameter, V2f( 0 ) ); + + replaceUSDShader( network, handle, std::move( imageShader ) ); + } +} + } // namespace namespace IECoreDelight @@ -640,75 +781,230 @@ namespace ShaderNetworkAlgo void convertUSDShaders( ShaderNetwork *shaderNetwork ) { + // Must convert these first, before we convert the connected + // UsdPrimvarReader inputs. + convertUSDUVTextures( shaderNetwork ); + for( const auto &[handle, shader] : shaderNetwork->shaders() ) { ShaderPtr newShader; - const auto it = g_shaderNameMap.find( shader->getName() ); - if( it != g_shaderNameMap.end() ) + if( shader->getName() == "UsdPreviewSurface" ) { - newShader = new Shader( it->second, "osl:light" ); - } + newShader = new Shader( "dlStandard", "osl:surface" ); + newShader->parameters()[g_baseParameter] = new FloatData( 1.0f ); - if( newShader ) - { - transferUSDLightParameters( shaderNetwork, handle, shader.get(), newShader.get() ); - transferUSDShapingParameters( shaderNetwork, handle, shader.get(), newShader.get() ); + // Easy stuff with a one-to-one correspondence between `UsdPreviewSurface` and `standard_surface`. + + transferUSDParameter( shaderNetwork, handle, shader.get(), g_diffuseColorParameter, newShader.get(), g_baseColorParameter, Color3f( 0.18 ) ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_roughnessParameter, newShader.get(), g_specularRoughnessParameter, 0.5f ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_clearcoatParameter, newShader.get(), g_coatParameter, 0.0f ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_clearcoatRoughnessParameter, newShader.get(), g_coatRoughnessParameter, 0.01f ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_iorParameter, newShader.get(), g_specularIORParameter, 1.5f ); + + // Emission. UsdPreviewSurface only has `emissiveColor`, which we transfer to `emission_color`. But then + // we need to turn on 3Delights's `emission_w` to that the `emission_color` is actually used. - // `pointLight` and `spotLight` are normalized by nature - // and normalization doesn't apply to `environmentLight` - if( newShader->getName() == "distantLight" || newShader->getName() == "areaLight" ) + transferUSDParameter( shaderNetwork, handle, shader.get(), g_emissiveColorParameter, newShader.get(), g_emissionColorParameter, Color3f( 0 ) ); + const bool hasEmission = + shaderNetwork->input( { handle, g_emissionColorParameter } ) || + parameterValue( newShader.get(), g_emissionColorParameter, Color3f( 0 ) ) != Color3f( 0 ); + ; + newShader->parameters()[g_emissionWeightParameter] = new FloatData( hasEmission ? 1.0f : 0.0f ); + + // Specular. + + if( parameterValue( shader.get(), g_useSpecularWorkflowParameter, 0 ) ) + { + transferUSDParameter( shaderNetwork, handle, shader.get(), g_specularColorParameter, newShader.get(), g_specularColorDelightParameter, Color3f( 0.0f ) ); + } + else { - transferUSDParameter( shaderNetwork, handle, shader.get(), g_normalizeParameter, newShader.get(), g_dlNormalizeParameter, false ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_metallicParameter, newShader.get(), g_metalnessParameter, 0.0f ); } - if( shader->getName() == "RectLight" ) + removeInput( shaderNetwork, { handle, g_metallicParameter } ); + removeInput( shaderNetwork, { handle, g_specularColorParameter } ); + + // Opacity. This is a float in USD and a colour in 3Delight. And USD + // has a funky `opacityThreshold` thing too, that we need to implement + // with a little compare/multiply network. + + float opacity = parameterValue( shader.get(), g_opacityParameter, 1.0f ); + const float opacityThreshold = parameterValue( shader.get(), g_opacityThresholdParameter, 0.0f ); + if( const ShaderNetwork::Parameter opacityInput = shaderNetwork->input( { handle, g_opacityParameter } ) ) { - const std::string textureFile = parameterValue( shader.get(), g_textureFileParameter, std::string() ); - if( textureFile != "" ) + if( opacityThreshold != 0.0f ) { - ShaderPtr textureShader = new Shader( "dlTexture" ); - textureShader->parameters()[g_dlTextureFileParameter] = new StringData( textureFile ); - // Add a `uvCoord` stub for `addDefaultUVShader()` to work with - textureShader->parameters()[g_uvCoordParameterName] = new FloatVectorData( { 0, 0 } ); - const InternedString textureHandle = shaderNetwork->addShader( handle.string() + "Texture", std::move( textureShader ) ); - - const Color3f color = parameterValue( shader.get(), g_colorParameter, Color3f( 1 ) ); - if( color != Color3f( 1 ) ) - { - // Multiply image with color - ShaderPtr multiplyShader = new Shader( "Maths/MultiplyColor" ); - multiplyShader->parameters()[g_multiplyColorParameter] = new Color3fData( color ); - const InternedString multiplyHandle = shaderNetwork->addShader( handle.string() + "Multiply", std::move( multiplyShader ) ); - shaderNetwork->addConnection( ShaderNetwork::Connection( { multiplyHandle, g_multiplyOutputParameter }, { handle, g_dlColorParameter } ) ); - shaderNetwork->addConnection( ShaderNetwork::Connection( { textureHandle, g_textureOutputParameter }, { multiplyHandle, g_multiplyInputParameter } ) ); - } - else - { - // Connect image directly - shaderNetwork->addConnection( ShaderNetwork::Connection( { textureHandle, g_textureOutputParameter }, { handle, g_dlColorParameter } ) ); - } + ShaderPtr compareShader = new Shader( "Utility/CompareFloat" ); + compareShader->parameters()[g_bParameter] = new FloatData( opacityThreshold ); + compareShader->parameters()[g_conditionParameter] = new IntData( 2 ); // Greater + const InternedString compareHandle = shaderNetwork->addShader( handle.string() + "OpacityCompare", std::move( compareShader ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( opacityInput, { compareHandle, g_aParameter } ) ); + ShaderPtr multiplyShader = new Shader( "multiplyDivide" ); + const InternedString multiplyHandle = shaderNetwork->addShader( handle.string() + "OpacityMultiply", std::move( multiplyShader ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( opacityInput, { multiplyHandle, g_input1Parameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { compareHandle, g_successParameter }, { multiplyHandle, g_input2XParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { compareHandle, g_successParameter }, { multiplyHandle, g_input2YParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { compareHandle, g_successParameter }, { multiplyHandle, g_input2ZParameter } ) ); + shaderNetwork->removeConnection( ShaderNetwork::Connection( opacityInput, { handle, g_opacityParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { multiplyHandle, g_oOutputParameter }, { handle, g_opacityParameter } ) ); } } - if( shader->getName() == "DomeLight" ) + else { - const std::string textureFile = parameterValue( shader.get(), g_textureFileParameter, std::string() ); - newShader->parameters()[g_dlEnvironmentTextureFileParameter] = new StringData( textureFile ); + opacity = opacity > opacityThreshold ? opacity : 0.0f; + } + + newShader->parameters()[g_opacityParameter] = new Color3fData( Color3f( opacity ) ); - if( !textureFile.empty() ) + // Normal + + if( const ShaderNetwork::Parameter normalInput = shaderNetwork->input( { handle, g_normalParameter } ) ) + { + ShaderPtr normalShader = new Shader( "bump2d", "osl:surface" ); + normalShader->parameters()[g_bumpInterpParameter] = new IntData( 1 ); + const InternedString normalHandle = shaderNetwork->addShader( handle.string() + "Normal", std::move( normalShader ) ); + // The UsdPreviewSurface specification expects normal maps to be provided to the shader as signed values, while + // 3Delight's bump2d shader does the conversion to signed itself, so we need first to convert back to colour. + ShaderPtr signedToColorShader = new Shader( "__usd/__signedToColor", "osl:surface" ); + const InternedString signedToColorHandle = shaderNetwork->addShader( handle.string() + "SignedToColor", std::move( signedToColorShader ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( normalInput, { signedToColorHandle, g_inParameter } ) ); + shaderNetwork->removeConnection( ShaderNetwork::Connection( normalInput, { handle, g_normalParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { signedToColorHandle, g_outParameter }, { normalHandle, g_bumpNormalParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { normalHandle, g_outNormalParameter }, { handle, g_inputNormalParameter } ) ); + if( const ShaderNetwork::Parameter uvInput = shaderNetwork->input( { normalInput.shader, g_uvCoordParameter } ) ) { - const std::string format = parameterValue( shader.get(), g_textureFormatParameter, std::string() ); - auto it = g_textureMappingModes.find( format ); - if( it == g_textureMappingModes.end() ) + // The bump2d shader requires the same UV coordinates as the normal texture. We assume the texture is the direct + // input of the UsdPreviewSurface shader's `normal` parameter. + shaderNetwork->addConnection( ShaderNetwork::Connection( uvInput, { normalHandle, g_uvCoordParameter } ) ); + } + } + } + else if( shader->getName() == "UsdTransform2d" ) + { + newShader = new Shader( "__usd/__matrixTransformUV", "osl:shader" ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_inParameter, newShader.get(), g_uvCoordParameter, V2f( 0 ) ); + const V2f t = parameterValue( shader.get(), g_translationParameter, V2f( 0 ) ); + const float r = parameterValue( shader.get(), g_rotationParameter, 0.0f ); + const V2f s = parameterValue( shader.get(), g_scaleParameter, V2f( 1 ) ); + M44f m; + m.translate( V3f( t.x, t.y, 0 ) ); + m.rotate( V3f( 0, 0, IECore::degreesToRadians( r ) ) ); + m.scale( V3f( s.x, s.y, 1 ) ); + newShader->parameters()[g_mParameter] = new M44fData( m ); + } + else if( shader->getName() == "UsdPrimvarReader_float" ) + { + newShader = new Shader( "ObjectProcessing/InFloat", "osl:surface" ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_varnameParameter, newShader.get(), g_nameParameter, std::string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_fallbackParameter, newShader.get(), g_defaultValueParameter, 0.0f ); + } + else if( shader->getName() == "UsdPrimvarReader_float2" ) + { + newShader = new Shader( "dlPrimitiveAttribute", "osl:surface" ); + newShader->parameters()[g_attributeTypeParameter] = new IntData( 3 ); // UV + transferUSDParameter( shaderNetwork, handle, shader.get(), g_varnameParameter, newShader.get(), g_attributeNameParameter, std::string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_fallbackParameter, newShader.get(), g_fallbackValueParameter, V2f( 0 ) ); + convertVecToColor( newShader.get(), g_fallbackValueParameter ); + } + else if( + shader->getName() == "UsdPrimvarReader_float3" || + shader->getName() == "UsdPrimvarReader_float4" || + shader->getName() == "UsdPrimvarReader_normal" || + shader->getName() == "UsdPrimvarReader_point" || + shader->getName() == "UsdPrimvarReader_vector" + ) + { + newShader = new Shader( "ObjectProcessing/InColor", "osl:surface" ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_varnameParameter, newShader.get(), g_nameParameter, std::string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_fallbackParameter, newShader.get(), g_defaultValueParameter, V3f( 0 ) ); + convertVecToColor( newShader.get(), g_defaultValueParameter ); + } + else if( shader->getName() == "UsdPrimvarReader_int" ) + { + newShader = new Shader( "ObjectProcessing/InInt", "osl:surface" ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_varnameParameter, newShader.get(), g_nameParameter, std::string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_fallbackParameter, newShader.get(), g_defaultValueParameter, 0 ); + } + else if( shader->getName() == "UsdPrimvarReader_string" ) + { + newShader = new Shader( "ObjectProcessing/InString", "osl:surface" ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_varnameParameter, newShader.get(), g_nameParameter, std::string() ); + transferUSDParameter( shaderNetwork, handle, shader.get(), g_fallbackParameter, newShader.get(), g_defaultValueParameter, std::string() ); + } + else + { + const auto it = g_shaderNameMap.find( shader->getName() ); + if( it != g_shaderNameMap.end() ) + { + newShader = new Shader( it->second, "osl:light" ); + } + + if( newShader ) + { + transferUSDLightParameters( shaderNetwork, handle, shader.get(), newShader.get() ); + transferUSDShapingParameters( shaderNetwork, handle, shader.get(), newShader.get() ); + + // `pointLight` and `spotLight` are normalized by nature + // and normalization doesn't apply to `environmentLight` + if( newShader->getName() == "distantLight" || newShader->getName() == "areaLight" ) + { + transferUSDParameter( shaderNetwork, handle, shader.get(), g_normalizeParameter, newShader.get(), g_dlNormalizeParameter, false ); + } + + if( shader->getName() == "RectLight" ) + { + const std::string textureFile = parameterValue( shader.get(), g_textureFileParameter, std::string() ); + if( textureFile != "" ) { - IECore::msg( IECore::Msg::Warning, "transferUSDTextureFile", fmt::format( "Unsupported mapping mode \"{}\"", format ) ); + ShaderPtr textureShader = new Shader( "dlTexture" ); + textureShader->parameters()[g_dlTextureFileParameter] = new StringData( textureFile ); + // Add a `uvCoord` stub for `addDefaultUVShader()` to work with + textureShader->parameters()[g_uvCoordParameterName] = new FloatVectorData( { 0, 0 } ); + const InternedString textureHandle = shaderNetwork->addShader( handle.string() + "Texture", std::move( textureShader ) ); + + const Color3f color = parameterValue( shader.get(), g_colorParameter, Color3f( 1 ) ); + if( color != Color3f( 1 ) ) + { + // Multiply image with color + ShaderPtr multiplyShader = new Shader( "Maths/MultiplyColor" ); + multiplyShader->parameters()[g_multiplyColorParameter] = new Color3fData( color ); + const InternedString multiplyHandle = shaderNetwork->addShader( handle.string() + "Multiply", std::move( multiplyShader ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { multiplyHandle, g_multiplyOutputParameter }, { handle, g_dlColorParameter } ) ); + shaderNetwork->addConnection( ShaderNetwork::Connection( { textureHandle, g_textureOutputParameter }, { multiplyHandle, g_multiplyInputParameter } ) ); + } + else + { + // Connect image directly + shaderNetwork->addConnection( ShaderNetwork::Connection( { textureHandle, g_textureOutputParameter }, { handle, g_dlColorParameter } ) ); + } } - else + } + if( shader->getName() == "DomeLight" ) + { + const std::string textureFile = parameterValue( shader.get(), g_textureFileParameter, std::string() ); + newShader->parameters()[g_dlEnvironmentTextureFileParameter] = new StringData( textureFile ); + + if( !textureFile.empty() ) { - newShader->parameters()[g_dlEnvironmentTextureFormatParameter] = new IntData( it->second ); + const std::string format = parameterValue( shader.get(), g_textureFormatParameter, std::string() ); + auto it = g_textureMappingModes.find( format ); + if( it == g_textureMappingModes.end() ) + { + IECore::msg( IECore::Msg::Warning, "transferUSDTextureFile", fmt::format( "Unsupported mapping mode \"{}\"", format ) ); + } + else + { + newShader->parameters()[g_dlEnvironmentTextureFormatParameter] = new IntData( it->second ); + } } } + } + } + if( newShader ) + { replaceUSDShader( shaderNetwork, handle, std::move( newShader ) ); } } diff --git a/src/IECoreDelightModule/IECoreDelightModule.cpp b/src/IECoreDelightModule/IECoreDelightModule.cpp index cedfc45205b..2eb01482cce 100644 --- a/src/IECoreDelightModule/IECoreDelightModule.cpp +++ b/src/IECoreDelightModule/IECoreDelightModule.cpp @@ -36,9 +36,18 @@ #include "boost/python.hpp" +#include "IECoreDelight/ShaderNetworkAlgo.h" + +using namespace boost::python; +using namespace IECoreDelight; + BOOST_PYTHON_MODULE( _IECoreDelight ) { - // We don't actually bind anything at present, but the module - // is still useful as a means of loading `libIECoreDelight` in - // Python, which makes the Delight renderer interface available. + + object shaderNetworkAlgoModule( borrowed( PyImport_AddModule( "IECoreDelight.ShaderNetworkAlgo" ) ) ); + scope().attr( "ShaderNetworkAlgo" ) = shaderNetworkAlgoModule; + scope shaderNetworkAlgoScope( shaderNetworkAlgoModule ); + + def( "convertUSDShaders", &ShaderNetworkAlgo::convertUSDShaders ); + }