HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
USD Hydra: Customizing for Houdini

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.

USD Hydra: Render Settings

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.

USD Hydra: Viewport Controls

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:

viewport = sprintf("%s_Viewport.ds", renderer_name);
rendersettings = sprintf("%s_Global.ds", renderer_name);
renderproduct = sprintf("%s_Product.ds", renderer_name);
rendervar = sprintf("%s_Aov.ds", renderer_name);
geometry = sprintf("%s_Geometry.ds", renderer_name);
light = sprintf("%s_Light.ds", renderer_name);
camera = sprintf("%s_Camera.ds", renderer_name);

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
    The 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
    The value for the tag is a string specifying an icon for the toolbar UI. The name of the icon can be an existing icon found in $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.

USD Hydra: Debugging Viewport Scripts

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.

USD Hydra: Custom Settings

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.

USD Hydra: houdini:interactive

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.

USD Hydra: houdini:interactive

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.

USD Hydra: houdini:interactive

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:

const char *
valueAsString(const VtValue &v)
{
return v.UncheckedGet<std::string>().c_str();
if (v.IsHolding<TfToken>())
return v.UncheckedGet<TfToken>().GetText();
return v.UncheckedGet<UT_StringHolder>().c_str();
return "";
}

The value of the string reflects the purpose for the render that has been triggered. The value will be one of:

  • normal
    This is a normal render
  • flipbooking
    The render is being performed to generate an image for a flipbook.
  • playing
    The user has hit the play-bar. This render is to render a frame while the play-bar is advancing to the next frame.
  • 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 viewport

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.

stageMetersPerUnit

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.

USD Hydra: USD File Information

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
    The filename passed on the command line
  • int64 usdFileTimeStamp
    The time stamp (modified time) of the file (if the 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.

USD Hydra: renderCameraPath

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.

USD Hydra: houdini:render_pause

The viewport down a bool value indicating whether rendering should be paused or resumed.

viewerMouseClick

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().

void
Delegate::SetRenderSetting(const TfToken &key, const VtValue &value)
{
static const TfToken viewerMouseClick("viewerMouseClick", TfToken::Immortal);
if (key == viewerMouseClick)
{
if (value.IsHolding<GfVec2i>())
{
GfVec2i mouse = value.Get<GfVec2i>();
if (mouse[0] == std::numeric_limits<int>::max())
myRenderer->clearFocus();
else
myRenderer->setFocus(mouse[0], mouse[1]);
}
else if (value.IsHolding<GfRect2i>())
{
GfRect2i region = value.Get<GfRect2i>();
if (region.IsNull())
myRenderer->clearFocus();
else
{
GfVec2i center = region.GetCenter();
myRenderer->setFocus(center[0], center[1]);
}
}
}
}

huskCheckpoint

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.

USD Hydra: Render Statistics

Every delegate can choose to implement the GetRenderStats() method. This should return a VtDictionary of render stats.

For viewport rendering, you can specify a subset of these statistics you'd like displayed. In the UsdRenderers.json entry for your delegate, you can specify the keys in the viewstats entry. For example:

"viewstats" : [
"cameraRays",
"lightGeoRays",
"indirectRays",
"occlusionRays"
]

You can inspect the statistics that Karma returns by looking in XUSD_Tokens.h and searching for HusdHdRenderStatsTokensType. Notably, both the viewport and husk will use the percentDone (0 to 100) to report render progress. totalClockTime can be used to report render time (in seconds) to the user in the viewport, and if used with percentDone, an estimation of when the render will finish. husk will use the worldToCamera and worldToScreen matrices to set metadata in EXR files.

It is also possible to display additional runtime information to the viewport (eg the status of different GPUs in the system). This can be done by writing string data to two VtDictionary keys:

  • renderProgressAnnotation :
    Displayed (right-alligned) directly under the render progress
  • renderStatsAnnotation :
    Displayed (left-alligned) above the render stats

example:

Example_HdDelegate::GetRenderStats() const
{
VtDictionary stats;
// ...
static const TfToken renderProgressAnnotation("renderProgressAnnotation");
static const TfToken renderStatsAnnotation("renderStatsAnnotation");
stats[renderProgressAnnotation] = VtValue("test");
// string data can contain multiple lines
stats[renderStatsAnnotation] = VtValue("test:\nline1\nline2\nline3");
return stats;
}

USD Hydra: Render Delegate Configuration

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:

{
"HdStormRendererPlugin" : {
"valid" : true,
"menulabel" : "Storm",
"menupriority" : 40,
"depthstyle" : "opengl",
"allowbackgroundupdate" : false,
"defaultpurposes" : [ "guide", "proxy" ],
"drawmodesupport" : true,
"aovsupport" : false
}
}

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 (True) @n
    Whether or not this render delegate should be available in Houdini.
- @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
    Whether this renderer allows updates to occur on a background thread.
- @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.

USD Hydra: Rendering Volumes from SOPs

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:

extern "C"
{
void *
SOPgetVDBVolumePrimitive(const char *filepath,
const char *name); // deprecated
void *
SOPgetVDBVolumePrimitiveWithIndex(const char *filepath,
const char *name,
int index);
void *
SOPgetHoudiniVolumePrimitive(const char *filepath,
const char *name,
int index);
}

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 bprimHoudiniFieldAsset. To support Houdini volumes, this token must be returned in the TfTokenVector returned by the render delegate's GetSupportedBprimTypes() method.

USD Hydra: Integration with husk

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.

Husk: RenderSettings

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.

Husk: Command Line Arguments

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.

Husk: Image properties

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.

// Example code to convert the resolution, cropWindow and overscan
// to the OpenEXR data and display windows.
GfVec2i resolution = renderSettings[TfToken("resolution")].Get<GfVec2i>();
GfVec4f cropWindow = renderSettings[TfToken("cropWindow")].Get<GfVec4f>();
GfVec4i overscan = renderSettings[TfToken("overscan")].Get<GfVec4i>();
// resolution, cropWindow and overscan are extracted from the
// HdRenderSettingsMap passed into the constructor (or modified by
// the SetRenderSetting() method on the render delegate.
UT_DimRect image_window(0, 0, resolution[0], resolution[1]);
UT_InclusiveRect display_window(
SYSceil(image_window.width() * cropWindow[0]),
SYSceil(image_window.width() * cropWindow[1] - 1),
SYSceil(image_window.height() * cropWindow[2]),
SYSceil(image_window.height() * cropWindow[3] - 1),
UT_InclusiveRect data_window(
display_window.x() - overscan[0],
display_window.y() - overscan[1],
display_window.x2() + overscan[2],
display_window.y2() + overscan[3]);
// AOVs will be allocated with:
// width = data_window.width()
// height = data_window.height()

Husk: Delegate Render Products

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:

...
float4 dataWindowNDC = (0, 0, 1, 1)
custom string driver:parameters:artist = ""
custom bool includeAovs = 1
bool instantaneousShutter = 0
rel orderedVars = [
</Render/Products/Vars/C>,
...

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 product
  • token productType : The type of the product
  • VtArray<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 HdAovDescriptor
  • VtValue aovdescriptor.clearValue - The clear value in the AOV descriptor
  • bool aovdescriptor.multiSampled - The value of the multiSampled field in the AOV descriptor
  • HdAovSettingsMap 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:

void
MyDelegate::SetRenderSetting(const TfToken &key, const VtValue &value)
{
static TfToken delegateRenderProducts("delegateRenderProducts", TfToken::Immortal);
static TfToken orderedVars("orderedVars", TfToken::Immortal);
static TfToken aovSettings("aovDescriptor.aovSettings", TfToken::Immortal);
if (key == delegateRenderProducts)
{
using delegateProduct = HdAovSettingsMap;
using delegateVar = HdAovSettingsMap;
using delegateProductList = VtArray<delegateProduct>;
using delegateVarList = VtArray<delegateVar>;
delegateProductList drp = value.Get<delegateProductList>();
std::cout << drp.size() << " delegate render products\n";
for (const auto &pit : drp)
{
std::cout << "Product\n";
for (const auto &pval : pit)
{
if (pval.first == orderedVars)
{
delegateVarList vars = pval.second.Get<delegateVarList>();
std::cout << " -- " << vars.size() << " render vars --\n";
for (const auto &vit : vars)
{
std::cout << " Render Var\n";
for (const auto &vval : vit)
{
if (vval.first == aovSettings)
{
std::cout << " AOV Settings\n";
HdAovSettingsMap amap = vval.second.Get<HdAovSettingsMap>();
for (const auto &aval : amap)
std::cout << " " << aval.first << " := " << aval.second << std::endl;
}
else
{
std::cout << " " << vval.first << " := " << vval.second << std::endl;
}
}
}
}
else
{
std::cout << " " << pval.first << " := " << pval.second << std::endl;
}
}
}
}
}

Husk: Image Metadata

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

custom string driver:parameters:OpenEXR:test_string = "Hello world"
custom float driver:parameters:OpenEXR:pi = 3.1415

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).

husk will also use the VtDictionary provided by the render delegate's GetRenderStats() to fill out additional metadata. All stats in the dictionary will be saved as metadata in the OpenEXR file, prefixed with info:.

There are special keys which map to specific OpenEXR metadata. husk will also look for many of the common keys used by OpenImageIO:

  • Software:
    The software used to render the image
  • ImageDescription, comment, comments:
    Stored in the comments header
  • Copyright, owner:
    Corresponds to the OpenEXR owner field
  • DateTime, capTime:
    Corresponds to the OpenEXR capTime field
  • PixelAspectRatio, pixelAspectRatio:
    The pixel aspect ratio. Note that this is normally picked up from the render settings, so it is not normally required.
  • ExposureTime, expTime:
    The OpenEXR expTime field
  • FNumber, aperture:
    The OpenEXR aperture field
  • FramesPerSecond:
    The frames per second playback rate (an int[2] or GfVec2i stored as a rational)
  • captureRate:
    The frames per second capture rate (an int[2] or GfVec2i stored as a rational)
  • smtpe:TimeCode:
    The SMPTE time code (int[2])
  • smtpe:KeyCode:
    The SMPTE key code (int[7])
  • worldToCamera, worldtocamera:
    Stored in the OpenEXR worldToCamera field.
  • worldToScreen, worldToNDC, worldtoscreen:
    The OpenEXR worldToNDC field.

This means that if the render statistics you return from your delegate contain these keys, the metadata should be saved out properly to an OpenEXR image. For example, you might have VtDictionary Delegate::GetRenderStats() const { VtDictionary stats; const auto &htokens = HusdHdRenderStatsTokens();

Set the world to camera and screen matrices stats[htokens->worldToCamera] = GfMatrix4d(getWorldToCamera()); stats[htokens->worldToScreen] = GfMatrix4d(getWorldToNDC());

Store the SMPTE time code stats["smtpe:TimeCode"] = GfVec2i(encodeTimeCode(hh, mm, ss, ff));

This will be saved out as "info:custom_tag" string metadata stats["custom_tag"] = "hello world"s;

return stats; }

Husk: Extra Channels

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: Licensing

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.