Optix Scene Graph as application main Scene Graph

Hi everybody,

if i need to make something like 3D editor, can and should Optix Scene Graph be used as the application main Scene Graph?


It’s generally ill advised to use the OptiX acceleration structure hierarchy (let’s call it “render graph”) as general purpose scene graph.

There exist restrictions on the maximum depth of that hierarchy and the runtime performance will degrade the deeper the render graph hierarchy gets.
See these OptiX Properties: https://github.com/NVIDIA/OptiX_Apps/blob/master/apps/rtigo3/src/Device.cpp#L467

The recommendation would be to have a host side scene graph which can hold arbitrary data stored at its nodes completely under your control and then implement a “crossbar” module which does a translation of that to the OptiX render graph.

Depending on the dynamic manipulation requirements and maxima in number of primitives and number of instances in your scene, that could result in different render graph layouts.

1.) For example, if everything is static and the number of primitives is smaller than 2^29 then you could put all of them into a single Geometry Acceleration Structure (GAS). Means every geometric primitive is pre-transformed to world coordinates.
(That single GAS hierarchy is an OptiX feature. DXR and the Vulkan RT extension do not support this.)

2.) If there are instances in the scene reusing GAS multiple times and the maximum number of instances is below 2^28 then you could flatten the scene graph to a render graph which has exactly one top-level Instance Acceleration Structure (IAS) and each instance, which is effectively an affine transformation to world space, is referencing one GAS.
Means there are exactly two AS from top-object to leaf geometry and BVH traversal through that is fully supported in hardware by the RT cores in RTX boards.

This would be my recommendation for most cases and is what I’m doing inside my OptiX 7 examples.
I’m using a very simple host side scene graph which consists of only three nodes, Group, Instance, Geometry (currently triangles) which can be arbitrarily deep and is flattened to an IAS->GAS render graph hierarchy once.
I’ve not implemented updates to the scene in that example.
(Internally I have a version which supports multiple different geometric primitive types. It’s very simple to add them.)

Check these out:

Conversion from host side scene graph to OptiX two-level render graph is a simple depth-first recursion:

All host side scene graph nodes are shared pointers so memory handling is super easy.
Nodes are derived from a base class allowing Instance nodes to have all other node types as children which allows any hierarchy. Geometry can be shared among instances while the instance defines the material.
The Shader Binding Table is setup with one entry per instance which allows to have different materials for shared geometry data. (There are other ways to achieve that, not shown in that example.)

Since that example handles import of triangle mesh data via ASSIMP and the scene description file allows multiple models, that already demonstrates how to build a simple host side scene graph for a static viewer application. Look into the Cornell box example scene.

(There is a small flaw in the scene description parser which doesn’t support local material names with spaces, which can happen in some ASSIMP loaded scenes. I enhanced that parser to allow strings in quotation marks which will appear in future examples when ready.)

3.) If there is a huge number of instances exceeding the OptiX limits if you flattened the render graph and things are grouped logically and manipulated in groups, you would need to use a multi-level AS hierarchy in the render graph. Means there are more than two AS from root to leaf, that is, more than one IAS.
(This is also an OptiX feature, again DXR and Vulkan RT do not support this.)
The BVH traversal through that deeper hierarchy works slightly differently and will be a little slower.
Don’t overdo it. You should try to avoid a naive translation from scene graph to render graph nodes. Try to make the render graph as efficient as possible.

The main application performance issue when dealing with host side scene graphs is the efficient handling of changes. Many scene graph implementations are simply traversing the hierarchy completely and update associated render graph nodes accordingly. That is really inefficient for huge graphs and a reason why many applications are CPU bound.

We’ve done a lot of research in our Developer Technology Professional Visualization team around that and came up with some solutions using quick validation schemes and observer patterns to update the minimum amount of changes in the render graph. That is some advanced C++ code though.
Find more information and presentations about that here when being used with OpenGL and GLSL: https://github.com/nvpro-pipeline/pipeline

Will your advices be the same for Optix 6.5? Sorry, forget to specify this moment.

Yes, of course.
It’s even worse because there exists no way to batch updates to transform node matrices before OptiX 7.

Applications normally store much more information in their scene graph nodes than would be required for rendering them. For example think about CAD applications which store manufacturers, part numbers, costs, and properties for all sorts of physical materials etc. The renderer just needs the data for the geometry and appearance.

This recommendation applies to any underlying graphics API.

If you mean the limits and grouping of primitives and instances, yes, that applies to OptiX 6.5.0 as well, because those are limitations of the underlying core driver module.