added interior mapping example with equirectangular samples#2951
Open
codesavory wants to merge 3 commits into
Open
added interior mapping example with equirectangular samples#2951codesavory wants to merge 3 commits into
codesavory wants to merge 3 commits into
Conversation
…ce renders - Add comment to closest filtertype: explains bilinear bleed risk at UV seam - Rewrite Decision 3 in design notes: replaces hand-wavy "abs_inv_dir absorbs the sign" with a sourced proof — HwViewDirectionNode.cpp:69 emits normalize(positionWorld - viewPosition), so viewdirection is camera-to-surface (inward), making viewDir_tangent.z < 0 and the slab formula correct as-is - Add preview_room.png and preview_cornell.png: rendered reference screenshots for PR visual verification (plane.obj, 1280x720, MaterialXView --captureFilename) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Contributor
Author
|
What is the review process, does this require more changes or should I add someone manually to the reviewers list? |
Member
|
@codesavory No additional changes needed before we can review, and we just need to find the time and focus! |
Contributor
|
Hi @codesavory, thanks for this contribution and some notes from my side:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds a basic example for the issue: #2526
Reference Renders
room.png(brick room):cornell.png(Cornell box):You can read more about the design, architecture and reasoning here:
interior-mapping-design.md
Design Notes
Interior Mapping for MaterialX — Design Notes
Issue: #2526 — Example graph for interior mapping
Branch:
issue-2526-interior-mappingFiles:
resources/Materials/TestSuite/nprlib/interior_mapping.mtlx,room.png,cornell.pngBackground
Interior mapping is a shading technique that fakes 3D rooms behind flat window surfaces — no room geometry required. Each UV tile becomes a separate room instance. The core idea: cast a ray from the camera through the surface into a virtual box, find which interior wall the ray hits, sample a texture at that point.
The ask (from jstone-lucasfilm) is simple: add a working
.mtlxexample to thenprlibtest suite. Taggedgood first issue. No new node definition required for a first contribution — a barenodegraphlikestarfield.mtlxis sufficient.Existing nprlib Pattern
Four examples establish the range:
edge_brighten.mtlxnodegraph, no nodedefstarfield.mtlxnodegraph, usesviewdirection— closest precedentgooch_shade.mtlxtoon_shade.mtlxnodedef+nodegraph+ material instance, namespaceInterior mapping targets the
starfield.mtlxpattern: bare nodegraph,viewdirection-based, nonodedef.Three Reference Implementations
1. Godot GLSL (~90 lines)
obj_vertex − obj_cam, no matrix mathceil()for tiled room boundariesceil()+step().zy,.xy,.xz), maps into 3×2 face atlasroom_depth × 22. Pablode (MaterialX XML, 155 lines)
viewdirectionthrough it; portable across any mesh orientation, costs ~6 extra nodes(frac(u)×2−1, frac(v)×2−1, +1)— Z=+1 places origin on front face of[−1,1]³cube;frac()gives per-tile tiling freedist = |1/dir| − origin·(1/dir), then twominnodes; fully branchlessatan2(x, z)+asin(y). Continuous, no per-face branching1/depth3. Jakethorn MXSL (~50 lines)
viewdirection("object"), negated explicitlyvec3(uv×2−1, −1)— same math as pablode, Z=−1 convention (opposite sign)dn = (−origin−1)/dir,dp = (−origin+1)/dir,max(dn, dp)per componentimage()callsComparison Table
Why It Matters for MaterialX
MaterialX nodegraphs are pure dataflow — no
if/else.The Godot and jakethorn face-detection approaches require 6-way or 5-way branching. Expressing that branchlessly in MaterialX needs ~60 extra
step()-based masking nodes to select and blend face UVs — defeats the "simple example" goal entirely.Pablode's equirectangular approach was designed with this constraint in mind. The pipeline
normalize → atan2/asin → imageis fully branchless and maps cleanly to MaterialX dataflow nodes.Design Decisions for This Implementation
Decision 1: Equirectangular projection (pablode approach)
Chosen because it's the only approach expressible without branching in MaterialX's dataflow model. Single texture, continuous UV mapping, ~30 nodes.
Decision 2: Tangent space (not object space)
Pablode's TBN approach is more portable. Object-space approaches only work correctly when the mesh is axis-aligned with the world — tangent space works on any oriented mesh surface.
Decision 3: No explicit negation needed — viewdirection is already inward
MaterialX
viewdirectionis documented as "from shading point toward camera," butHwViewDirectionNode.cpp(line 69) emitsnormalize(positionWorld − viewPosition)— surface point minus camera position — which is the camera → surface (inward) direction.In tangent space,
viewDir_tangent.z < 0for a front-facing surface. The slab formula|1/dir| − origin·(1/dir)works correctly with this inward direction:inv_dir.z = 1/dzwheredz < 0, soinv_dir.z < 0abs_inv_dir.z = |1/dz| > 0dist.z = |1/dz| − 1·(1/dz) = |1/dz| − (negative) = 2/|dz| > 0For a straight-on view (
dz = −1),dist.z = 2, hitting the back wall att = 2. For angled viewsmin_dist < 2and the ray hits a side wall. No negation node is needed.Decision 4: atan2 seam fix — negate both dx and dz
Vanilla
atan2(dx, dz)has its ±π discontinuity at dz < 0 (the back wall of the box). That's the most-viewed face — a visible seam artifact.Fix:
atan2(−dx, −dz)shifts the discontinuity to dz > 0 (the front face, +Z), which is never visible in interior mapping (the ray origin is on the front face, so the ray never hits it). Back wall, side walls, floor, and ceiling are all seam-free.Decision 5: Rotation offset 0.25
After negating dx and dz, atan2=0 → lon = 0.5 (back wall center). Adding 0.25 keeps the same texture mapping as the unnegated version: back wall center → U = 0.75.
Decision 6: Precise spherical projection constants
−0.15915−1/(2π), converts radians → [0,1]0.318311/πDecision 7: Filter mode
filtertype="closest"is deliberate — bilinear filtering bleeds across the0/1UV wrap boundary, producing a visible horizontal seam artifact at the equirectangular face boundaries.Node Graph Summary
Total: ~35 nodes. No branching. One texture.
What Was Ruled Out
step()-based masking, ~60 extra nodes, defeats simplicity goalOpen questions
I will create a separate PR for a node-def version for interior-mapping
@jstone-lucasfilm, @jakethorn, @pablode Please review and share feedback if you have a chance!
Attribution: Support of Claude-Code!
Changes since PR open
Commit
40e07cefclosestfilter comment — added inline note thatfiltertype="closest"is deliberate: bilinear filtering bleeds across the0/1UV wrap boundary, producing a visible horizontal seamHwViewDirectionNode.cpp:69emitsnormalize(positionWorld − viewPosition)(camera→surface, inward), soviewDir_tangent.z < 0for front-facing surfaces and the slab formula givesdist.z = 2/|dz| > 0without any negation node needed