These examples show how to use Hydra Viewport Toolbox (HVT) tasks, helpers, and workflows in application code. HVT both simplifies OpenUSD Hydra viewport integration and extends OpenUSD with additional features (e.g. WBOIT, outlines) and capabilities (e.g. geometry helpers, scene-index filters).
Each How-to lives in howTos/ as a runnable source file and is also compiled into the test binary — but its primary purpose is demonstration, not exhaustive validation.
For traditional unit and regression testing, see tests/. That directory contains many tests per feature (params equality, construction, edge cases, image baselines). There is typically one How-to per feature.
ℹ️ The list of examples is ordered from very basic first steps to much more advanced concepts. You do not need to follow the order if you look for a specific topic.
- How to compile
Hydra Viewport Toolboxin my environment - How to create an Hgi instance
- How to create one frame pass
- How to create two frame passes
- How to create a custom render task
- How to use the SSAO (Ambient Occlusion) task
- How to use the FXAA (Anti-aliasing) task
- How to include or exclude prims from a frame pass
- How to display the bounding box of a scene
- How to display the wire frame of a scene
- How to explicitly create the list of tasks
- How to use the SkyDome task
- How to use the WBOIT (transparency) task
- How to use the outline (selection highlight) tasks
- How to use the OutlineManager wrapper (recommended)
Below are the usual steps to correctly create your local clone of Hydra Viewport Toolbox.
git clone --recurse-submodules https://github.com/Autodesk/hydra-viewport-toolbox.git hvt
cd hvt
cmake --preset debug
cmake --build --preset debugNow, you have to specify that you want to build Viewport Toolbox from your local USD using the cmake option OPENUSD_INSTALL_PATH which must point to the install directory of your local compilation.
cmake -GNinja -DOPENUSD_INSTALL_PATH=../../usd_1/_install -DCMAKE_INSTALL_PREFIX=../_install ../.
ninja
ninja installHow-tos and unit tests share the same harness. To run or debug them:
./bin/hvt_testor
ctest --preset debugℹ️ For more details such as platform specific aspects, refer to the project README.
This example (refer to HowTo01_CreateHgiImplementation.cpp for the implementation details) demonstrates how to create and destroy an Hgi (i.e., Hydra Graphics Interface) implementation. In fact, the Hgi API is an abstraction of a backend (e.g., OpenGL, Metal, etc.).
The way to initialize the default pxr::Hgi implementation is:
pxr::HgiUniquePtr hgi = pxr::Hgi::CreatePlatformDefaultHgi();The way to initialize a specific pxr::Hgi implementation is:
pxr::HgiUniquePtr hgi = pxr::Hgi::CreateNamedHgi(pxr::HgiTokens->Metal);ℹ️ To have the complete list of supported backends, check here
The way to declare an pxr::HdDriver is:
pxr::HdDriver hgiDriver;
hgiDriver.name = pxr::HgiTokens->renderDriver;
hgiDriver.driver = pxr::VtValue(hgi.get());This example (refer to HowTo02_CreateOneFramePass.cpp for implementation details) demonstrates how to create one frame pass using the Storm render delegate.
There are three steps to fully create a frame pass from an arbitrary USD scene file.
Step 1: Below is the code to create a render index instance for a specific hgi implementation with Storm as the renderer.
hvt::RenderIndexProxyPtr renderIndex;
hvt::RendererDescriptor renderDesc;
renderDesc.hgiDriver = &hgiDriver;
renderDesc.rendererName = "HdStormRendererPlugin";
hvt::ViewportEngine::CreateRenderer(renderIndex, renderDesc);Step 2: Below is the code to create a scene index.
pxr::HdSceneIndexBaseRefPtr sceneIndex = hvt::ViewportEngine::CreateUSDSceneIndex(stage);
renderIndex->RenderIndex()->InsertSceneIndex(sceneIndex, pxr::SdfPath::AbsoluteRootPath());
Step 3: Below is the code to create a frame pass.
A frame pass is the class used to render (or select) a collection of prims using a set of input parameters and render state. The class internally contains a task controller to generate the list of render tasks and an engine to render them.
hvt::FramePassDescriptor passDesc;
passDesc.renderIndex = renderIndex->RenderIndex();
passDesc.uid = pxr::SdfPath("/sceneFramePass");
hvt::FramePassPtr sceneFramePass = hvt::ViewportEngine::CreateFramePass(passDesc);Once a frame pass is created its parameters can be set on it to control how the rendering and selection are performed. Default values are fine unless the rendering step needs values different from the defaults. For example, the view and projection matrices must change after loading a new scene or moving the camera.
auto& params = sceneFramePass->params();
params.renderBufferSize = pxr::GfVec2i(context.width(), context.height());
params.viewInfo.framing = hvt::ViewParams::GetDefaultFraming(context.width(), context.height());
params.viewInfo.viewMatrix = stage.viewMatrix();
params.viewInfo.projectionMatrix = stage.projectionMatrix();
<...>ℹ️ The complete list of parameters are defined here
The code must now create all the render tasks and render them by doing:
sceneFramePass->Render();In fact the call is decomposed in two steps:
const pxr::HdTaskSharedPtrVector renderTasks = sceneFramePass->GetRenderTasks();to get the list of render tasks.sceneFramePass->Render(renderTasks);to sequentially execute the render tasks.
This example (refer to HowTo03_CreateTwoFramePasses.cpp for implementation details) demonstrates how to create two frame passes.
In order to create two frame passes, the idea is to repeat the HowTo02 example to create the two frame passes, use the HowTo01b example to load the selected manipulator asset and finally, to add the needed glue between the two frame passes to make it work.
When creating the render index a dev can select a specific render to use instead of the default one i.e., Storm by updating the following line:
renderDesc.rendererName = "HdStormRendererPlugin";When having multiple frame passes, dev can select a renderer for the scene pass and another one for the secondary pass(es). For example it could be HdEmbreeRendererPlugin for the scene pass and the default HdStormRendererPlugin for all the secondary frame passes.
As explained in the previous example, the code below updates the first render frame but it delays the display to let the second frame pass also update the render buffers.
{
auto& params = framePass1->params();
<...>
// Do not display right now, wait for the second frame pass.
params.enablePresentation = false;
framePass1->Render();
}The code below gets the color and depth render buffers from the first frame pass so the second frame uses them. That's for now the default composition between frame passes.
// Get the input AOV's from the first frame pass and use them in all overlays so the
// overlay's draw into the same color and depth buffers.
auto& pass = mainFramePass.sceneFramePass;
hvt::RenderBufferBindings inputAOVs = pass->GetRenderBufferBindingsForNextPass(
{ pxr::HdAovTokens->color, pxr::HdAovTokens->depth });Finally, the code below updates the second frame pass, renders without clearing the background as the render buffers already contain the final render of the first frame pass, and finally displays the result (i.e. by default params.enablePresentation is true).
{
auto& params = framePass2->params();
<...>
// Do not clear the background as it contains the previous frame pass result.
params.clearBackground = false;
params.backgroundColor = pxr::GfVec4f(0.0f, 0.0f, 0.0f, 0.0f);
// Get the list of tasks to render but use the render buffers from the main frame pass.
const pxr::HdTaskSharedPtrVector renderTasks = framePass2->GetRenderTasks(inputAOVs);
framePass2->Render(renderTasks);
}This example (refer to HowTo04_CreateACustomRenderTask.cpp for implementation details) demonstrates how to create and render a custom render task like the blur task from the Viewport Toolbox resources.
Take the HowTo02 example to create the frame pass and then, add the custom render task.
As the blur task is a rendering task there are mainly two parts to create i.e., the glslfx containing the shader program and the task itself i.e., pxr::HdxTask.
As example:
There are only few virtual methods to implement.
/// The blur parameters.
struct VIEWPORT_Export BlurTaskParams
{
/// The amount of Blur to apply.
float blurAmount = 0.5f;
/// The name of the aov to blur.
pxr::TfToken aovName = pxr::HdAovTokens->color;
};
/// The blur render task.
class BlurTask : public pxr::HdxTask
{
public:
BlurTask(pxr::HdSceneDelegate* delegate, pxr::SdfPath const& id);
~BlurTask() override;
void Prepare(pxr::HdTaskContext* ctx, pxr::HdRenderIndex* renderIndex) override { ... }
void Execute(pxr::HdTaskContext* ctx) override { ... }
protected:
void _Sync(pxr::HdSceneDelegate* delegate, pxr::HdTaskContext* ctx,
pxr::HdDirtyBits* dirtyBits) override { ...}
BlurTaskParams _params;
};- The
_Sync()synchronizes the blur parameters with the render task parameters. - The
Prepare()performs any preprocessing works (before the execution). - The
Execute()applies the blur image processing i.e. it executes the shader program in that case.
In most cases, the _Sync() implementation can be quite generic:
void BlurTask::_Sync(HdSceneDelegate* delegate, HdTaskContext* ctx, HdDirtyBits* dirtyBits)
{
if ((*dirtyBits) & HdChangeTracker::DirtyParams)
{
BlurTaskParams params;
if (_GetTaskParams(delegate, ¶ms))
{
_params = params;
}
}
*dirtyBits = HdChangeTracker::Clean;
}The code below adds the blur task to the frame pass.
// Adds the 'blur' custom task to the frame pass.
{
// Defines the blur task update function.
// In that case, there is no need for any update.
auto fnCommit =
[&](hvt::TaskManager::GetTaskValueFn const& /*fnGetValue*/,
hvt::TaskManager::SetTaskValueFn const& /*fnSetValue*/) {};
// Adds the blur task i.e., 'blurTask' before the color correction one.
const pxr::SdfPath colorCorrectionTask =
main->GetTaskManager()->GetTaskPath("colorCorrectionTask");
const pxr::SdfPath blurPath =
main->GetTaskManager()->AddTask<hvt::BlurTask>(
hvt::BlurTask::GetToken(), fnCommit, colorCorrectionTask,
hvt::TaskManager::InsertionOrder::insertBefore);
// Sets the default value.
hvt::BlurTaskParams blurParams;
blurParams.blurAmount = 8.0f;
main->GetTaskManager()->SetTaskValue(blurPath, pxr::HdTokens->params, pxr::VtValue(blurParams));
}When the blur value is dynamically changeable, the update method (i.e., fnCommit) can be:
// Lets define the application parameters.
struct AppParams
{
float blur { 8.0f };
//...
} app;
// Defines the update function callback.
auto fnCommit =
[&](hvt::TaskManager::GetTaskValueFn const& fnGetValue,
hvt::TaskManager::SetTaskValueFn const& fnSetValue) {
const pxr::VtValue value = fnGetValue(pxr::HdTokens->params);
hvt::BlurTaskParams params = value.Get<hvt::BlurTaskParams>();
params.blurAmount = app.blur;
fnSetValue(pxr::HdTokens->params, pxr::VtValue(params));
};If the app needs to enable/disable the task, the code is:
const pxr::SdfPath blurTask = main->GetTaskManager()->GetTaskPath("blurTask");
main->GetTaskManager()->EnableTask(blurTask, myApp->enableBlur);The last step is to render the frame pass using the updated list of render passes.
// Renders the updated list of render tasks.
sceneFramePass->Render();ℹ️ The last step encapsulates the pxr::HdEngine usage.
It uses the frame pass engine instance to execute the list of render tasks.
float FramePass::Render(const pxr::HdTaskSharedPtrVector& renderTasks)
{
// Render using a list of render tasks.
_engine->Execute(
_taskController->GetRenderIndex(), const_cast<HdTaskSharedPtrVector*>(&renderTasks));
return 1.0f;
}Internally, the engine updates the render task parameters, prepares them and renders them. To better understand the code could be simplified like:
Engine::Execute(HdRenderIndex *index, HdTaskSharedPtrVector *tasks)
{
index->SyncAll(tasks, &_taskContext);
for (size_t taskNum = 0; taskNum < numTasks; ++taskNum)
{
const HdTaskSharedPtr &task = (*tasks)[taskNum];
task->Prepare(&_taskContext, index);
}
for (size_t taskNum = 0; taskNum < numTasks; ++taskNum)
{
const HdTaskSharedPtr &task = (*tasks)[taskNum];
task->Execute(&_taskContext);
}
}This example (refer to HowTo05_UseSSAORenderTask.cpp for implementation details) demonstrates how to use the SSAO (i.e., Ambient Occlusion render task) task from the Hydra Viewport Toolbox resources.
ℹ️ Specifically, Hydra Viewport Toolbox task implements "screen-space ambient occlusion" (SSAO), which computes ambient occlusion in real-time using image-space information.
Follow the HowTo02 example to create a frame pass and then add the SSAO task as a custom render task.
To visualize the ambient occlusion (ao) buffer only, the variable in isShowOnlyEnabled needs to be true. It will update a flag in the shader and output the occlusion result only.
// Adds ssao custom task to the frame pass
{
// Defines ssao task update function
auto fnCommit = [&](hvt::TaskManager::GetTaskValueFn const& fnGetValue,
hvt::TaskManager::SetTaskValueFn const& fnSetValue) {
const pxr::VtValue value = fnGetValue(pxr::HdTokens->params);
hvt::SSAOTaskParams params = value.Get<hvt::SSAOTaskParams>();
params.ao = app.ao;
auto renderParams = sceneFramePass->params().renderParams;
params.view.cameraID = renderParams.camera;
params.view.framing = renderParams.framing;
params.view.overrideWindowPolicy = renderParams.overrideWindowPolicy;
params.ao.isEnabled = true;
params.ao.isShowOnlyEnabled = true;
params.ao.amount = 2.0f;
params.ao.sampleRadius = 10.0f;
fnSetValue(pxr::HdTokens->params, pxr::VtValue(params));
};
// Adds the ssao task i.e., 'ssaoTask' before the color correction one.
const pxr::SdfPath colorCorrectionTask = sceneFramePass->GetTaskManager()->GetTaskPath(
pxr::HdxPrimitiveTokens->colorCorrectionTask);
const pxr::SdfPath ssaoPath =
sceneFramePass->GetTaskManager()->AddTask<hvt::SSAOTask>(
hvt::SSAOTask::GetToken(), fnCommit, colorCorrectionTask,
hvt::TaskManager::InsertionOrder::insertBefore);
// Sets the default value.
hvt::SSAOTaskParams ssaoParams;
ssaoParams.ao = app.ao;
auto renderParams = sceneFramePass->params().renderParams;
ssaoParams.view.cameraID = renderParams.camera;
ssaoParams.view.framing = renderParams.framing;
ssaoParams.view.overrideWindowPolicy = renderParams.overrideWindowPolicy;
ssaoParams.ao.isEnabled = true;
ssaoParams.ao.isShowOnlyEnabled = true;
ssaoParams.ao.amount = 2.0f;
ssaoParams.ao.sampleRadius = 10.0f;
sceneFramePass->GetTaskManager()->SetTaskValue(
ssaoPath, pxr::HdTokens->params, pxr::VtValue(ssaoParams));The example in HowTo06_UseFXAARenderTask.cpp demonstrates how to use the FXAA render task of the Hydra Viewport Toolbox. It implements the "Fast Approximate Anti-aliasing" algorithm, which applies an image wide blur filter to smooth out aliasing effects.
Follow the HowTo02 example to create a frame pass and then add the FXAA task as a custom render task.
// Adds the 'FXAA' custom task to the frame pass.
{
// Defines the anti-aliasing task update function.
auto fnCommit =
[&](hvt::TaskManager::GetTaskValueFn const& fnGetValue,
hvt::TaskManager::SetTaskValueFn const& fnSetValue) {
const pxr::VtValue value = fnGetValue(pxr::HdTokens->params);
hvt::FXAATaskParams params = value.Get<hvt::FXAATaskParams>();
params.resolution = myApp->fxaaResolution;
fnSetValue(pxr::HdTokens->params, pxr::VtValue(params));
};
// Adds the anti-aliasing task i.e., 'fxaaTask'.
const pxr::SdfPath colorCorrectionTask =
sceneFramePass->GetTaskManager()->GetTaskPath("colorCorrectionTask");
// Note: Inserts the FXAA render task into the task list after color correction.
const pxr::SdfPath fxaaPath =
sceneFramePass->GetTaskManager()->AddTask<hvt::FXAATask>(
pxr::TfToken("fxaaTask"), fnCommit, colorCorrectionTask,
hvt::TaskManager::InsertionOrder::insertAfter);
// Sets the default value.
hvt::FXAATaskParams fxaaParams;
fxaaParams.resolution = myApp->fxaaResolution;
sceneFramePass->GetTaskManager()->SetTaskValue(
fxaaPath, pxr::HdTokens->params, pxr::VtValue(fxaaParams));
}Note that this task can also be used as a custom render task by other task controllers such as the one in the USD library, as explained in the HowTo04 example.
This example (refer to HowTo07_UseIncludeExclude.cpp for implementation details) demonstrates how to include or exclude prims from a frame pass.
It takes the HowTo02 example to create the frame pass and then, demonstrates the inclusion or exclusion of geometry prims.
The code below creates the default collection used by the frame passes (which by default includes all the prims) and only excludes the geometry prims from the grid.
pxr::HdRprimCollection collection { hvt::FramePassParams().collection };
collection.SetExcludePaths({ gridPath });The code below creates the default collection used by the frame passes but it only includes geometry prims from the grid.
pxr::HdRprimCollection collection { hvt::FramePassParams().collection };
collection.SetRootPath(gridPath);The code below gets the parameters from the frame pass and set the new collection.
auto& params = sceneFramePass->params();
params.collection = collection;This example (refer to HowTo08_UseBoundingBoxSceneIndex.cpp for implementation details) demonstrates how to use a scene index filter like the 'Bounding box' one.
It takes the HowTo02 example to create a single frame pass and it uses the scene index to add the filter.
From the USD documentation:
It's fairly straightforward to implement a scene index by referring to an input scene index for data access, but then selectively overriding the input scene data. This can be thought of as the lazy programming version of running a transformation on the scene at load time. We call this pattern a scene index filter, and provide a base class for this behavior in HdSingleInputFilteringSceneIndexBase.
Note: When the code accesses to a stage it can create the associated scene index using the method ViewportEngine::CreateUSDSceneIndex().
The code to insert scene indice filters is then:
pxr::HdSceneIndexBaseRefPtr sceneIndex = hvt::ViewportEngine::CreateUSDSceneIndex(stage.stage());
sceneIndex = hvt::BoundingBoxSceneIndex::New(sceneIndex);
renderIndex->RenderIndex()->InsertSceneIndex(sceneIndex, pxr::SdfPath::AbsoluteRootPath());The bounding box can also be driven through USD's DrawMode schema (OpenUSD UsdImaging, not an
HVT helper). That path inserts an override callback when creating the scene index — see
UsdImagingDrawModeSceneIndex in pxr/usdImaging/usdImaging/drawModeSceneIndex.h. The HVT-native
filter approach above (hvt::BoundingBoxSceneIndex) is what
HowTo08 demonstrates.
This example (refer to HowTo09_UseWireFrameSceneIndex.cpp for implementation details) demonstrates how to display a wire frame of an arbitrary scene.
The example provides two different ways to display a wire frame. The last case combines different render delegates to use the capabilities of each render delegate.
The implementation demonstrates how to display the wire frame of an arbitrary scene using the collection representation. The only difference with the HowTo02 (which creates a single frame pass) is to change the default collection representation of the geometry.
auto& params = sceneFramePass->params();
<...>
// Changes the geometry representation.
params.collection = pxr::HdRprimCollection(
pxr::HdTokens->geometry, pxr::HdReprSelector(pxr::HdReprTokens->wire));
sceneFramePass->Render();Note: The HD_REPR_TOKENS define lists all the existing representations supported by USD/Hydra.
Refer to pxr/imaging/hd/tokens.h for the details.
The implementation demonstrates how to display the wire frame of an arbitrary scene using scene index filters.
Unfortunately, that's specific to the Storm render delegate.
// Step 2 - Adds the 'wireframe' scene index.
sceneIndex = hvt::ViewportEngine::CreateUSDSceneIndex(stage.stage());
sceneIndex = hvt::DisplayStyleOverrideSceneIndex::New(sceneIndex);
sceneIndex = hvt::WireFrameSceneIndex::New(sceneIndex);
renderIndex->RenderIndex()->InsertSceneIndex(sceneIndex, pxr::SdfPath::AbsoluteRootPath());This example (refer to HowTo10_CustomListOfTasks.cpp for implementation details) demonstrates how to create one frame pass using the Storm render delegate.
The code contains three ways to manually create the list of tasks.
hvt::FramePassDescriptor frameDesc;
frameDesc.renderIndex = renderIndex->RenderIndex();
frameDesc.uid = pxr::SdfPath("/sceneFramePass");
// Manually creates the default list of tasks.
framePass = std::make_unique<hvt::FramePass>(frameDesc.uid.GetText());
framePass->Initialize(frameDesc);
framePass->CreateDefaultTasks();hvt::FramePassDescriptor frameDesc;
frameDesc.renderIndex = renderIndex->RenderIndex();
frameDesc.uid = pxr::SdfPath("/sceneFramePass");
// Manually creates the default list of tasks.
framePass = std::make_unique<hvt::FramePass>(frameDesc.uid.GetText());
framePass->Initialize(frameDesc);
// Note: When the render delegate is Storm, the creation is as below.
const hvt::FramePassParams& params = framePass->params();
const auto getLayerSettings =
[&framePass]() -> hvt::BasicLayerParams const* {
return &framePass->params();
};
hvt::TaskCreation::CreateDefaultTasks(framePass->GetTaskManager(),
framePass->GetRenderBufferAccessor(), framePass->GetLightingAccessor(),
framePass->GetSelectionSettingsAccessor(), getLayerSettings, false);hvt::FramePassDescriptor frameDesc;
frameDesc.renderIndex = renderIndex->RenderIndex();
frameDesc.uid = pxr::SdfPath("/sceneFramePass");
// Manually creates the minimal list of tasks.
framePass = std::make_unique<hvt::FramePass>(frameDesc.uid.GetText());
framePass->Initialize(frameDesc);
const auto getLayerSettings =
[&framePass]() -> hvt::BasicLayerParams const* {
return &framePass->params();
};
hvt::TaskCreation::CreateMinimalTasks(framePass->GetTaskManager(),
framePass->GetRenderBufferAccessor(), framePass->GetLightingAccessor(),
getLayerSettings);This example (refer to HowTo11_UseSkyDomeTask.cpp for implementation details) demonstrates how to add a Sky Dome render task before all existing default render tasks. An important detail that can easily be overlooked is the necessity to have a valid dome light in the frame pass viewInfo parameter:
pxr::GlfSimpleLight domeLight;
domeLight.SetID(pxr::SdfPath("DomeLight"));
domeLight.SetIsDomeLight(true);
params.viewInfo.lights.push_back(domeLight);
Other than this detail, the Sky Dome task is typically inserted before other render tasks, as it should be the farthest geometry and should be rendered first.
This example (refer to HowTo19_UseWBOITRenderTask.cpp for implementation details) demonstrates weighted blended order-independent transparency (WBOIT) as an alternative to Hydra's default linked-list OIT.
To enable WBOIT, set TaskCreationOptions::useWbOit = true in the FramePassDescriptor before creating the frame pass. The default task list will then use WbOitRenderTask and WbOitResolveTask instead of the standard OIT tasks.
ℹ️ See docs/wboit.md for design details and limitations.
This example (refer to HowTo20_UseOutlineTasks.cpp for implementation details) demonstrates GPU selection outlines using ID-buffer edge detection (OutlinePrimIdsTask, OutlineMaskTask, OutlineOverlayTask).
ℹ️ See docs/outline.md for the full pipeline, shader details, and platform limitations (some tests skip Apple/Metal).
This example (refer to HowTo21_UseOutlineManager.cpp for
implementation details) demonstrates the recommended high-level path for selection outlines
via hvt::Outline::OutlineManager. It wraps the five internal outline tasks, AOV bindings, and
commit logic so callers only call Install, SetStyle, and SetInputs.
Prefer HowTo21 over HowTo20 for new integrations unless you need direct control over individual outline tasks.
ℹ️ See docs/outline.md § OutlineManager for API details and test/tests/testOutlineManager.cpp for unit coverage.