Skip to content

Commit

Permalink
3Delight : Support Gaffer spline parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
ericmehl committed Aug 23, 2023
1 parent d40f352 commit 59091e5
Show file tree
Hide file tree
Showing 5 changed files with 405 additions and 5 deletions.
4 changes: 3 additions & 1 deletion Changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ Features

- LightTool : Added manipulator for quad lights.
- Collect : Added a utility node for collecting the values of arbitrary inputs across a range of contexts.
- 3Delight : Added support for VDB volume objects.
- 3Delight
- Added support for VDB volume objects.
- Added support for spline parameters in shaders.

Improvements
------------
Expand Down
2 changes: 1 addition & 1 deletion SConstruct
Original file line number Diff line number Diff line change
Expand Up @@ -1267,7 +1267,7 @@ libraries = {
"LIBPATH" : [ "$DELIGHT_ROOT/lib" ],
## \todo Remove GafferScene. We need it at present to get access to `IECoreScenePreview::Renderer`,
# but IECoreDelight must never depend on Gaffer code; logically it is in the layer below Gaffer.
"LIBS" : [ "GafferScene", "3delight", "IECoreImage$CORTEX_LIB_SUFFIX", "IECoreScene$CORTEX_LIB_SUFFIX", "IECoreVDB" ],
"LIBS" : [ "GafferScene", "3delight", "IECoreImage$CORTEX_LIB_SUFFIX", "IECoreScene$CORTEX_LIB_SUFFIX", "IECoreVDB", "OpenImageIO_Util$OIIO_LIB_SUFFIX", "oslquery$OSL_LIB_SUFFIX" ],
},
"pythonEnvAppends" : {
"LIBS" : [ "IECoreScene$CORTEX_LIB_SUFFIX", "IECoreDelight" ],
Expand Down
142 changes: 142 additions & 0 deletions python/IECoreDelightTest/RendererTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
import time
import unittest
import pathlib
import os
import subprocess

import imath

Expand Down Expand Up @@ -664,6 +666,134 @@ def testCustomGridName( self ) :
self.__assertInNSI( '"vdbfilename" "string" 1 "' + vdb.fileName() + '"', nsi )
self.__assertInNSI( '"densitygrid" "string" 1 "customGrid"', nsi )

def testSplineParameters( self ) :

# Converting from OSL parameters to Gaffer spline parameters is
# tested in GafferOSLTest.OSLShaderTest

r = GafferScene.Private.IECoreScenePreview.Renderer.create(
"3Delight",
GafferScene.Private.IECoreScenePreview.Renderer.RenderType.SceneDescription,
str( self.temporaryDirectory() / "test.nsi" ),
)

os.environ["OSL_SHADER_PATHS"] += os.pathsep + ( pathlib.Path( __file__ ).parent / "shaders" ).as_posix()

s = self.__compileShader( pathlib.Path( __file__ ).parent / "shaders" / "delightSplineParameters.osl" )

network = IECoreScene.ShaderNetwork(
shaders = {
"splineHandle" : IECoreScene.Shader(
s,
"osl:shader",
{
"floatSpline" : IECore.Splineff(
IECore.CubicBasisf.linear(),
[
( 0, 0.25 ),
( 0, 0.25 ),
( 1, 0.75 ),
( 1, 0.75 ),
]
),
"colorSpline" : IECore.SplinefColor3f(
IECore.CubicBasisf.bSpline(),
[
( 0, imath.Color3f( 0.25 ) ),
( 0, imath.Color3f( 0.25 ) ),
( 0, imath.Color3f( 0.25 ) ),
( 1, imath.Color3f( 0.75 ) ),
( 1, imath.Color3f( 0.75 ) ),
( 1, imath.Color3f( 0.75 ) ),
]
),
"dualInterpolationSpline" : IECore.Splineff(
IECore.CubicBasisf.linear(),
[
( 0, 0.25 ),
( 0, 0.25 ),
( 1, 0.75 ),
( 1, 0.75 ),
]
),
"trimmedFloatSpline" : IECore.Splineff(
IECore.CubicBasisf.catmullRom(),
[
( 0, 0.25 ),
( 0, 0.25 ),
( 1, 0.75 ),
( 1, 0.75 ),
]
),
"mayaSpline" : IECore.Splineff(
IECore.CubicBasisf.linear(),
[
( 0, 0.25 ),
( 0, 0.25 ),
( 1, 0.75 ),
( 1, 0.75 ),
]
),
"inconsistentNameSpline": IECore.Splineff(
IECore.CubicBasisf.bSpline(),
[
( 0, 0.25 ),
( 0, 0.25 ),
( 0, 0.25 ),
( 1, 0.75 ),
( 1, 0.75 ),
( 1, 0.75 ),
]
),
}
),
},
output = "splineHandle"
)

o = r.object(
"testPlane",
IECoreScene.MeshPrimitive.createPlane( imath.Box2f( imath.V2f( -1 ), imath.V2f( 1 ) ) ),
r.attributes( IECore.CompoundObject( { "osl:surface" : network } ) )
)
del o

r.render()
del r

nsi = self.__parse( self.temporaryDirectory() / "test.nsi" )

# 3Delight gives defaults for linear splines as though they have a multiplicity of 2,
# whereas we expect a multiplicity of 1. These tests mirror 3Delight's convention.
# This results in two extra segments, with the first and last of zero length.
# In practice this seems to give correct results, so we leave it as-is rather than
# adding more edge-case handling.
self.__assertInNSI( '"floatSpline_Knots" "float[6]" 1 [ 0 0 0 1 1 1 ]', nsi )
self.__assertInNSI( '"floatSpline_Floats" "float[6]" 1 [ 0.25 0.25 0.25 0.75 0.75 0.75 ]', nsi )
self.__assertInNSI( '"floatSpline_Interp" "int[6]" 1 [ 1 1 1 1 1 1 ]', nsi )

self.__assertInNSI( '"colorSpline_Knots" "float[6]" 1 [ 0 0 0 1 1 1 ]', nsi )
self.__assertInNSI( '"colorSpline_Colors" "color[6]" 1 [ 0.25 0.25 0.25 0.25 0.25 0.25 0.25 0.25 0.25 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 0.75 ]', nsi )
self.__assertInNSI( '"colorSpline_Interp" "int[6]" 1 [ 3 3 3 3 3 3 ]', nsi )

self.__assertInNSI( '"dualInterpolationSpline_Knots" "float[6]" 1 [ 0 0 0 1 1 1 ]', nsi )
self.__assertInNSI( '"dualInterpolationSpline_Floats" "float[6]" 1 [ 0.25 0.25 0.25 0.75 0.75 0.75 ]', nsi )
self.__assertInNSI( '"dualInterpolationSpline_Interp" "int[6]" 1 [ 1 1 1 1 1 1 ]', nsi )

# Monotone cubic splines, tested here consistently with how 3Delight presents defaults
# have a multiplicity of 1, resulting in a single segment.
self.__assertInNSI( '"trimmedFloatSpline_Knots" "float[4]" 1 [ 0 0 1 1 ]', nsi )
self.__assertInNSI( '"trimmedFloatSpline_Floats" "float[4]" 1 [ 0.25 0.25 0.75 0.75 ]', nsi )
self.__assertInNSI( '"trimmedFloatSpline_Interp" "int[4]" 1 [ 3 3 3 3 ]', nsi )

self.__assertInNSI( '"mayaSpline_Knots" "float[6]" 1 [ 0 0 0 1 1 1 ]', nsi )
self.__assertInNSI( '"mayaSpline_Floats" "float[6]" 1 [ 0.25 0.25 0.25 0.75 0.75 0.75 ]', nsi )
self.__assertInNSI( '"mayaSpline_Interp" "int[6]" 1 [ 1 1 1 1 1 1 ]', nsi )

self.__assertInNSI( '"inconsistentNameSpline_chaos" "float[6]" 1 [ 0 0 0 1 1 1 ]', nsi )
self.__assertInNSI( '"inconsistentNameSpline_moreChaos" "float[6]" 1 [ 0.25 0.25 0.25 0.75 0.75 0.75 ]', nsi )
self.__assertInNSI( '"inconsistentNameSpline_ahhh" "int[6]" 1 [ 3 3 3 3 3 3 ]', nsi )

# Helper methods used to check that NSI files we write contain what we
# expect. This is a very poor substitute to being able to directly query
# an NSI scene. We could try to write a proper parser that builds a node
Expand Down Expand Up @@ -711,5 +841,17 @@ def __assertNotInNSI( self, s, nsi ) :
# Continue search at next position
pos += 1

def __compileShader( self, sourceFileName ) :

outputFileName = self.temporaryDirectory() / pathlib.Path( sourceFileName ).with_suffix( ".oso" ).name

subprocess.check_call(
[ "oslc", "-q" ] +
[ "-I" + p for p in os.environ.get( "OSL_SHADER_PATHS", "" ).split( os.pathsep ) ] +
[ "-o", str( outputFileName ), str( sourceFileName ) ]
)

return outputFileName.with_suffix("").as_posix()

if __name__ == "__main__":
unittest.main()
72 changes: 72 additions & 0 deletions python/IECoreDelightTest/shaders/delightSplineParameters.osl
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2023, 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 delightSplineParameters
(

float floatSpline_Knots[] = { 0, 0, 1, 1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],
float floatSpline_Floats[] = { 0, 0, 1, 1 } [[ string widget = "floatRamp" ]],
int floatSpline_Interp[] = { 1, 1, 1, 1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],

float colorSpline_Knots[] = { 0, 0, 0, 1, 1, 1 } [[ string widget = "null", string related_to_widget = "colorRamp" ]],
color colorSpline_Colors[] = { 0, 0, 0, 1, 1, 1 } [[ string widget = "colorRamp" ]],
int colorSpline_Interp[] = { 3, 3, 3, 3, 3, 3 } [[ string widget = "null", string related_to_widget = "colorRamp" ]],

float dualInterpolationSpline_Knots[] = { 0, 0, 1, 1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],
float dualInterpolationSpline_Floats[] = { 0, 0, 1, 1 } [[ string widget = "floatRamp" ]],
string dualInterpolationSpline_Interpolation = "linear" [[ string widget = "null", string related_to_widget = "floatRamp" ]],
int dualInterpolationSpline_Interp[] = { -1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],

float trimmedFloatSpline_Floats[] = { 0, 1 } [[ string widget = "floatRamp" ]],
float trimmedFloatSpline_Knots[] = { 0, 1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],
int trimmedFloatSpline_Interp[] = { 3, 3 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],

float mayaSpline_Knots[] = { 0, 0, 1, 1 } [[ string widget = "null", string related_to_widget = "maya_floatRamp" ]],
float mayaSpline_Floats[] = { 0, 0, 1, 1 } [[ string widget = "maya_floatRamp" ]],
int mayaSpline_Interp[] = { 1, 1, 1, 1 } [[ string widget = "null", string related_to_widget = "maya_floatRamp" ]],

float inconsistentNameSpline_chaos[] = { 0, 0, 0, 1, 1, 1 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],
float inconsistentNameSpline_moreChaos[] = { 0, 0, 0, 1, 1, 1 } [[ string widget = "floatRamp" ]],
int inconsistentNameSpline_ahhh[] = { 2, 2, 2, 2 } [[ string widget = "null", string related_to_widget = "floatRamp" ]],

output closure color out = 0
)
{
float t = splineinverse( "bspline", v, colorSpline_Knots );
color c = spline( "bspline", t, colorSpline_Colors );
out = c * emission() + debug( "R", "type", "float", "value", color( c[0] ) );
Ci = out;
}
Loading

0 comments on commit 59091e5

Please sign in to comment.