Skip to content

Commit f174f8d

Browse files
committed
USDScene : Fix writing of UsdLux lights
We were writing them as regular material assignments before, which might have round-tripped back into Gaffer OK, but which were not what other USD clients needed. We need to make sure that the primary shader is written as a `UsdLux*Light` prim, and that any input shaders are stored as children of that prim. We still share most of the implementation with the generic material-writing code, but with differences in orchestration. It would have been possible to continue to do this with one `writeShaderNetwork()` uber-function internally with a bunch of conditionals depending on whether or not we were writing lights. But I felt that it was clearer and more maintainable to have two small top-level functions that each made appropriate calls to a set of shared utility functions.
1 parent aab95d3 commit f174f8d

File tree

5 files changed

+236
-65
lines changed

5 files changed

+236
-65
lines changed

Changes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Improvements
99
Fixes
1010
-----
1111

12+
- USDScene : Fixed writing of lights so they are represented as appropriate `UsdLux*Light` prims in USD.
1213
- FrameRange : Prevented creation of FrameRanges with negative steps
1314
- IECore.dataTypeFromElement : Fixed support for list to Vector conversions
1415
- LinkedScene : Fixed bug where `linkLocations` attribute was baked incorrectly if the link target location wasn't the ROOT

contrib/IECoreUSD/include/IECoreUSD/ShaderAlgo.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,10 @@ IECOREUSD_API pxr::UsdShadeOutput writeShaderNetwork( const IECoreScene::ShaderN
6060
IECOREUSD_API IECoreScene::ShaderNetworkPtr readShaderNetwork( const pxr::UsdShadeOutput &output );
6161

6262
#if PXR_VERSION >= 2111
63+
/// Writes a UsdLuxLight from a shader network.
64+
IECOREUSD_API void writeLight( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim prim );
6365
/// Reads a ShaderNetwork from a light.
64-
IECOREUSD_API IECoreScene::ShaderNetworkPtr readShaderNetwork( const pxr::UsdLuxLightAPI &light );
66+
IECOREUSD_API IECoreScene::ShaderNetworkPtr readLight( const pxr::UsdLuxLightAPI &light );
6567
#endif
6668

6769
} // namespace ShaderAlgo

contrib/IECoreUSD/src/IECoreUSD/ShaderAlgo.cpp

Lines changed: 145 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@
4343

4444
#if PXR_VERSION >= 2111
4545
#include "pxr/usd/usdLux/cylinderLight.h"
46+
#include "pxr/usd/usdLux/nonboundableLightBase.h"
4647
#include "pxr/usd/usdLux/sphereLight.h"
48+
49+
#include "pxr/usd/usd/schemaRegistry.h"
4750
#endif
4851

4952
#include "boost/algorithm/string/replace.hpp"
@@ -217,15 +220,89 @@ IECoreScene::ShaderNetwork::Parameter readShaderNetworkWalk( const pxr::SdfPath
217220
}
218221
}
219222

223+
IECoreScene::ConstShaderNetworkPtr adaptShaderNetworkForWriting( const IECoreScene::ShaderNetwork *shaderNetwork )
224+
{
225+
IECoreScene::ShaderNetworkPtr result = shaderNetwork->copy();
226+
IECoreScene::ShaderNetworkAlgo::expandSplines( result.get() );
227+
IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( result.get() );
228+
return result;
229+
}
230+
231+
pxr::UsdShadeConnectableAPI createShaderPrim( const IECoreScene::Shader *shader, const pxr::UsdStagePtr &stage, const pxr::SdfPath &path )
232+
{
233+
pxr::UsdShadeShader usdShader = pxr::UsdShadeShader::Define( stage, path );
234+
if( !usdShader )
235+
{
236+
throw IECore::Exception( "Could not create shader at " + path.GetAsString() );
237+
}
238+
const std::string type = shader->getType();
239+
std::string typePrefix;
240+
size_t typeColonPos = type.find( ":" );
241+
if( typeColonPos != std::string::npos )
242+
{
243+
typePrefix = type.substr( 0, typeColonPos ) + ":";
244+
if( typePrefix == "ai:" )
245+
{
246+
typePrefix = "arnold:";
247+
}
248+
}
249+
usdShader.SetShaderId( pxr::TfToken( typePrefix + shader->getName() ) );
250+
251+
return usdShader.ConnectableAPI();
252+
}
253+
254+
void writeShaderParameterValues( const IECoreScene::Shader *shader, pxr::UsdShadeConnectableAPI usdShader )
255+
{
256+
for( const auto &p : shader->parametersData()->readable() )
257+
{
258+
pxr::UsdShadeInput input = usdShader.CreateInput(
259+
toUSDParameterName( p.first ),
260+
IECoreUSD::DataAlgo::valueTypeName( p.second.get() )
261+
);
262+
input.Set( IECoreUSD::DataAlgo::toUSD( p.second.get() ) );
263+
}
264+
265+
const IECore::BoolData *adapterMeta = shader->blindData()->member<IECore::BoolData>( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() );
266+
if( adapterMeta && adapterMeta->readable() )
267+
{
268+
usdShader.GetPrim().SetMetadata( g_adapterLabelToken, true );
269+
}
270+
}
271+
272+
using ShaderMap = std::unordered_map<IECore::InternedString, pxr::UsdShadeConnectableAPI>;
273+
void writeShaderConnections( const IECoreScene::ShaderNetwork *shaderNetwork, const ShaderMap &usdShaders )
274+
{
275+
for( const auto &shader : shaderNetwork->shaders() )
276+
{
277+
pxr::UsdShadeConnectableAPI usdShader = usdShaders.at( shader.first );
278+
for( const auto &c : shaderNetwork->inputConnections( shader.first ) )
279+
{
280+
pxr::UsdShadeInput dest = usdShader.GetInput( pxr::TfToken( c.destination.name.string() ) );
281+
if( !dest )
282+
{
283+
dest = usdShader.CreateInput( toUSDParameterName( c.destination.name ), pxr::SdfValueTypeNames->Token );
284+
}
285+
286+
pxr::UsdShadeShader sourceUsdShader = usdShaders.at( c.source.shader );
287+
std::string sourceOutputName = c.source.name.string();
288+
if( sourceOutputName.size() == 0 )
289+
{
290+
sourceOutputName = "DEFAULT_OUTPUT";
291+
}
292+
pxr::UsdShadeOutput source = sourceUsdShader.CreateOutput( pxr::TfToken( sourceOutputName ), dest.GetTypeName() );
293+
dest.ConnectToSource( source );
294+
}
295+
}
296+
}
297+
220298
} // namespace
221299

222300
pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer )
223301
{
224-
IECoreScene::ShaderNetworkPtr shaderNetworkWithAdapters = shaderNetwork->copy();
225-
IECoreScene::ShaderNetworkAlgo::expandSplines( shaderNetworkWithAdapters.get() );
226-
IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( shaderNetworkWithAdapters.get() );
302+
IECoreScene::ConstShaderNetworkPtr adaptedNetwork = adaptShaderNetworkForWriting( shaderNetwork );
303+
shaderNetwork = adaptedNetwork.get();
227304

228-
IECoreScene::ShaderNetwork::Parameter networkOutput = shaderNetworkWithAdapters->getOutput();
305+
IECoreScene::ShaderNetwork::Parameter networkOutput = shaderNetwork->getOutput();
229306
if( networkOutput.shader.string() == "" )
230307
{
231308
// This could theoretically happen, but a shader network with no output is not useful in any way
@@ -235,36 +312,14 @@ pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene
235312
);
236313
}
237314

315+
ShaderMap usdShaders;
238316
pxr::UsdShadeOutput networkOutUsd;
239-
for( const auto &shader : shaderNetworkWithAdapters->shaders() )
317+
for( const auto &shader : shaderNetwork->shaders() )
240318
{
241-
pxr::SdfPath usdShaderPath = shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) );
242-
pxr::UsdShadeShader usdShader = pxr::UsdShadeShader::Define( shaderContainer.GetStage(), usdShaderPath );
243-
if( !usdShader )
244-
{
245-
throw IECore::Exception( "Could not create shader at: " + shaderContainer.GetPath().GetString() + " / " + shader.first.string() );
246-
}
247-
std::string type = shader.second->getType();
248-
std::string typePrefix;
249-
size_t typeColonPos = type.find( ":" );
250-
if( typeColonPos != std::string::npos )
251-
{
252-
typePrefix = type.substr( 0, typeColonPos ) + ":";
253-
if( typePrefix == "ai:" )
254-
{
255-
typePrefix = "arnold:";
256-
}
257-
}
258-
usdShader.SetShaderId( pxr::TfToken( typePrefix + shader.second->getName() ) );
259-
260-
for( const auto &p : shader.second->parametersData()->readable() )
261-
{
262-
pxr::UsdShadeInput input = usdShader.CreateInput(
263-
toUSDParameterName( p.first ),
264-
DataAlgo::valueTypeName( p.second.get() )
265-
);
266-
input.Set( DataAlgo::toUSD( p.second.get() ) );
267-
}
319+
const pxr::SdfPath usdShaderPath = shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) );
320+
pxr::UsdShadeConnectableAPI usdShader = createShaderPrim( shader.second.get(), shaderContainer.GetStage(), usdShaderPath );
321+
writeShaderParameterValues( shader.second.get(), usdShader );
322+
usdShaders[shader.first] = usdShader;
268323

269324
if( networkOutput.shader == shader.first )
270325
{
@@ -278,36 +333,9 @@ pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene
278333
// Currently, we don't really track output types in Gaffer.
279334
networkOutUsd = usdShader.CreateOutput( outName, pxr::SdfValueTypeNames->Token );
280335
}
281-
282-
const IECore::BoolData* adapterMeta = shader.second->blindData()->member<IECore::BoolData>( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() );
283-
if( adapterMeta && adapterMeta->readable() )
284-
{
285-
usdShader.GetPrim().SetMetadata( g_adapterLabelToken, true );
286-
}
287336
}
288337

289-
for( const auto &shader : shaderNetworkWithAdapters->shaders() )
290-
{
291-
pxr::UsdShadeShader usdShader = pxr::UsdShadeShader::Get( shaderContainer.GetStage(), shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) ) );
292-
for( const auto &c : shaderNetworkWithAdapters->inputConnections( shader.first ) )
293-
{
294-
pxr::UsdShadeInput dest = usdShader.GetInput( pxr::TfToken( c.destination.name.string() ) );
295-
if( ! dest.GetPrim().IsValid() )
296-
{
297-
dest = usdShader.CreateInput( toUSDParameterName( c.destination.name ), pxr::SdfValueTypeNames->Token );
298-
}
299-
300-
pxr::UsdShadeShader sourceUsdShader = pxr::UsdShadeShader::Get( shaderContainer.GetStage(), shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( c.source.shader.string() ) ) ) );
301-
std::string sourceOutputName = c.source.name.string();
302-
if( sourceOutputName.size() == 0 )
303-
{
304-
sourceOutputName = "DEFAULT_OUTPUT";
305-
}
306-
pxr::UsdShadeOutput source = sourceUsdShader.CreateOutput( pxr::TfToken( sourceOutputName ), dest.GetTypeName() );
307-
dest.ConnectToSource( source );
308-
}
309-
310-
}
338+
writeShaderConnections( shaderNetwork, usdShaders );
311339

312340
return networkOutUsd;
313341
}
@@ -357,7 +385,63 @@ IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const px
357385

358386
#if PXR_VERSION >= 2111
359387

360-
IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const pxr::UsdLuxLightAPI &light )
388+
// This is very similar to `writeShaderNetwork` but with these key differences :
389+
//
390+
// - The output shader is written as a UsdLight-derived prim rather than a UsdShadeShader.
391+
// - The other shaders are parented inside the light.
392+
// - We don't need to create a UsdShadeOutput to return.
393+
void IECoreUSD::ShaderAlgo::writeLight( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim prim )
394+
{
395+
IECoreScene::ConstShaderNetworkPtr adaptedNetwork = adaptShaderNetworkForWriting( shaderNetwork );
396+
shaderNetwork = adaptedNetwork.get();
397+
398+
// Verify that the light shader corresponds to a valid USD light type.
399+
400+
const IECoreScene::Shader *outputShader = shaderNetwork->outputShader();
401+
if( !outputShader )
402+
{
403+
IECore::msg( IECore::Msg::Warning, "ShaderAlgo::writeLight", "No output shader" );
404+
return;
405+
}
406+
407+
pxr::TfType type = pxr::UsdSchemaRegistry::GetInstance().GetTypeFromName( pxr::TfToken( outputShader->getName() ) );
408+
if(
409+
!type.IsA<pxr::UsdLuxBoundableLightBase>() &&
410+
!type.IsA<pxr::UsdLuxNonboundableLightBase>()
411+
)
412+
{
413+
IECore::msg( IECore::Msg::Warning, "ShaderAlgo::writeLight", boost::format( "Shader `%1%` is not a valid UsdLux light type" ) % outputShader->getName() );
414+
return;
415+
}
416+
417+
// Write the light itself onto the prim we've been given.
418+
419+
ShaderMap usdShaders;
420+
prim.SetTypeName( pxr::TfToken( outputShader->getName() ) );
421+
writeShaderParameterValues( outputShader, pxr::UsdShadeConnectableAPI( prim ) );
422+
usdShaders[shaderNetwork->getOutput().shader] = pxr::UsdShadeConnectableAPI( prim );
423+
424+
// Then write any other shaders as child prims so they are
425+
// encapsulated within the light.
426+
427+
for( const auto &shader : shaderNetwork->shaders() )
428+
{
429+
if( shader.second == outputShader )
430+
{
431+
continue;
432+
}
433+
const pxr::SdfPath usdShaderPath = prim.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) );
434+
pxr::UsdShadeConnectableAPI usdShader = createShaderPrim( shader.second.get(), prim.GetStage(), usdShaderPath );
435+
writeShaderParameterValues( shader.second.get(), usdShader );
436+
usdShaders[shader.first] = usdShader;
437+
}
438+
439+
// Finally, connect everything up.
440+
441+
writeShaderConnections( shaderNetwork, usdShaders );
442+
}
443+
444+
IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readLight( const pxr::UsdLuxLightAPI &light )
361445
{
362446
IECoreScene::ShaderNetworkPtr result = new IECoreScene::ShaderNetwork();
363447
IECoreScene::ShaderNetwork::Parameter lightHandle = readShaderNetworkWalk( light.GetPath().GetParentPath(), pxr::UsdShadeConnectableAPI( light ), *result );

contrib/IECoreUSD/src/IECoreUSD/USDScene.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1087,7 +1087,7 @@ ConstObjectPtr USDScene::readAttribute( const SceneInterface::Name &name, double
10871087
#if PXR_VERSION >= 2111
10881088
else if( name == g_lightAttributeName )
10891089
{
1090-
return ShaderAlgo::readShaderNetwork( pxr::UsdLuxLightAPI( m_location->prim ) );
1090+
return ShaderAlgo::readLight( pxr::UsdLuxLightAPI( m_location->prim ) );
10911091
}
10921092
#endif
10931093
else if( name == g_kindAttributeName )
@@ -1187,8 +1187,15 @@ void USDScene::writeAttribute( const SceneInterface::Name &name, const Object *a
11871187
}
11881188
else if( const IECoreScene::ShaderNetwork *shaderNetwork = runTimeCast<const ShaderNetwork>( attribute ) )
11891189
{
1190-
const auto &[output, purpose] = materialOutputAndPurpose( name.string() );
1191-
m_materials[purpose][output] = shaderNetwork;
1190+
if( name == g_lightAttributeName )
1191+
{
1192+
ShaderAlgo::writeLight( shaderNetwork, m_location->prim );
1193+
}
1194+
else
1195+
{
1196+
const auto &[output, purpose] = materialOutputAndPurpose( name.string() );
1197+
m_materials[purpose][output] = shaderNetwork;
1198+
}
11921199
}
11931200
else if( name.string() == "gaffer:globals" )
11941201
{

contrib/IECoreUSD/test/IECoreUSD/USDSceneTest.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3367,6 +3367,83 @@ def testMultipleLights( self ) :
33673367
self.assertIsInstance( attribute, IECoreScene.ShaderNetwork )
33683368
self.assertEqual( attribute.outputShader().parameters["exposure"], IECore.FloatData( exposure ) )
33693369

3370+
def testWriteLight( self ) :
3371+
3372+
fileName = os.path.join( self.temporaryDirectory(), "pointInstancePrimvars.usda" )
3373+
scene = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write )
3374+
3375+
light = scene.createChild( "light" )
3376+
light.writeAttribute(
3377+
"light",
3378+
IECoreScene.ShaderNetwork(
3379+
shaders = {
3380+
"output" : IECoreScene.Shader(
3381+
"SphereLight", "light",
3382+
{
3383+
"exposure" : 3.0,
3384+
"radius" : 1.5,
3385+
"color" : imath.Color3f( 1, 0.5, 0.25 ),
3386+
}
3387+
)
3388+
},
3389+
output = "output",
3390+
),
3391+
0
3392+
)
3393+
3394+
del light, scene
3395+
3396+
stage = pxr.Usd.Stage.Open( fileName )
3397+
light = pxr.UsdLux.SphereLight( stage.GetPrimAtPath( "/light" ) )
3398+
self.assertTrue( light )
3399+
3400+
self.assertEqual( light.GetExposureAttr().Get(), 3.0 )
3401+
self.assertEqual( light.GetRadiusAttr().Get(), 1.5 )
3402+
self.assertEqual( light.GetColorAttr().Get(), pxr.Gf.Vec3f( 1.0, 0.5, 0.25 ) )
3403+
self.assertEqual( light.GetPrim().GetChildren(), [] )
3404+
3405+
def testWriteLightWithInputNetwork( self ) :
3406+
3407+
# Write to USD
3408+
3409+
fileName = os.path.join( self.temporaryDirectory(), "pointInstancePrimvars.usda" )
3410+
scene = IECoreScene.SceneInterface.create( fileName, IECore.IndexedIO.OpenMode.Write )
3411+
3412+
lightNetwork = IECoreScene.ShaderNetwork(
3413+
shaders = {
3414+
"output" : IECoreScene.Shader( "RectLight", "light" ),
3415+
"texture" : IECoreScene.Shader(
3416+
"UsdUVTexture", "shader",
3417+
{
3418+
"file" : "test.exr",
3419+
}
3420+
),
3421+
},
3422+
connections = [
3423+
( ( "texture", "rgb" ), ( "output", "color" ) )
3424+
],
3425+
output = "output",
3426+
)
3427+
3428+
light = scene.createChild( "light" )
3429+
light.writeAttribute( "light", lightNetwork, 0 )
3430+
3431+
del light, scene
3432+
3433+
# Verify via USD API
3434+
3435+
stage = pxr.Usd.Stage.Open( fileName )
3436+
self.assertTrue( pxr.UsdLux.RectLight( stage.GetPrimAtPath( "/light" ) ) )
3437+
3438+
light = pxr.UsdLux.LightAPI( stage.GetPrimAtPath( "/light" ) )
3439+
source, sourceName, sourceType = light.GetInput( "color" ).GetConnectedSource()
3440+
self.assertEqual( source.GetPrim().GetName(), "texture" )
3441+
self.assertEqual( source.GetPrim().GetParent(), light.GetPrim() )
3442+
self.assertEqual( sourceName, "rgb" )
3443+
self.assertEqual( sourceType, pxr.UsdShade.AttributeType.Output )
3444+
3445+
self.assertEqual( light.GetPrim().GetChildren(), [ source.GetPrim() ] )
3446+
33703447
def testPointInstancerPrimvars( self ) :
33713448

33723449
# Use the USD API to author a point instancer with primvars on it.

0 commit comments

Comments
 (0)