diff --git a/Changes.md b/Changes.md index 4fcbbc621bc..0816e64f06e 100644 --- a/Changes.md +++ b/Changes.md @@ -1,7 +1,15 @@ 1.4.x.x (relative to 1.4.2.0) ======= +Improvements +----------- + +- Arnold : If it exists, an `ai:volume` attribute is preferred over an `ai:surface` attribute when resolving shaders for volumes. + +Fixes +----- +- Arnold : Fixed rendering of `ai:volume` shaders loaded from USD (#5830). 1.4.2.0 (relative to 1.4.1.0) ======= diff --git a/python/IECoreArnoldTest/RendererTest.py b/python/IECoreArnoldTest/RendererTest.py index c1427f00d89..c7e3fbfd1c4 100644 --- a/python/IECoreArnoldTest/RendererTest.py +++ b/python/IECoreArnoldTest/RendererTest.py @@ -4314,6 +4314,58 @@ def testArnoldOSLWithCode( self ) : del renderer + def testVolumeShaderAttribute( self ) : + + import IECoreVDB + + renderer = GafferScene.Private.IECoreScenePreview.Renderer.create( + "Arnold", + GafferScene.Private.IECoreScenePreview.Renderer.RenderType.Batch, + str( self.temporaryDirectory() / "test.ass" ) + ) + universe = ctypes.cast( renderer.command( "ai:queryUniverse", {} ), ctypes.POINTER( arnold.AtUniverse ) ) + + for withVolume in ( True, False ) : + for withSurface in ( True, False ) : + for step in ( 0.0, 0.1 ) : + with self.subTest( withVolume = withVolume, withSurface = withSurface, step = step ) : + + attributes = IECore.CompoundObject( { "ai:shape:step_size" : IECore.FloatData( step ) } ) + + if withVolume : + attributes["ai:volume"] = IECoreScene.ShaderNetwork( + shaders = { "shader" : IECoreScene.Shader( "standard_volume" ) }, + output = ( "shader", "out" ), + ) + + if withSurface : + attributes["ai:surface"] = IECoreScene.ShaderNetwork( + shaders = { "shader" : IECoreScene.Shader( "standard_surface" ) }, + output = ( "shader", "out" ), + ) + + attributes = renderer.attributes( attributes ) + + meshName = f"mesh{withVolume}{withSurface}{step}" + renderer.object( meshName, IECoreScene.MeshPrimitive.createSphere( 1 ), attributes ) + + vdbName = f"vdb{withVolume}{withSurface}{step}" + renderer.object( vdbName, IECoreVDB.VDBObject(), attributes ) + + meshShader = arnold.AiNodeGetPtr( arnold.AiNodeLookUpByName( universe, meshName ), "shader" ) + self.assertEqual( + arnold.AiNodeEntryGetName( arnold.AiNodeGetNodeEntry( meshShader ) ), + "standard_volume" if ( withVolume and step ) else "standard_surface" if withSurface else "utility" + ) + + vdbShader = arnold.AiNodeGetPtr( arnold.AiNodeLookUpByName( universe, vdbName ), "shader" ) + self.assertEqual( + arnold.AiNodeEntryGetName( arnold.AiNodeGetNodeEntry( vdbShader ) ), + "standard_volume" if withVolume else "standard_surface" if withSurface else "utility" + ) + + del renderer + def testInternedStringAttributes( self ) : r = GafferScene.Private.IECoreScenePreview.Renderer.create( diff --git a/src/IECoreArnold/Renderer.cpp b/src/IECoreArnold/Renderer.cpp index a94ac1e8cbf..1cd939d19c6 100644 --- a/src/IECoreArnold/Renderer.cpp +++ b/src/IECoreArnold/Renderer.cpp @@ -922,6 +922,7 @@ namespace bool isConvertedProcedural( const AtNode *node ); IECore::InternedString g_surfaceShaderAttributeName( "surface" ); +IECore::InternedString g_volumeShaderAttributeName( "volume" ); IECore::InternedString g_lightShaderAttributeName( "light" ); IECore::InternedString g_doubleSidedAttributeName( "doubleSided" ); IECore::InternedString g_setsAttributeName( "sets" ); @@ -951,6 +952,7 @@ IECore::InternedString g_volumeVisibilityAutoBumpAttributeName( "ai:autobump_vis IECore::InternedString g_subsurfaceVisibilityAutoBumpAttributeName( "ai:autobump_visibility:subsurface" ); IECore::InternedString g_arnoldSurfaceShaderAttributeName( "ai:surface" ); +IECore::InternedString g_arnoldVolumeShaderAttributeName( "ai:volume" ); IECore::InternedString g_arnoldLightShaderAttributeName( "ai:light" ); IECore::InternedString g_arnoldFilterMapAttributeName( "ai:filtermap" ); IECore::InternedString g_arnoldUVRemapAttributeName( "ai:uv_remap" ); @@ -1059,6 +1061,13 @@ class ArnoldAttributes : public IECoreScenePreview::Renderer::AttributesInterfac m_surfaceShader = shaderCache->get( surfaceShaderAttribute, attributes ); } + const IECoreScene::ShaderNetwork *volumeShaderAttribute = attribute( g_arnoldVolumeShaderAttributeName, attributes ); + volumeShaderAttribute = volumeShaderAttribute ? volumeShaderAttribute : attribute( g_volumeShaderAttributeName, attributes ); + if( volumeShaderAttribute ) + { + m_volumeShader = shaderCache->get( volumeShaderAttribute, attributes ); + } + if( auto filterMapAttribute = attribute( g_arnoldFilterMapAttributeName, attributes ) ) { m_filterMap = shaderCache->get( filterMapAttribute, attributes ); @@ -1375,9 +1384,9 @@ class ArnoldAttributes : public IECoreScenePreview::Renderer::AttributesInterfac AiNodeSetBool( node, g_matteArnoldString, m_shadingFlags & ArnoldAttributes::Matte ); - if( m_surfaceShader && m_surfaceShader->root() ) + if( AtNode *shader = preferredShader( geometry ) ) { - AiNodeSetPtr( node, g_shaderArnoldString, m_surfaceShader->root() ); + AiNodeSetPtr( node, g_shaderArnoldString, shader ); } else { @@ -1995,6 +2004,7 @@ class ArnoldAttributes : public IECoreScenePreview::Renderer::AttributesInterfac h.append( m_sidedness ); h.append( m_shadingFlags ); hashOptional( m_surfaceShader.get(), h ); + hashOptional( m_volumeShader.get(), h ); hashOptional( m_filterMap.get(), h ); hashOptional( m_uvRemap.get(), h ); hashOptional( m_lightShader.get(), h ); @@ -2018,10 +2028,33 @@ class ArnoldAttributes : public IECoreScenePreview::Renderer::AttributesInterfac h.append( m_sssSetName.c_str() ? m_sssSetName.c_str() : "" ); } + AtNode *preferredShader( const AtNode *geometry ) const + { + if( m_volumeShader ) + { + // Prefer the volume shader if we have one, and the geometry is either + // a volume or a shape being rendered as a volume (non-zero step size). + bool preferVolume = AiNodeIs( geometry, g_volumeArnoldString ); + if( !preferVolume && AiNodeEntryLookUpParameter( AiNodeGetNodeEntry( geometry ), g_stepSizeArnoldString ) ) + { + preferVolume = AiNodeGetFlt( geometry, g_stepSizeArnoldString ) > 0.0f; + } + if( preferVolume ) + { + return m_volumeShader->root(); + } + } + + // Otherwise use the surface shader. We use this even for volume geometry, + // because Gaffer has always assigned volume shaders as `ai:surface`. + return m_surfaceShader ? m_surfaceShader->root() : nullptr; + } + unsigned char m_visibility; unsigned char m_sidedness; unsigned char m_shadingFlags; ArnoldShaderPtr m_surfaceShader; + ArnoldShaderPtr m_volumeShader; ArnoldShaderPtr m_filterMap; ArnoldShaderPtr m_uvRemap; IECoreScene::ConstShaderNetworkPtr m_lightShader;