HDK
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
Custom Viewport Rendering

Introduction

Houdini offers several rendering hooks that allow you to augment or replace rendering in the Houdini viewport. There are two main types of hooks - primitive hooks, and scene hooks.

Primitive hooks execute rendering code based on specific primitives found within a displayed GU_Detail. They can be used to implement viewport rendering code for custom-designed primitives, add decorations and guides to existing primitives, or replace the Houdini rendering of a native primitive.

Scene hooks are not tied to any given geometry, and instead operate at the viewport level. They can do things such as render additional material to the beauty pass, draw in the overlay plane, or do a framebuffer effect such as a depth of field.

Primitive Render Hooks

A primitive render hook is responsible for creating a GR_Primitive-based object for a certain primitive type (or types) in a GU_Detail. A primitive render hook is represented by GUI_PrimitiveHook. This class is only ever instantiated once.

GUI_PrimitiveHook's main function is to inspect GT or GEO primitives passed to it and decide whether to create a custom GR_Primitive for this primitive, or pass it along to other hooks or the native Houdini rendering method. A GUI_PrimitiveHook can:

  • create a single GR_Primitive object for a single GEO or GT primitive
  • create a single GR_Primitive object for multiple GEO or GT primitives
  • create a GR_Primitive for a GEO or GT primitive, but allow processing to continue for that primitive. This allows it to augment the rendering of that primitive by drawing guides, text, or decorations, without having to draw that primitive by itself.
  • be installed for multiple times for different GT or GEO primitive types, so that families of primitives can be rendered by the same GR_Primitive code.

Installing a Primitive Render Hook

A GUI_PrimitiveHook must be registered before it can be used. This is done in one of two ways, depending on whether it is being installed on a native or custom primitive.

A hook for a native primitive can be installed via the newRenderHook() function. The type ID for a native GT or GEO primitive is known from the header (GA_PrimitiveTypes.h or GT_PrimitiveTypes.h), so it can be installed directly:

{
const int prim_hook_priority = 0;
table->registerGEOHook( new GR_PrimAwesomeHook,
prim_hook_priority );
}

A custom primitive's type ID is not known until it is registered, however, so any render hooks for this primitive must be registered when the GEO primitive is registered:

GA_PrimitiveDefinition *GU_PrimTetra::theDef = NULL;
void
GU_PrimAwesome::registerMyself(GA_PrimitiveFactory *factory)
{
// register GU primitive
theDef = factory->registerDefinition("HDK_Awesome",
gu_newPrimAwesome,
theDef->setLabel("hdk_awesome");
registerIntrinsics(*theDef);
// register GR primitive
const int hook_priority = 0;
DM_RenderTable::getTable()->registerGEOHook(new GR_PrimAwesomeHook,
theDef->getId(),
hook_priority,
}

A GUI_PrimitiveHook must have a name for message reporting, and also a render mask which describes the renderers it supports. A render hook can support all GL renderers, a subset, or a single GL renderer. These are set using the bitfield GUI_RenderMask.

GUI_PrimitiveHook(const char *hook_name,

When installing GUI_PrimitiveHook, a priority and an optional GUI_PrimitiveHookFlags parameter can be specified. The priority defines the order that hooks defined for the same primitive type will see the primitive, with precedence given to larger numbers. Any signed integer except MIN_INT32 can be used as a priority value. The priority allows multiple primitive hooks to analyze a primitive, which is useful in cases where a hook may look for the existence of an attribute to render the primitive differently (eg. 'guide_hair' is 1).

The GUI_PrimitiveHookFlags parameter is a bitfield of flags:

  • : GUI_HOOK_FLAG_AUGMENT_PRIM: Hook to this primitive but also allow the native Houdini rendering of that primitive to draw.
  • : GUI_HOOK_FLAG_PRIM_FILTER: Use a GT primitive filter to generate a new GT primitive from the one supplied by Houdini.

Installing a Scene Render Hook

Scene render hooks replace or augment render passes, and are not tied to any specific geometry. They allow you to render text in the viewport, apply effects to the beauty pass, draw your own background, or entirely replace the Houdini viewport.

To install a scene hook, derive a class from DM_SceneHook and DM_SceneRenderHook, then install it to a render pass.

{
const int hook_priority = 0; // normal priority
table->registerSceneHook( new GR_TextOverlayHook("text_overlay",
hook_priority),
DM_HOOK_FOREGFROUND,
}

The render passes are, in order of rendering:

  • DM_HOOK_BACKGROUND: The first element drawn to the viewport, without any camera transform applied (matrices set up to 1:1 pixel correspondence).
  • DM_HOOK_PRE_RENDER: Elements drawn before the beauty pass, with a camera transform. The background image is drawn in the Houdini native pre-render pass.
  • DM_HOOK_BEAUTY: All opaque user-generated geometry is drawn in this pass.
  • DM_HOOK_BEAUTY_TRANSPARENT: Any transparent user-generated geometry is drawn in this pass, after the opaque beauty pass.
  • DM_HOOK_UNLIT: Unlit (non-lit, non-shadowed) geometry is drawn in this pass. Guide and template geometries are a good example of what is drawn in this pass.
  • DM_HOOK_XRAY: Objects with the X-ray flag set (such as bones) are drawn in this pass. Objects should only draw as wireframe in this pass.
  • DM_HOOK_POST_RENDER: Elements drawn after all the scene geometry, but still with the camera transform, are drawn. The origin, field guide and safe area are drawn in the native Houdini post-render pass.
  • DM_HOOK_FOREGROUND: The last elements drawn in the viewport. No native Houdini elements are drawn in this pass.

A scene hook can be registered multiple times to different render passes. This can be used to bracket the native rendering, or use data from earlier passes later on. The hook is responsible for any sharing of the render hook between passes.

The pre-render to post-render passes are rendered per-stereo eye perspective, while the background and foreground passes are only rendered once. Not all passes are available in the UV viewport (no BEAUTY_TRANSPARENT, UNLIT or XRAY).

For the BEAUTY, BEAUTY_TRANSPARENT, UNLIT and XRAY passes in OBJ,SOPs, and DOPs, a list of the geometry that falls into these categories can be queried from DM_VPortAgent, which is passed to the render method. These are getOpaqueObject() (and getNumOpaqueObjects()) for the beauty pass, getTransparentObject() for the beauty transparent pass, getUnlitObject() for the unlit pass, and getXRayObject() for the xray pass. All except the xray object list are mutually exclusive.

For LOPs, only the display and current LOPs are available, which can be used to query the stage (LOP_Node::getCookedDataHandle() ) and inspect the scene as a whole.

There is a also a DM_HOOK_FULL_SCENE hook which allows all of the above to be replaced by a single hook.

For High Quality Lighting, two additional hooks are provided, with the light provided:

  • DM_HOOK_SHADOW: Shadow pass for generating a shadow map, or other lighting technique, for the light pass. Only computed when the light or user geometry changes. Only done when shadows are enabled.
  • DM_HOOK_HQ_LIGHT_PASS: Per-light lighting pass. Additive blending is enabled when this hook is called, so only the lighting contribution for this light needs to be drawn.

The RE_Light object is passed to both passes, and data can be stored on the RE_Light by using the attachUserData(), detachUserData(), and getAttachedUserData() methods (each of which takes an index, so that multiple data chunks can be attached by the same or different viewports). The @ RE_ShadowMap object for a light can also be accessed and rendered to or used in the lighting passes.

For selecting things within hooks, there are two type of picking hooks - a DM_HOOK_FRAMEBUFFER_PICK hook for single clicks and visible only, and a DM_HOOK_FRUSTUM_PICK for volume selection. This are active when doing component selection in SOPs or LOPs.

  • DM_HOOK_FRAMEBUFFER_PICK will be called when the user selects something by single clicking or area selecting with "Area Select Visible Geometry Only" enabled. The framebuffer consists of 2 ivec2 attachments. The first one should have the value of the ivec3 RE_UNIFORM_PICK_BASE_ID builtin uniform written to it, and the second, the ivec3 RE_UNIFORM_PICK_COMPONENT_ID. The base ID should not be changed by the hook, but you can alter the component ID. You don't need to use the component uniform solely, you can dynamically assign all or part of it in the shader. Note that this hook can be called multiple times with different pick components, passed in the DM_SceneHookData as pick_type. If you don't have that type of component, don't render anything. Once the pick render is done, () will be called with the pick records that were selected. This may be an empty list if nothing was selected in your hook. The pick buffer is only regneerated when the scene changes, so selectionResult() could be called many times without a corresponding hook render (though the hook render will always be called before it at least once).
  • DM_HOOK_FRUSTUM_PICK will be called for an volume selection (area selection with "Area Select Visible Geometry Only" off). Unlike the framebuffer pick, nothing is rendered to a framebuffer. You can use a geometry shader and transform feedback to fetch the pick IDs, or your can simply compute what's in the frustum on the CPU. The frustum area is sent in DM_SceneHookData via pick_area. If lasso or paint is used, the mask is sent in pick_mask (an array of uint8 the size of pick_area.w()*pick_area.h() ). No () callback is provided for this method as the hook knows what is selected during the course of processing the picks.

NOTE: For Houdini 20.0, some of the render passes are still rendering to OpenGL framebuffers as is Vulkan gradually integrated. The passes that cannot use Vulkan for rendering are:

  • DM_HOOK_PRE_RENDER
  • DM_HOOK_POST_RENDER
  • DM_HOOK_FULL_SCENE

There are several scene hook examples:

  • HDK_Sample::DM_BackgroundHook : Replaces the Houdini background with a checkered one. A good beginner example. (DM_BackgroundHook.C)
  • HDK_Sample::DM_InfoHook: Prints the current SOP at the bottom of the viewport, demonstrating how to query details in the viewport and draw text. (DM_InfoHook.C)
  • HDK_Sample::DM_SceneBoundsHook: Draws a bounding box around all objects in the scene. This demonstrates how to iterate over all displayed items in the viewprot and draw in 3D. It also shows how to restrict the hook from the UV viewport. (HDK_SceneBoundsHook.C)
  • HDK_Sample::DM_LightBloomHook: Draws a simple glow around bright objects, illustrating how to query the beauty framebuffer and use it to add an additional effect. (DM_LightBloomHook.C)
  • HDK_Sample::DM_ObjectPathHook: Draws a path with the up vectors at each frame for the current object, if animated. Demonstrates how to query objects directly and draw more complex guides in the 3D viewport. (DM_ObjectPathHook.C)
  • HDK_Sample::DM_OverdrawHook: A sample showing how to register a hook in multiple passes, sharing data between passes. This hook measures the total samples drawn in the viewport and determines the overdraw percentage, based on how many samples were rejected by depth testing. (DM_OverdrawHook.C)

Generating a GR_Primitive with a Hook

A GUI_PrimitiveHook's only reponsibility is to create a GR_Primitive from given criteria using the createPrimitive() method, or to produce a new GT_Primitive from filterPrimitive(). This method is only called if the primitive's type ID matches the one currently be processed in the GU_Detail. A hook can be a GR_Primitive creator, or a GT_Primitive filter (via GUI_HOOK_FLAG_PRIM_FILTER), but never both. This section deals with hooks that override createPrimitive() to create new GR_Primitive types.

The hook can simply handle all primitives passed to it, which are guarenteed to be of the type it was registered with, or do a more detailed inspection of the primitive's attributes or values.

virtual GR_Primitive *createPrimitive(const GT_PrimitiveHandle &gt_prim,
const GEO_Primitive *geo_prim,
const GR_RenderInfo *info,
const char *cache_name,
GR_PrimAcceptResult &processed);

Both a GT and GEO primitive are passed, though the GEO primitive will be NULL if this hook is defined for a GT primitive type. The 'info' and 'cache_name' parameters are needed by the base GR_Primitive class constructor, so they are generally passed straight though the custom GR_Primitive's constructor. The processed parameter must not be GR_NOT_PROCESSED if a new GR_Primitive is returned, and it can take the following values:

  • GR_PROCESSED: A GR_Primitive has been created, do not process further hooks or native Houdini primitives for this primitive.
  • GR_PROCESSED_NON_EXCLUSIVE: A GR_Primitive has been craeted, but continue processing hooks or Houdini primitives for this primitive.

The non-exclusive case often requires that the GUI_PrimitiveHook is installed with a higher priority than other custom hooks for the same type, especially if they return GR_PROCESSED.

Once a GR_Primitive is created, it gains some autonomy in maintaining itself. In order to provide better performance, the viewport attempts to reuse existing GR primitives whenever possible. If a GU_Detail is changed, the next update for the detail will first call acceptPrimitive() for each primitive that was previously generated. In this case, the GT or GEO type passed to acceptPrimitive() may not match the GR_Primitive's expected type:

virtual GR_PrimAcceptResult acceptPrimitive(GT_PrimitiveType t,
int geo_type,
const GT_PrimitiveHandle &ph,
const GEO_Primitive *prim);

GR_Primitives are deleted when no longer needed. This can occur if:

  • The detail is no longer being displayed
  • The primitive has been removed from the detail
  • Primitives were reordered in the detail such that a match between a primitive and its GR primitive could not be made.

Rendering Primitives

Once a GR_Primitive has been created for a primitive, or group of primitives, it will generally be updated and rendered. A GR_Primitive can be persistent over multiple GU_Detail changes, so information can be cached within the GR_Primitive itself or in the GL cache. There are two main phases of a GR_Primitive - updating and rendering.

A GR_Primitive is updated primarily through its update() method. It is responsible for creating all data required for the render methods. Textures, buffers, and other GL objects should all be created in this method.

virtual void update(RE_Render *r,
const GT_PrimitiveHandle &primh,
const GR_UpdateParms &p);

update() is called much less often than render, so it is recommended that any heavy computation is done in update() rather than the render methods. update() is called when:

The reason for the update is stated in the bitfield p.reason. Multiple reasons may be given.

In addition to update(), there is a virtual viewUpdate() method which is called if only the view frustum has changed and the primitive requested view change updates.

Rendering is performed in render(). The basic render method is:

virtual void render(RE_Render *r,
GR_RenderMode render_mode,
GR_DrawParms draw_parms);

Each primitive can be rendered in a variety of ways, not all of which need to be supported by a custom primitive. The render modes are:

  • GR_RENDER_BEAUTY: Normal quality beauty pass, direct lighting render.
  • GR_RENDER_MATERIAL: High Quality render, deferred shading render.
  • GR_RENDER_CONSTANT: Primitive rendered in a constant color, no shading.
  • GR_RENDER_WIREFRAME: Wireframe representation.
  • GR_RENDER_XRAY_LINE: Wireframe, but with hidden lines muted.
  • GR_RENDER_HIDDEN_LINE: Wireframe, but with hidden lines removed.
  • GR_RENDER_GHOST_LINE: Wireframe, with a dark constant fill of the surface.
  • GR_RENDER_MATERIAL_WIREFRAME: Wireframe when HQ Lighting is active.
  • GR_RENDER_DEPTH: Depth buffer render only, nonlinear [0..1] clip space
  • GR_RENDER_DEPTH_CUBE: Depth render, linear depth, for a cube map
  • GR_RENDER_DEPTH_LINEAR: Depth render, linear depth, for a regular shadow map
  • GR_RENDER_MATTE: Constant fill color, but with depth check against the beauty pass's depth
  • GR_RENDER_XRAY: Render an X-ray matte of the object
  • GR_RENDER_OBJECT_PICK: Object selection. A shader should not be set, or must output the value of 'uniform ivec4 glH_PickID'
  • GR_RENDER_POST_PASS: The primitive requested a post-pass. The post-pass ID should be checked against the ID in 'myInfo' (GR_RenderInfo) in case multiple post-passes were requested.

Modes when you don't intend to support should immediately return from render(). Ignoring some modes, like Hidden Line, will cause your geometry to disappear when a user selects "Hidden Line" mode, so in some cases it is better to substitute a close render mode (like Wireframe). Other modes, like Matte or Depth Linear, will cause the object not to highlight when located or participate in shadow maps.

There are also variations for some modes, usually shaded ones, passed in the GR_RenderFlags parameter:

  • GR_RENDER_FLAG_FLAT_SHADED: Do not interpolate normals over the surface.
  • GR_RENDER_FLAG_UNLIT: Render the surface without lighting.
  • GR_RENDER_FLAG_WIRE_OVER: Render the surface with wireframe guides overlaid (polygon outlines, isoparms)
  • GR_RENDER_FLAG_FADED: Ignore the Cd attribute (color), used for template rendering
  • GR_RENDER_FLAG_COLOR_OVERRIDE: The Cd attribute has been overridden
  • GR_RENDER_FLAG_ALPHA_OVERRIDE: The Alpha attribute has been overridden
  • GR_RENDER_FLAG_POINTS_ONLY: Render the primitive's points only.
  • GR_RENDER_FLAG_SHADED_CURVES: Render curves as shaded, lit strips
  • GR_RENDER_FLAG_WIRE_PRIMS_ONLY: Only render wireframe primitives, no solids.
  • GR_RENDER_FLAG_USE_SUBDIVISION: Use subdivision for the curve or surface, if supported.
  • GR_RENDER_FLAG_POINT_UV_POS: (UV Viewport only) - UV is a point attribute
  • GR_RENDER_FLAG_VERTEX_UV_POS: (UV Viewport onlu) - UV is a vertex attribute
  • GR_RENDER_FLAG_BONE_DEFORM: Use deformation for up to 4 bones
  • GR_RENDER_FLAG_BONE_DEFORM_SINGLE: Use deformation, only a single bone.
  • GR_RENDER_FLAG_HIDE_MATERIALS: Do not draw with any materials other than the default.

render() is frequently called more often than update(), such as when tumbling the view, so it is recommended that this method be as lightweight as possible.

A hook must be coded for Vulkan if Vulkan is the current renderer, and a GL4 renderer must be used if the rendering is OpenGL. For GUI_PrimitiveHook, return a mask of all the renderers the hook supports. For DM_SceneHook, return true if the renderer passed to supportsRenderer() is supported. Your hook can support both; instead of passing an RE_Render pointer to the render and update methods, an RE_RenderContext is passed instead. If Vulkan is active, its isVulkan() will return true. If your hook doesn't support Vulkan, return immediately if this is true. Similarly, if your doesn't support OpenGL, return if it is false. An RE_Render (GL) or an RV_Render (Vulkan) can be extracted from the RE_RenderContext.

There are also several ways to do OpenGL rendering within Houdini. The first method is to use OpenGL or Vulkan directly. The second uses higher-level RE or RV objects such as RE_Shader, RE_Geometry, and RE_VertexArray. These gain the benefits of the Houdini GL cache, GL extension abstractions, and simpler interfaces. The third way is through GR classes designed to render various classes of objects in the viewport (GR_PolySurfaceGL3, GR_VolumeGL3, GR_PointsGL3, GR_PolySurfaceVK). These classes know all about picking, selection, instancing, and special draw modes, like hidden line, ghosting and shadowmap rendering.

All three methods have their advantages, and generally trade off flexibilty for quicker development when moving from raw GL/Vulkan, to RE/RV, to GR.

The following pages cover these rendering topics in more detail.

Creating a GT_Primitive with a Filter Hook

In addition to the normal GUI_RenderHook, there is a primitive filter hook. It is registered with the GUI_HOOK_FLAG_PRIM_FILTER flag (and only that flag):

{
const int prim_hook_priority = 0;
table->registerGEOHook( new GR_PrimFilterHook,
prim_hook_priority,
}

When this type of hook is active, it creates a new GT_Primitive for the input GT or GEO primitive:

GT_PrimitiveHandle filterPrimitive(const GT_PrimitiveHandle &gt_prm,
const GEO_Primitive *geo_prm,
const GR_RenderInfo *info,
GR_PrimAcceptResult &processed);

The new GT primitive may be of the same type as the input primitive, or it may be a completely different type, such as converting a polygon mesh into a point cloud.

A filter hook should always set 'processed' to GR_PROCESSED if it always or might affect its primitive, such as if it is conditionally applied using display option. This ensures that the refinement process knows that it may be conditionally applied. Returning a NULL GT_PrimitiveHandle from a filter will cause it to be ignored even if GR_PROCESSED is returned.

Registering a primitive display option for a filter handle requires the 'refine_requred' flag to be set:

GR_UserOption *opt = table->installGeometryOption("visNasCd", "Visualize N");
if(opt)
opt->setRefineRequired(true);

This will trigger a geometry refine when the option is toggled so that the filterPrimitive() method is called again. Only filter hooks require this; primitive creation hooks should not set it.

The filter effect can then be toggled on or off (as taken from the GUI_PolygonNormalShade HDK sample):

GUI_PolygonNormalShade::filterPrimitive(const GT_PrimitiveHandle &gt_prm,
const GEO_Primitive *geo_prm,
const GR_RenderInfo *info,
GR_PrimAcceptResult &processed)
{
if(!info->getDisplayOption()->getUserOptionState("visNasCd"))
{
// we're interested in this prim, but are not doing anything with it
// at this time. Mark it as processed but don't return a new prim.
processed = GR_PROCESSED;
return ph;
}
// using gt_prim, create a new GT_Primitive and assign to ph //
return ph;
}

The only restriction for a filter hook is that it can only return a single GT_Primitive. It cannot return a collection of GT_Primitives.

See Also
HDK_ViewportGR_ClassesGT.