Skip to content

Commit b156c83

Browse files
authored
Merge pull request #1376 from johnhaddon/usdLuxIO
USDScene : Fix writing of UsdLux lights
2 parents 91a84de + b6fa2b7 commit b156c83

File tree

5 files changed

+277
-61
lines changed

5 files changed

+277
-61
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: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,13 @@ namespace ShaderAlgo
5757
IECOREUSD_API pxr::UsdShadeOutput writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer );
5858

5959
/// Reads a ShaderNetwork from a material output, typically obtained from `UsdShadeMaterial::GetOutput()`.
60-
IECoreScene::ShaderNetworkPtr readShaderNetwork( const pxr::UsdShadeOutput &output );
60+
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-
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: 159 additions & 56 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,87 +220,89 @@ IECoreScene::ShaderNetwork::Parameter readShaderNetworkWalk( const pxr::SdfPath
217220
}
218221
}
219222

220-
} // namespace
221-
222-
pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer )
223+
IECoreScene::ConstShaderNetworkPtr adaptShaderNetworkForWriting( const IECoreScene::ShaderNetwork *shaderNetwork )
223224
{
224-
IECoreScene::ShaderNetworkPtr shaderNetworkWithAdapters = shaderNetwork->copy();
225-
IECoreScene::ShaderNetworkAlgo::expandSplines( shaderNetworkWithAdapters.get() );
226-
IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( shaderNetworkWithAdapters.get() );
225+
IECoreScene::ShaderNetworkPtr result = shaderNetwork->copy();
226+
IECoreScene::ShaderNetworkAlgo::expandSplines( result.get() );
227+
IECoreScene::ShaderNetworkAlgo::addComponentConnectionAdapters( result.get() );
228+
return result;
229+
}
227230

228-
IECoreScene::ShaderNetwork::Parameter networkOutput = shaderNetworkWithAdapters->getOutput();
229-
if( networkOutput.shader.string() == "" )
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 )
230235
{
231-
// This could theoretically happen, but a shader network with no output is not useful in any way
232-
IECore::msg(
233-
IECore::Msg::Warning, "IECoreUSD::ShaderAlgo::writeShaderNetwork",
234-
"No output shader in network"
235-
);
236+
throw IECore::Exception( "Could not create shader at " + path.GetAsString() );
236237
}
237-
238-
pxr::UsdShadeOutput networkOutUsd;
239-
for( const auto &shader : shaderNetworkWithAdapters->shaders() )
238+
const std::string type = shader->getType();
239+
std::string typePrefix;
240+
size_t typeColonPos = type.find( ":" );
241+
if( typeColonPos != std::string::npos )
240242
{
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 )
243+
typePrefix = type.substr( 0, typeColonPos ) + ":";
244+
if( typePrefix == "ai:" )
244245
{
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-
}
246+
typePrefix = "arnold:";
257247
}
258-
usdShader.SetShaderId( pxr::TfToken( typePrefix + shader.second->getName() ) );
248+
}
249+
usdShader.SetShaderId( pxr::TfToken( typePrefix + shader->getName() ) );
250+
251+
return usdShader.ConnectableAPI();
252+
}
259253

260-
for( const auto &p : shader.second->parametersData()->readable() )
254+
void writeShaderParameterValues( const IECoreScene::Shader *shader, pxr::UsdShadeConnectableAPI usdShader )
255+
{
256+
for( const auto &p : shader->parametersData()->readable() )
257+
{
258+
const pxr::TfToken usdParameterName = toUSDParameterName( p.first );
259+
pxr::UsdShadeInput input = usdShader.GetInput( usdParameterName );
260+
if( !input )
261261
{
262-
pxr::UsdShadeInput input = usdShader.CreateInput(
262+
input = usdShader.CreateInput(
263263
toUSDParameterName( p.first ),
264-
DataAlgo::valueTypeName( p.second.get() )
264+
IECoreUSD::DataAlgo::valueTypeName( p.second.get() )
265265
);
266-
input.Set( DataAlgo::toUSD( p.second.get() ) );
267266
}
268-
269-
if( networkOutput.shader == shader.first )
267+
if( auto *s = IECore::runTimeCast<IECore::StringData>( p.second.get() ) )
270268
{
271-
pxr::TfToken outName( networkOutput.name.string() );
272-
if( outName.GetString().size() == 0 )
269+
// USD has several "stringy" types - convert if necessary.
270+
if( input.GetTypeName() == pxr::SdfValueTypeNames->Token )
273271
{
274-
outName = pxr::TfToken( "DEFAULT_OUTPUT" );
272+
input.Set( pxr::TfToken( s->readable() ) );
273+
continue;
274+
}
275+
else if( input.GetTypeName().GetType().IsA<pxr::SdfAssetPath>() )
276+
{
277+
input.Set( pxr::SdfAssetPath( s->readable() ) );
278+
continue;
275279
}
276-
277-
// \todo - we should probably be correctly tracking the output type if it is typed?
278-
// Currently, we don't really track output types in Gaffer.
279-
networkOutUsd = usdShader.CreateOutput( outName, pxr::SdfValueTypeNames->Token );
280280
}
281+
input.Set( IECoreUSD::DataAlgo::toUSD( p.second.get() ) );
282+
}
281283

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-
}
284+
const IECore::BoolData *adapterMeta = shader->blindData()->member<IECore::BoolData>( IECoreScene::ShaderNetworkAlgo::componentConnectionAdapterLabel() );
285+
if( adapterMeta && adapterMeta->readable() )
286+
{
287+
usdShader.GetPrim().SetMetadata( g_adapterLabelToken, true );
287288
}
289+
}
288290

289-
for( const auto &shader : shaderNetworkWithAdapters->shaders() )
291+
using ShaderMap = std::unordered_map<IECore::InternedString, pxr::UsdShadeConnectableAPI>;
292+
void writeShaderConnections( const IECoreScene::ShaderNetwork *shaderNetwork, const ShaderMap &usdShaders )
293+
{
294+
for( const auto &shader : shaderNetwork->shaders() )
290295
{
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 ) )
296+
pxr::UsdShadeConnectableAPI usdShader = usdShaders.at( shader.first );
297+
for( const auto &c : shaderNetwork->inputConnections( shader.first ) )
293298
{
294299
pxr::UsdShadeInput dest = usdShader.GetInput( pxr::TfToken( c.destination.name.string() ) );
295-
if( ! dest.GetPrim().IsValid() )
300+
if( !dest )
296301
{
297302
dest = usdShader.CreateInput( toUSDParameterName( c.destination.name ), pxr::SdfValueTypeNames->Token );
298303
}
299304

300-
pxr::UsdShadeShader sourceUsdShader = pxr::UsdShadeShader::Get( shaderContainer.GetStage(), shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( c.source.shader.string() ) ) ) );
305+
pxr::UsdShadeShader sourceUsdShader = usdShaders.at( c.source.shader );
301306
std::string sourceOutputName = c.source.name.string();
302307
if( sourceOutputName.size() == 0 )
303308
{
@@ -306,9 +311,51 @@ pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene
306311
pxr::UsdShadeOutput source = sourceUsdShader.CreateOutput( pxr::TfToken( sourceOutputName ), dest.GetTypeName() );
307312
dest.ConnectToSource( source );
308313
}
314+
}
315+
}
316+
317+
} // namespace
318+
319+
pxr::UsdShadeOutput IECoreUSD::ShaderAlgo::writeShaderNetwork( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim shaderContainer )
320+
{
321+
IECoreScene::ConstShaderNetworkPtr adaptedNetwork = adaptShaderNetworkForWriting( shaderNetwork );
322+
shaderNetwork = adaptedNetwork.get();
309323

324+
IECoreScene::ShaderNetwork::Parameter networkOutput = shaderNetwork->getOutput();
325+
if( networkOutput.shader.string() == "" )
326+
{
327+
// This could theoretically happen, but a shader network with no output is not useful in any way
328+
IECore::msg(
329+
IECore::Msg::Warning, "IECoreUSD::ShaderAlgo::writeShaderNetwork",
330+
"No output shader in network"
331+
);
332+
}
333+
334+
ShaderMap usdShaders;
335+
pxr::UsdShadeOutput networkOutUsd;
336+
for( const auto &shader : shaderNetwork->shaders() )
337+
{
338+
const pxr::SdfPath usdShaderPath = shaderContainer.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) );
339+
pxr::UsdShadeConnectableAPI usdShader = createShaderPrim( shader.second.get(), shaderContainer.GetStage(), usdShaderPath );
340+
writeShaderParameterValues( shader.second.get(), usdShader );
341+
usdShaders[shader.first] = usdShader;
342+
343+
if( networkOutput.shader == shader.first )
344+
{
345+
pxr::TfToken outName( networkOutput.name.string() );
346+
if( outName.GetString().size() == 0 )
347+
{
348+
outName = pxr::TfToken( "DEFAULT_OUTPUT" );
349+
}
350+
351+
// \todo - we should probably be correctly tracking the output type if it is typed?
352+
// Currently, we don't really track output types in Gaffer.
353+
networkOutUsd = usdShader.CreateOutput( outName, pxr::SdfValueTypeNames->Token );
354+
}
310355
}
311356

357+
writeShaderConnections( shaderNetwork, usdShaders );
358+
312359
return networkOutUsd;
313360
}
314361

@@ -357,7 +404,63 @@ IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const px
357404

358405
#if PXR_VERSION >= 2111
359406

360-
IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readShaderNetwork( const pxr::UsdLuxLightAPI &light )
407+
// This is very similar to `writeShaderNetwork` but with these key differences :
408+
//
409+
// - The output shader is written as a UsdLight-derived prim rather than a UsdShadeShader.
410+
// - The other shaders are parented inside the light.
411+
// - We don't need to create a UsdShadeOutput to return.
412+
void IECoreUSD::ShaderAlgo::writeLight( const IECoreScene::ShaderNetwork *shaderNetwork, pxr::UsdPrim prim )
413+
{
414+
IECoreScene::ConstShaderNetworkPtr adaptedNetwork = adaptShaderNetworkForWriting( shaderNetwork );
415+
shaderNetwork = adaptedNetwork.get();
416+
417+
// Verify that the light shader corresponds to a valid USD light type.
418+
419+
const IECoreScene::Shader *outputShader = shaderNetwork->outputShader();
420+
if( !outputShader )
421+
{
422+
IECore::msg( IECore::Msg::Warning, "ShaderAlgo::writeLight", "No output shader" );
423+
return;
424+
}
425+
426+
pxr::TfType type = pxr::UsdSchemaRegistry::GetInstance().GetTypeFromName( pxr::TfToken( outputShader->getName() ) );
427+
if(
428+
!type.IsA<pxr::UsdLuxBoundableLightBase>() &&
429+
!type.IsA<pxr::UsdLuxNonboundableLightBase>()
430+
)
431+
{
432+
IECore::msg( IECore::Msg::Warning, "ShaderAlgo::writeLight", boost::format( "Shader `%1%` is not a valid UsdLux light type" ) % outputShader->getName() );
433+
return;
434+
}
435+
436+
// Write the light itself onto the prim we've been given.
437+
438+
ShaderMap usdShaders;
439+
prim.SetTypeName( pxr::TfToken( outputShader->getName() ) );
440+
writeShaderParameterValues( outputShader, pxr::UsdShadeConnectableAPI( prim ) );
441+
usdShaders[shaderNetwork->getOutput().shader] = pxr::UsdShadeConnectableAPI( prim );
442+
443+
// Then write any other shaders as child prims so they are
444+
// encapsulated within the light.
445+
446+
for( const auto &shader : shaderNetwork->shaders() )
447+
{
448+
if( shader.second == outputShader )
449+
{
450+
continue;
451+
}
452+
const pxr::SdfPath usdShaderPath = prim.GetPath().AppendChild( pxr::TfToken( pxr::TfMakeValidIdentifier( shader.first.string() ) ) );
453+
pxr::UsdShadeConnectableAPI usdShader = createShaderPrim( shader.second.get(), prim.GetStage(), usdShaderPath );
454+
writeShaderParameterValues( shader.second.get(), usdShader );
455+
usdShaders[shader.first] = usdShader;
456+
}
457+
458+
// Finally, connect everything up.
459+
460+
writeShaderConnections( shaderNetwork, usdShaders );
461+
}
462+
463+
IECoreScene::ShaderNetworkPtr IECoreUSD::ShaderAlgo::readLight( const pxr::UsdLuxLightAPI &light )
361464
{
362465
IECoreScene::ShaderNetworkPtr result = new IECoreScene::ShaderNetwork();
363466
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
{

0 commit comments

Comments
 (0)