Skip to content

MAXTOA-2033: bind volume shaders on step-size meshes#2705

Open
ZapAndersson wants to merge 2 commits into
Autodesk:masterfrom
ZapAndersson:andersz/MAXTOA-2033-volume-shader-issue
Open

MAXTOA-2033: bind volume shaders on step-size meshes#2705
ZapAndersson wants to merge 2 commits into
Autodesk:masterfrom
ZapAndersson:andersz/MAXTOA-2033-volume-shader-issue

Conversation

@ZapAndersson

Copy link
Copy Markdown
Contributor

A polymesh with step_size > 0 renders as an implicit volume, but the writer only routed shaders to the material's volume terminal for native volume nodes, so a mesh-volume's standard_volume landed on the surface terminal. On read-back the shape is treated as a volume and the surface-bound shader is dropped (falling back to _fallbackVolume), rendering black.

  • Writer: treat a polymesh with step_size > 0 as a volume (matching the reader's IsVolume) so its shader is written to the volume terminal.

  • Reader: for volume shapes, resolve the volume terminal with priority over the surface terminal, so a universal/preview surface (e.g. a UsdPreviewSurface from MaxUSD) can't shadow it.

  • Add writer (test_17642.1) and reader (test_17642.2) regression tests.

Issues fixed in this pull request
Fixes MAXTOA-2033: FlowRender: Differences in Volume Rendering

Additional context
Add any other context or screenshots about the pull request here.

A polymesh with step_size > 0 renders as an implicit volume, but the
writer only routed shaders to the material's volume terminal for native
volume nodes, so a mesh-volume's standard_volume landed on the surface
terminal. On read-back the shape is treated as a volume and the
surface-bound shader is dropped (falling back to _fallbackVolume),
rendering black.

- Writer: treat a polymesh with step_size > 0 as a volume (matching the
  reader's IsVolume) so its shader is written to the volume terminal.
- Reader: for volume shapes, resolve the volume terminal with priority
  over the surface terminal, so a universal/preview surface (e.g. a
  UsdPreviewSurface from MaxUSD) can't shadow it.
- Add writer (test_17642.1) and reader (test_17642.2) regression tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ZapAndersson ZapAndersson force-pushed the andersz/MAXTOA-2033-volume-shader-issue branch from 63d0456 to 1aaa763 Compare July 2, 2026 12:06
@ZapAndersson

Copy link
Copy Markdown
Contributor Author

MAXTOA-2033 — volume shader renders black (handoff)

Symptom: a standard_volume on a step-size polymesh (mesh-as-volume) renders black.

Root cause — a volume shape's shader ends up on the wrong material terminal, and a competing surface terminal shadows it. Three code paths, all affected differently:

  1. Writer (Export volume shaders in the volume context #2615 regression): processMaterialBinding only treated native volume nodes as volumes (AiNodeIs(node, str::volume)), so a polymesh+step_size volume shader was written to outputs:arnold:surface instead of arnold:volume. → Fixed: added IsVolumeShape() (native volume or polymesh with step_size > 0, mirroring the reader's IsVolume).
  2. Translator reader (Path 1): GetMaterialTargets was surface-first and ComputeSurfaceSource falls back to the universal scope, so MaxUSD's universal UsdPreviewSurface shadowed the volume. → Fixed: for volume shapes, resolve the volume terminal first.
  3. Hydra 1 (Path 2) — the nasty one: OpenUSD's UsdImagingMaterialAdapter::GetMaterialResource builds surface XOR volume (materialAdapter.cpp:502, "Only build a volume materialNetwork if we do not have a surface"). MaxUSD's universal outputs:surface satisfies the if, so the volume terminal is never handed to the render delegate → GetCachedVolumeShader() → _fallbackVolume → black. This can't be fixed in libs/render_delegate/ (the delegate never sees the terminal). → Worked around in the writer: for volume shapes, strip the material's surface terminal(s) so ComputeSurfaceSource returns empty and the volume terminal is built. Fixes all three paths at the source.
  • Note: Hydra 2 (Path 3) uses dataSourceMaterial.cpp (per-render-context, no XOR, skips universal-context outputs for the arnold material) and appears fine — unverified.

Commits on the branch: writer (IsVolumeShape + volume terminal + surface-strip in prim_writer.cpp), reader (GetMaterialTargets volume-preference in utils.cpp/.h), tests test_17642.1 (writer) and test_17642.2 (reader/translator).

Open decisions for Seb:

  • Location of the surface-strip: currently in the shared arnold-usd writer (affects all DCCs). Correct in principle (a volume shape has no surface), but is a MAXtoA-only fix preferable?
  • Shared-material edge case: if one MaxUSD material is bound to both a volume and a surface object, the strip removes the surface for both. Not expected with per-shader materials, but flagging it.
  • Preview surface: stripping means volume materials lose their UsdPreviewSurface stand-in for non-Arnold viewports. Acceptable?
  • Better long-term fix? e.g. a custom arnold Material adapter that builds all terminals (bypasses OpenUSD's XOR), or fixing this upstream — vs. the authoring-side strip.
  • Test gap: the removal path (pre-existing surface) isn't directly covered; test_17642.1 only asserts the resulting invariant. A chaser-simulating test could be added.

@cpichard

cpichard commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

✅ CI passed (build 6).

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.

2 participants