HDK
|
USD provides the Hydra Framework (Hd) that enables communication between multiple scene graphs and multiple renderers. The Solaris viewport in Houdini uses Hydra to communicate with render delegates and allows some additional customization. Once you get your render delegate to successfully appear in the Solaris viewport, you may want to read this section.
Houdini communicates to the delegate using two methods.
When the delegate is created, the plugin is called with the HdRenderSettingsMap
initialized with default settings when calling HdxRendererPlugin::CreateRenderDelegate()
. This can be used by the delegate to initialize state based on Houdini's current state.
Houdini also makes use of the HdRenderDelegate::SetRenderSetting()
method to pass updates to the delegate. Unlike the rest of Hydra, these events are not passed using Sync()
methods with dirty bits. So the delegate may receive a larger number of parameter updates than is strictly necessary. Delegates should be careful to only perform updates if values have actually changed.
One common oversight when writing a delegate is to implement the plugin create method that doesn't take the HdRenderSettingsMap
. When rendering with husk
, the initial render settings are sent en mass instead of one at a time.
Houdini looks for several custom dialog script files (.ds). These files are found in the
HOUDINI_PATH
under the soho/parameters
sub-directory. You should be able to see the example file HdEmbreeRendererPlugin_Viewport.ds
in that location.
The path for the files is determined by using the delegate name in the following fashion:
The viewport file is parsed to create custom UI decorations around the Solaris viewport. The example .ds file for Embree is fairly well documented. There are two special keywords the can be used to control where the UI widget appears.
uiscope
uiscope
tag's value is a string which, at the current time, can be a combination of "viewport"
and "toolbar"
tokens (that is, both tokens can exist in the string value if desired). The "viewport"
token will cause the UI to appear in the "Display Options" for the viewport. The "toolbar"
option will cause the UI to appear on the side-bar beside the viewport (with the drawing modes).uiicon
$HH/config/Icons
(including icons in the SVGIcon.cache
), or it can be a full path to an icon file.Please note that parameter names are encoded using the hou.text.encodeParm()
method of the hou
Python module. This is required if parameter names are fully qualified or have non-standard naming.
The other files are parsed to add custom UI to the standard LOP nodes for creating specific USD primitive types. Render Settings LOPs use the rendersettings
file. Render Product LOPs use the renderproduct
file. Render Var LOPs use the rendervar
file. Light LOPs (including Dome Light and other nodes for specific light subclasses) use the light
file. Camera LOPs use the camera
file. And the Render Geometry Settings LOP (for adding renderer-specific primvars to geometry primitives) uses the geometry
file.
These files are loaded by the creation script for each of these LOPs, and invokes the addRendererParmFolders
in the loputils.py
Python code (found in $HH/scripts/python*
) If this file exists, custom UI should automatically show up when new LOP noes of these types are created in a /stage
network.
If you're having difficulty having your scripts show up, you can set HOUDINI_SCRIPT_DEBUG
to 2 or greater to have Houdini generate output in the console. For light UI, you may have to put debug statements in the loputils.py
code.
Houdini sends several custom settings through to the render delegate. These can safely be ignored, but they can also be used to enhance the user experience with the delegate.
Houdini and husk both send the current render frame through as a render setting with a key of houdini:frame
. The value should be a double. This is required by some delegates to resolve texture maps or other reasons.
For the first frame, husk sends this information in the inital render settings in the delegate constructor.
Husk sends the timeCodesPerSecond()
defined on the stage as the houdini:fps
render setting (a double value). This can be used by delegates when computing velocity motion blur.
For the first frame, husk sends this information in the inital render settings in the delegate constructor.
The houdini:interactive
keyword will be passed a VtValue
holding a string (possibly a std::string
, TfToken
or UT_StringHolder
). Code to get the value could be something like:
The value of the string reflects the purpose for the render that has been triggered. The value will be one of:
normal
flipbooking
playing
scrubbing
The
user is scrubbing the playbar.editing
The
user is changing a value of a parameter in the network.tumbling
The
user is tumbling the viewporthusk:mplay
This
setting is sent when husk is sending data to mplay.While this setting can be ignored, it can also be used to give a better experience to the user. For example, when playing
or flipbooking
, you might want to let the renderer create a more complete frame. When tumbling
or scrubbing
, you might want to have a more coarse rendering to give faster feedback to the user.
This settings passes down the value of UsdGeomGetStageMetersPerUnit()
for the rendering stage.
This metric may be useful for many things, including DOF computations. Hydra specifies that lens metrics are given in 10ths of a scene unit, so, DOF computations should be consistent regardless of the meters per unit, but this option is still provided in case there are other requirements.
You can test this by adding a Configure Layer LOP to the scene and changing the Meters per Unit
value.
husk
sends down information about the USD file that's being rendered. This information may be useful to invalidate checkpoint files or for various other purposes. These settings will only be passed when the plugin is created (in the HdRenderSettingsMap
).
string usdFilename
int64 usdFileTimeStamp
ArResolver
resolves to a path on disk).It should be noted that this information will not necessarily capture all changes to the USD scene since no information about any files referenced by the main USD file is considered.
The viewport passes down an SdfPath to the rendering camera before the path is available via the HdRenderPassStateSharedPtr
. This value is passed down before any Sync calls are made, so it allows you to capture information such as shutter open/close times from the camera that the user is looking through in the view port. Note also that cameras (and other sprims) will always be Sync'ed before any rprims.
The viewport down a bool
value indicating whether rendering should be paused or resumed.
Some Houdini viewers may pass user mouse clicks through to the delegate. This is done by calling SetRenderSetting()
during the render with the token viewerMouseClick
and ether a GfVec2i
or GfRect2i
value for the mouse coordinates. When the value is a GfVec2i
, this specifies a region of interest (instead of a single point). The delegate is free to do what they want with this information. For example, the delegate my decide to focus rendering on the region around that pixel.
To clear the focus, clients may pass an invalid GfRect2i
(or a zero area rectangle). Alternately, the client may pass down a mouse location of std::numeric_limits<int32>::max()
.
When SIGUSR1
is sent to , this will cause husk to save a snapshot of the current rendered image. When husk
receives this signal, it will also send a message to the delegate by calling SetRenderSetting
with a "husk:snapshot"
message. Renderers might use this opportunity to save out checkpointing information.
Every delegate can choose to implement the GetRenderStats()
method. This should return a VtDictionary
of render stats.
Every renderer likely has its own representation of render stats. Houdini can use the stats provided by GetRenderStats()
in various ways.
For example, a renderer might provide render stats in a VtDictionary
that looks like this (using JSON):
The viewport uses some stats to display render progress or other useful information about the status of the renderer. The statsdatapaths
dictionary in the UsdRenderers.json
file can describe where this data can be found. The statsdatapaths
entry uses the JSONPath format (https://goessner.net/articles/JsonPath/) to specify the location in the VtDictionary
where the data where these entries can be found:
percentDone
- An integer/float representing the render progress (from 0 to 100).peakmemory
- The peak memory used by the renderrenderStage
- A string describing the current stage of rendering. For example "Dicing Geometry", "Compiling Shaders"totalClockTime
- The total wall-clock time taken to rendertotalUTime
- The total user time (CPU time) used by renderingtotalSTime
- The total system time (CPU time) used by renderingrenderProgressAnnotation
- An arbitrary one-line message message displayed directly under the render progress.renderStatsAnnotation
- A multi-lined message displayed above the render stats (when render stats display is turned on).In addition the viewstats
entry in the UsdRenderers.json
file can be used to add additional information to be displayed. As with the pre-defined tags, the statsdatapath
is used to specify the location of the stats to display.
Using the example VtDictionary
, the UsdRenderers.json
file might look like:
Many image formats can store arbitrary metadata.
In addition to the metadata saved by the Render Product (and RenderVars), husk uses the VtDictionary
of render stats as the source of the metadata. The metadata source is specified in the UsdRenderers.json
file's husk.metadata
and husk.stats_metadata
entries.
Render delegates can pass information back to husk about the rendering using the GetRenderStats()
method. It's often useful to save these stats in the metadata for the rendered image.
The husk.stats_metadata
key specifies a Houdini string pattern of the dictionary keys which should be converted to image metadata. This defaults to *
, meaning that if a delegate doesn't specify the pattern, all of the render stats will be automatically saved as image metadata.
For example, setting "husk.stats_metadata" : "ray_*,img_*"
would take all keys in the VtDictionary
of render stats that begin with ray_
or img_
and store them directly in the image metadata.
Sometimes, the dictionary returned by GetRenderStats()
is extremely large or may not map directly to a useful representation to be stored in image metadata. In this case, delegate authors can use the husk.metadata
field to fine tune the data that's written to image metadata.
Tip: If the delegate specifies a husk.metadata
field, it may be prudent to set `husk.stats_metadata := "".
Like the viewport render stats, the husk.metadata
specifies JSONPath format path specifications for the data source. However, husk
provides extra information, storing the delegate's render stats in the render_stats
entry of the dictionary:
In the above example, husk
is rendering 3 frames, starting at frame 1023 (so frame 1023, 1024 and 1025). The frame
item represents the Usd frame while the frame_index
represents the offset in the sequence of frames being rendered. The delegate's render stats are stored as a dictionary under the render_stats
keyword.
The husk.metadata
dictionary uses variable expansion of JSONPath entries to determine the value of the metadata. For example:
"${render_stats.camera_near_far[0]}"
- would expand to "0.001"
"Frame: $frame ($frame_index of $frame_count)"
would expand to "Frame: 1023 (1 of 3)"
If the JSONPath doesn't specify a leaf node, the object specified by the JSONPath will be expanded as its JSON representation. For example
"${render_stats.world_to_camera}"
would expand to [0.866,-0.203,-0.457,0,0,0.914,-0.407,0,-0.5,-0.35,-0.79,0,0,-42,155,1]
"${render_stats.ray_counts}"
would expand to {"indirect":69367,"light_geometry":53922,"occlusion":94361,"primary":9216,"probe":0,"total":226866}
Tip: The code for this expansion can be inspected in HUSD_HuskEngine::addMetadata()
The keys for the metadata items can either be global or namespaced for a particular device. The list of keywords for each format can be found by running iconvert
, which shows that the keyword "artist"
is recognized by Houdini, TIFF and PNG formats for example. To specify a global tag for artist
, you could have:
Alternatively, you could namespace the artist for each format explicitly:
When saving multipart OpenEXR images (the default behaviour), metadata can be specified with a richer set of type information. It can be specified as <type> OpenEXR:<key>
, where type is one of:
Types which expect multiple values (e.g. vec3f
) expect the expanded string to be able to be parsed using JSON format. Since complex (non-leaf) nodes in the stats are expanded as JSON objects, this makes things simple. For example:
When saving multi-part OpenEXR files, the metadata conventions used by OpenImageIO are respected: https://openimageio.readthedocs.io/en/latest/stdmetadata.html
When the user specifies verbose output using the -V
option on husk, can use two settings in the UsdRenderers.json
file to provide a callback. The husk.verbose_callback
entry specifies a Python file which husk
calls to print out stats. The husk.verbose_interval
is used to specify how frequently the Python script is invoked.
The script is passed 3 arguments.
The first argument encodes the verbose settings from husk. This can be decoded using:
The second argument is the "mode" that the script is being invoked and will be one of:
The third argument stores the husk render stats in JSON form. This is the same JSON dictionary used for meta-data expansion, for example:
The karma_stats.py
script shipped with Houdini can be used as an example.
husk
will handle printing of ALF_PROGRESS
(and a few other verbose messages).There are scenarios where a delegate will hit a critical error and need to terminate rendering early. There's no well defined process in USD/Hydra to pass this failure state to the caller other than render stats. If a delegate hits a critical error, a stand-alone batch renderer (like husk
) should terminate with an error exit status.
At the current time, the only way a delegate can communicate this failure state to the caller is using the Render Stats. husk
looks for an integer in the stats called huskErrorStatus
. If the value of this stat is non-zero, husk will terminate the render early and exit with the given status.
A variety of configuration options specific to Houdini can be provided by creating a UsdRenderers.json
file in the HOUDINI_PATH
. This JSON file should contain a dictionary of dictionaries. The top level dictionary maps the render delegate internal name to a dictionary of settings. For example, the settings for the Storm
renderer would be:
The following settings are supported. The data type of each settings is indicated, along with the default value used if this setting isn't provided. If your render delegate doesn't have settings specified in any UsdRenderers.json
file, it uses all default values for its settings.
- @c valid : bool|string (True) @n Whether or not this render delegate should be available in Houdini. If the value for `valid` is a string, it can be set to a simple expression based on the prefix to the string value. See the `usdrenderers.py` script for more details. Currently, there are two prefixes handled: `platform:REGEX`: Match the os.sys.platform string against the given regex. `env:NAME` or `env:-NAME`: Check the environment variable for the named variable (or the non-existence of the named variable). - @c aovsupport : bool (True) @n Whether this renderer is able to generate AOV buffers. - @c drawmodesupport : bool (False) @n Whether this renderer wants to support USD draw mode settings. Generally this is only supported by OpenGL based renderers. A value of False causes the full geometry to always be rendered. - @c menulabel : string (display_name) @n Text to appear in viewport Renderers menu. - @c menupriority : int (0) @n Priority to determine ordering in Renderers menu (higher numbers first). - @c complexitymultiplier : float (1.0) @n Multiplier to use on draw complexity when this renderer is active. - @c depthstyle : string ('normalized') @n Describes the range used when returning depth information. Can be normalized (-1, 1), linear (distance to camera), or opengl (0, 1). - @c defaultpurposes : string array (['proxy']) @n Specifies the default list of USD render purposes which will be displayed by this render delegate. - @c needsdepth : bool (False) @n Whether this renderer needs the Houdini OpenGL renderer to generate depth information. - @c needsselection : bool (False) @n Whether this renderer needs the Houdini OpenGL renderer to draw the currently selected primitives in an overlay. - @c allowbackgroundupdate : bool (True) @n Whether this renderer allows updates to occur on a background thread. - @c restartrendersettings : string array ([]) @n A list of render settings that require the renderer to restart if they are changed. - @c restartcamerasettings : string array ([]) @n A list of camera settings that require the renderer to restart if they are changed. - @c viewstats : string array ([]) @n A list of render statistics that can be printed in the viewer. - @c preloadlibraries : string array ([]) @n A list of dynamically loadable libraries that Houdini should load (using dlopen on Linux/MacOS or LoadLibrary on Windows) prior to trying to load the plugin for this render delegate. By preloading some libraries, delegates may be able to avoid issues involving RPATH or RUNPATH settings in the delegate binaries when the Houdini installation directory is not known. These paths can contain environment variables which will be expanded by Houdini before trying to load each library.
Render delegates can also define custom entries, though data related to these custom entries is only to be read and understood by that render delegate. An example of this is that the Houdini GL render delegate supports two additional pieces of data:
- @c lighttypes : string array ([]) @n A list of Hydra SPrim types that represent custom light primitive types which should be recognized by Houdini GL as lights. This means they have guide geometry displayed in the viewport, and appear in the "Look Through Light" menu. They will not contribute to the lighting of the scene, since Houdini GL has no way to know how the light primitive or its parameters should be interpreted. - @c pointinstancertypes : string array ([]) @n A list of USD primitive types that should be treated like point instancers for the purpose of picking instances in the viewport. This mechanism allows custom prim adapters to create instances that are selectable in the viewport even though each instance does not have a unique scene graph location.
These two additional pieces of data are explicitly read by the Houdini GL render delegate when it is created. They will not appear in the standard renderer info object.
Solaris has the ability to author USD volume primitives that refer directly to VDB or Houdini volume primitives that are being authored in SOPs. In this situation, the volume and field primitives will appear in USD as normal, but the filePath attribute of the field primitive will begin with "op:", followed by the full path to the SOP from which the volume is being referenced.
These paths can be converted directly into a GT_Primitive
pointer using a pair of function defined in a dynamically loadable library, $HH/dso/USD_SopVol
.so. This file can be dynamically loaded (using dlopen()
or LoadLibrary()
on Windows). It exports three functions:
The returned pointers are void
in the function definition, but can both be cast to GT_Primitive
pointers. From these GT_Primitive
pointers, it is possible to directly access the in-memory representation of the volume data. The pointers will remain valid as long as the USD stage holding the volumes remains valid. Any chaneg to the stage that will invalidate the volume pointers will result in a call to the render delegate to sync or remove the volume primitive.
Note that the hydra primitive type name for Houdini Volumes is houdiniFieldAsset
. To support Houdini volumes, this token must be returned in the TfTokenVector
returned by the render delegate's GetSupportedBprimTypes()
method.
The husk
renderer is a stand-alone tool shipped with Houdini which can be used for batch rendering with any Hydra delegate, provided the delegate supports rendering to AOVs.
There are two virtual methods to create a render delegate in HdxRendererPlugin
. Husk will always invoke the method that's passed the HdRenderSettingsMap
. This is used to pass information from the USD Settings to the render delegate. When running husk -V4
, the application will print out all the settings, render products and render variables which are defined by the settings (-s
command line option). These are the settings that your delegate should receive when rendering.
In the HdRenderSettingsMap
that's passed to the delegate, one of the settings will be batchCommandLine
which contains the command line and all arguments. It will also pass huskDelegateOptions
which contains the string given by the –delegate-options
argument.
This section is to clarify exactly how husk interprets the cropWindow
, overscan
and resolution
properties in the Render Settings.
The resolution
property specifies the full image resolution. This will be picked up from the settings primitive (-s
option) and can be overriden by the user by using the –res
command line option. This resolution is passed through to the Render Delegate unchanged. However, the resolution of the AOV buffers may be different than this value.
In terms of the OpenEXR data and display window, the cropWindow
property is used by husk
to define the display windwow. The overscan
property is applied after the display window is computed. This result of this overscan expansion results in the OpenEXR equivalent data window.
When husk
allocates AOV buffers, it will use the data window resolution (rather than the image resolution). Often, these vales are identical, but may be different if there's a crop window or overscan. It's up to the delegate to capture the resolution
, cropWindow
and overscan
from the HdRenderSettingsMap and interpret thses to understand the full image representation.
Each render product has a type
associated with it. This type defaults to raster
and husk
will save these products from the AOVs that are filled out by the delegate. However, there may be product types that husk
doesn't understand (for example deep
or photon_map
or checkpoint
). Many of these products types may be specific to a single delegate.
Prior to rendering a frame, husk
will collect all the products that it doesn't know how to handle and pass these to the delegate as a render setting. Since the delegate shouldn't depend on usdRender
, the render products and variables are serialized into an HdAovSettingsMap
.
As a note, when husk
is determining the AOVs required for rendering, it will normally skip any render vars defined for non-raster products. However, husk
will look for a custom render setting bool includeAovs
. If the value of this custom render setting is true
, then the AOVs will be created by husk
. For example, in a USDA file, this might look like:
Please note that delegates should still continue to render AOVs normally so that husk can save out valid raster
products.
Each product will have the following key/values:
token
productName
: The name of the producttoken
productType
: The type of the productVtArray<HdAovSettingsMap>
orderedVars
: the list of ordered render variables. Note that the product may have additional settings passed down.Each render variable in the orderedVars
will have the key/values:
token
sourceType
- The source type (i.e. "lpe")string
sourceName
- The name (i.e. "lpe:C.*")token
dataType
- The type of data (i.e. color3f
or normal3f
)HdFormat
aovDescriptor.format - The format of the variable's HdAovDescriptorVtValue
aovdescriptor.clearValue - The clear value in the AOV descriptorbool
aovdescriptor.multiSampled - The value of the multiSampled field in the AOV descriptorHdAovSettingsMap
aovdescriptor.aovSettings - The aovSettings map from the AOV descriptor. This will hold any extra settings for the render variable (e.g. metadata)Below is code that shows how to interpret the data sent by husk:
husk
uses the Houdini image libraries to write out images, which means that if need be, you can customize an image device (see the HDK documentation for loading and saving raster images).
Metadata is passed to all image devices in the same fashion, but not all image devices will process metadata in the same way.
When saving an image, husk
will process all the attributes on the render product. It looks for settings that begin with driver:parameters:
and will pass that data down to the image as a piece of metadata. For example
will add metadata to OpenEXR files named "test_string" and "pi" with the types and values you might expect. Note that OpenEXR doesn't necessarily support all the types of data allowed in USD.
Users are free to add any metadata they want. However, some data is only known to the render delegate (for example the worldToCamera
transform matrix). This delegate specific data is provided using the husk.metadata
entry in the UsdRenderers.json
file.
There may be cases where it's useful to have a RenderVar
that outputs more than one image plane when written out to disk. For example, a single Cryptomatte layer may require four image planes (or more, depending on its settings), but it's more convenient and user-friendly to have to declare just one RenderVar
for it instead of four. This may be accomplished by returning a shared pointer to a UT_HUSDExtraAOVResource from the GetResource
method of your HdRenderBuffer subclass.
UT_HUSDExtraAOVResource
is a simple struct that should be very portable (no reliance on Houdini or USD classes). It provides the information for an affiliated extra image planes and is currently only used by husk
when writing images to disk (or mplay). These affiliated AOVs cannot be displayed in a Hydra viewport. See HUSD_RenderBuffer for further information on using UT_HUSDExtraAOVResource.
Husk will not consume any Houdini licenses when it runs. Husk will check for the existence of some kind of Houdini token, but will not actually consume the Houdini license while rendering.
Note that with Apprentice rendering, the resolution of products may be restricted.