Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LightToCamera : Support USD spot lights #5447

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ Improvements
- Viewer :
- Added visualisation of light filters for USD lights.
- Added support for USD lights and shaders in the floating inspector panel.
- Improved support for looking through USD spot lights.
- ShaderTweaks/ShaderQuery : Added presets for USD light and surface shaders.
- Test app :
- The `-category` argument now accepts a space-separated list of categories, optionally containing wildcards.
- Added `-excludedCategories` and `-showCategories` arguments.
- Added information about performance test timings to the output stream.
- LightToCamera : Added support for USD spot lights.

Fixes
-----
Expand Down
69 changes: 69 additions & 0 deletions python/GafferUSDTest/USDLightTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,5 +125,74 @@ def testShapingAndShadowAPIs( self ) :
light["parameters"]["shaping:focus"]["value"]
)

def testLightToCamera( self ) :

g = GafferScene.Group()

inputs = {}
for shader, name in [
("SphereLight", "sphere1"),
("SphereLight", "sphere2"),
("DistantLight", "distant1"),
("DomeLight", "env1"),
] :
light = GafferUSD.USDLight()
light.loadShader( shader )
light["name"].setValue( name )
inputs[name] = light

inputs["camera"] = GafferScene.Camera()
for i in inputs.values() :
g["in"][-1].setInput( i["out"] )

f = GafferScene.PathFilter()
f["paths"].setValue( IECore.StringVectorData( [ "/group/sphere1", "/group/env1", "/group/distant1" ] ) )

lc = GafferScene.LightToCamera()
lc["in"].setInput( g["out"] )
lc["filter"].setInput( f["out"] )

# Test light with no corresponding camera outputs default camera
self.assertEqual(
lc["out"].object( "/group/sphere1" ).parameters(),
IECore.CompoundData( {
"projection" : IECore.StringData( "perspective" ),
} )
)

def assertSpotCamera( camera ) :

calculatedFieldOfView = camera.calculateFieldOfView()
self.assertAlmostEqual( calculatedFieldOfView[0], 65, 4 )
self.assertAlmostEqual( calculatedFieldOfView[1], 65, 4 )
self.assertEqual( camera.getClippingPlanes(), imath.V2f( 0.01, 100000 ) )
self.assertEqual( camera.getProjection(), "perspective" )
self.assertEqual( camera.getFilmFit(), IECoreScene.Camera.FilmFit.Fit )
self.assertEqual( camera.hasResolution(), False )

# Test adding a spot cone to sphere light will output a persp cam
inputs["sphere1"]["parameters"]["shaping:cone:angle"]["enabled"].setValue( True )
inputs["sphere1"]["parameters"]["shaping:cone:angle"]["value"].setValue( 32.5 )
assertSpotCamera( lc["out"].object( "/group/sphere1" ) )

# Test distant light outputs ortho cam
distantCam = lc["out"].object( "/group/distant1" )
self.assertEqual( distantCam.getAperture(), imath.V2f( 2, 2 ) )
self.assertEqual( distantCam.getClippingPlanes(), imath.V2f( -100000, 100000 ) )
self.assertEqual( distantCam.getProjection(), "orthographic" )
self.assertEqual( distantCam.getFilmFit(), IECoreScene.Camera.FilmFit.Fit )
self.assertEqual( distantCam.hasResolution(), False )

# Test adding a cone to a distant light will output a persp cam
inputs["distant1"]["parameters"]["shaping:cone:angle"]["enabled"].setValue( True )
inputs["distant1"]["parameters"]["shaping:cone:angle"]["value"].setValue( 32.5 )
assertSpotCamera( lc["out"].object( "/group/distant1" ) )

self.assertEqual( lc["out"].set( "__lights" ).value.paths(), [ "/group/sphere2" ] )
self.assertEqual(
set( lc["out"].set( "__cameras" ).value.paths() ),
set( [ "/group/camera", "/group/sphere1", "/group/distant1", "/group/env1" ] )
)

if __name__ == "__main__":
unittest.main()
14 changes: 14 additions & 0 deletions src/GafferScene/LightToCamera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,12 @@ const char *lightType( const IECore::CompoundData *shaderParameters, const std::
return "";
}

if( boost::starts_with( metadataTarget, "light:" ) && shaderParameters->member<FloatData>( "shaping:cone:angle" ) )
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is definitely a step forward for USD lights authored within Gaffer, where we only expect shaping:cone:angle to have been enabled if it is wanted and has a sensible value. But I've seen at least one other DCC that seemed to always author shaping:cone:angle, using a value of 90 (or was it 180?) as a sentinel value meaning "ignore me". I don't know what the official line on that is as UsdLux is badly under specified, but I think it'd be worth us ignoring values >=90 here regardless, since the perspective projection is broken in those cases. I'm going to merge anyway as we wanted to include this in today's release, but I think it might be worth doing a quick follow up?

{
// USD lights with a cone angle defined are treated as spot lights
return "spot";
}

return type->readable().c_str();
}

Expand All @@ -151,6 +157,14 @@ float lightOuterAngle( const IECore::CompoundData *shaderParameters, const std::
}
}

if( ConstStringDataPtr coneAngleType = Metadata::value<StringData>( metadataTarget, "coneAngleType" ) )
{
if( coneAngleType->readable() == "half" )
{
coneAngle *= 2.0f;
}
}

const std::string *penumbraType = nullptr;
ConstStringDataPtr penumbraTypeData = Metadata::value<StringData>( metadataTarget, "penumbraType" );
if( penumbraTypeData )
Expand Down
Loading