Skip to content

Add support for USD coordinate systems (coordSys) for camera projection - #2720

Open
cpichard wants to merge 21 commits into
Autodesk:masterfrom
cpichard:issue_32
Open

Add support for USD coordinate systems (coordSys) for camera projection#2720
cpichard wants to merge 21 commits into
Autodesk:masterfrom
cpichard:issue_32

Conversation

@cpichard

@cpichard cpichard commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Changes proposed in this pull request

Add support for USD coordinate systems (UsdShadeCoordSysAPI / Hydra coordSys) in the render delegate, so MaterialX/OSL shaders can transform into a named coordinate system — typically a camera projection (map_proj, map_proj:ndc, map_proj:raster, map_proj:screen as authored by Houdini/Solaris).

Arnold's OSL render services resolve a named space by looking up a camera node whose name matches the part before the suffix (<name>.camera, <name>.NDC, <name>.screen, <name>.raster), via AiNodeLookUpByName. The implementation follows from that:

  • HdArnoldCoordSys sprim (libs/render_delegate/coord_sys.{h,cpp}) — new HdCoordSys implementation that creates an Arnold camera node per coordinate system, named uniquely from the sprim id so several coordinate systems sharing a name never collide. When the coordinate system is bound to a camera prim, the bound camera's transform and frustum are mirrored (persp_camera or ortho_camera, recreating the node in place if the projection type changes), so the projective .NDC/.screen/.raster spaces match the projector. The bound camera is resolved from the sprim id in the scene-index path, and by world-transform matching against the camera sprims in the legacy delegate path (where the id only encodes the binding relationship), so hydra and hydra2 agree.
  • coordSys registered as a supported sprim type and created in HdArnoldRenderDelegate::CreateSprim (render_delegate.cpp). Registering the token is also what makes UsdImagingDelegate answer coord-sys binding queries.
  • Scene-index plugin (plugins/scene_index/coordSysSIP.{h,cpp}) — UsdImagingCreateSceneIndices does not turn coordSys bindings into coordSys prims, so a renderer scene-index plugin appends HdsiCoordSysPrimSceneIndex for renderer "Arnold". This makes it work in any Hydra host (usdview, husk, the procedural), not just one. Correspondingly, the procedural's manual AppendSceneIndicesForRenderer call in reader.cpp was removed: HdRenderIndex already applies renderer plugins at its terminal, so it ran every plugin twice — harmless for the pre-existing filtering plugins, but the prim-adding coordSys plugin produced a duplicate camera.
  • Space-name translation (libs/common/materials_utils.cpp) — MaterialX geometric nodes (ND_position_vector3, ...) become Arnold OSL nodes whose space input is rewritten to Arnold's naming: map_proj:ndcmap_proj.NDC, :raster/:screen → dotted, plain names → <name>.camera. Standard MaterialX/OSL spaces (model, object, world, ...) and already-dotted values are left untouched. Shared by the procedural and render-delegate paths.
  • Per-rprim camera binding (node_graph.{h,cpp}, and every rprim that assigns a material) — because Arnold resolves named spaces globally by node name, the material's space strings must point at the camera bound by the specific rprim. HdArnoldNodeGraph::RemapCoordSysSpaces rewrites them, and the remap-aware GetCached*Shader(remap) overloads let a material shared by rprims with different bindings work: the first binding claims the base network (no overhead in the common case), and each conflicting binding gets its own re-translated variant with stable node names across re-syncs. The rprim side is the shared HdArnoldGetCoordSysRemap() helper in coord_sys.{h,cpp}, used by mesh.cpp, basis_curves.cpp, points.cpp, native_rprim.cpp, gaussian_splat.cpp and volume.cpp; each of those re-assigns its material on DirtyCategories (which carries binding changes) and includes that bit in its initial dirty bits. Light-filter graphs stay on the plain accessor — lights have no coordSys bindings.
  • Resolution-independent projection (render_delegate.{h,cpp}, render_pass.cpp) — Arnold derives every camera's vertical fov from the render frame aspect ratio (AiWorldToScreenMatrix), not from the camera's own aperture, so a projector's vertical extent was wrong whenever the render resolution's aspect differed from the projector's aperture. Coordinate-system cameras register with the delegate and UpdateCoordSysCameraProjections() cancels the frame aspect from their vertical screen window in HdArnoldRenderPass::_Execute, after xres/yres are final for the render (a sync-time read is stale). It only writes when the value changed, so it does not restart the render every frame. This matches Karma, which uses the projector's own aperture.
  • Vertical-flip configuration (config.{h,cpp}) — Arnold's NDC convention is Y-opposite to its screen/raster, so no single camera flip makes all four spaces agree. HDARNOLD_coordsys_flip_ndc_v (default on) routes .NDC through a second, extra-flipped camera node while .camera/.screen/.raster keep the primary one; HDARNOLD_coordsys_flip_v (default off) flips every space together. The defaults were chosen to agree with Karma out of the box for all four spaces.
  • Testsuitetest_13221 (perspective projectors), test_13221.1 (orthographic projectors), test_13221.2 (one shared material bound by two rprims to different cameras) and test_13221.3 (curves + points, i.e. non-mesh rprims), exercising .camera/.NDC/.raster/.screen on both the hydra and hydra2 passes. CoordSys is implemented in the render delegate only, so these opt out of the procedural usd pass through a per-pass skip mechanism: tools/test/testsuite.py now populates the previously-unused pass_<name> skip lists from testsuite/groups.

Issues fixed in this pull request
Fixes #32 (ARNOLD-13221)

Additional context

Reference: Coordinate Systems in USD proposal

Known limitations / follow-ups, all deliberate:

  1. The procedural (usd) pass has no coordSys support — implemented in the render delegate only; the reader would need the same camera-node creation, frustum mirroring, name remap and aspect cancellation. Either factor the shared logic out of coord_sys.cpp, or let the reader route through Hydra.
  2. Shared material with conflicting bindings costs a network copy — the conflicting rprim gets a re-translated variant of the material. Correct, but not free in memory.
  3. The space-name rewrite is heuristic for plain names — without the rprim's binding list at material-translation time, a plain non-standard space name cannot be distinguished from an arbitrary custom OSL space, so it gets a .camera suffix. The colon forms are unambiguous. Gating on GetCoordSysBindings() membership would change the shared materials_utils.cpp signatures.
  4. Legacy delegate camera recovery is by transform matching — two projectors at the identical world transform with different frustums are indistinguishable; the code returns nullptr (transform-only fallback) rather than pick wrong. The legacy delegate exposes no way to map the binding-relationship sprim id back to its backing camera prim.
  5. The "correct" projection orientation is still an open convention question — the flip defaults match Karma, but neither Karma nor Arnold strictly follows the USD convention, so they may be revisited.

🤖 Generated with Claude Code

@cpichard
cpichard marked this pull request as ready for review August 6, 2026 12:46
Unregister the coordinate-system camera from the per-render aspect
correction on both frustum-less paths, so a projector that stops
resolving to a camera no longer has its screen window rewritten from
a stale aperture ratio.

Only rewrite a MaterialX "space" input when the value actually holds a
string or token; VtValueGetString yields an empty string otherwise,
which clobbered the OSL node's default space.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Hydra "coordSys"

1 participant