Skip to content

Render tiled-camera heightfields via the mesh path - #3088

Merged
adenzler-nvidia merged 2 commits into
newton-physics:mainfrom
adenzler-nvidia:adenzler/tiled-camera-heightfield-render
Jun 5, 2026
Merged

Render tiled-camera heightfields via the mesh path#3088
adenzler-nvidia merged 2 commits into
newton-physics:mainfrom
adenzler-nvidia:adenzler/tiled-camera-heightfield-render

Conversation

@adenzler-nvidia

@adenzler-nvidia adenzler-nvidia commented Jun 5, 2026

Copy link
Copy Markdown
Member

Summary

SensorTiledCamera did not render heightfield (HFIELD) shapes, and #2971 introduced a tiled-camera render-performance regression. Both stem from the same gap.

#2971 added an HFIELD ray-intersection branch to the three tiled-camera render kernels, but heightfields were never wired into the render BVH: is_supported_shape_type, compute_shape_local_bounds, and compute_shape_bvh_bounds (all in geometry/bvh.py) omitted HFIELD, so heightfields never entered the visible-shape BVH and never reached the renderer. The added branch was therefore unreachable, yet it still enlarged the color render megakernel's register footprint, dropping it below full GPU occupancy and regressing tiled-camera rendering.

Fix

Heightfields are already triangulated wp.Mesh shapes — their mesh is stored in shape_source_ptr and they carry no per-vertex normals — so they can be rendered through the existing mesh path rather than a dedicated branch:

  • Add HFIELD to the render-BVH inclusion (is_supported_shape_type) and to both the local- and world-bounds kernels, so heightfields enter the visible-shape BVH with correct AABBs.
  • RenderContext presents heightfields to the render kernels as MESH (via a per-shape render-type array passed to the kernel). model.shape_type is left as HFIELD for collision and BVH bounds.
  • The HFIELD branch is removed from the render kernels entirely. The render kernel is now identical to its pre-Add intersect_ray function that uses BVH accelerated raycasting, Remove SensorRaycast #2971 form, so it regains full occupancy while gaining heightfield support, with no per-scene kernel specialization.

Tests

Adds newton/tests/test_sensor_tiled_camera_heightfield.py, which renders a flat heightfield from above and verifies the depth is geometrically correct. The existing tiled-camera, heightfield-collision, and raycast suites continue to pass.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed SensorTiledCamera rendering for heightfield geometry so heightfields now appear correctly.
    • Restored tiled-camera render performance to previous levels, eliminating a regression.
    • Improved depth-hit reliability and accuracy when rendering heightfield surfaces.

newton-physics#2971 added HFIELD ray-intersection to the tiled-camera render
kernels but never wired heightfields into the render BVH:
is_supported_shape_type and the local- and world-bounds kernels
all omitted HFIELD, so heightfields never rendered. The unreachable
branch also enlarged the color render kernel enough to lower its GPU
occupancy, regressing tiled-camera rendering.

Heightfields are already triangulated wp.Mesh shapes with no
per-vertex normals, so render them through the existing mesh path:
add HFIELD to the render-BVH inclusion and bounds, and have
RenderContext present heightfields to the render kernels as MESH.
This removes the HFIELD branch from the kernels entirely, so the
render kernel is unchanged from before newton-physics#2971 while gaining
heightfield support. model.shape_type is left untouched for
collision and BVH bounds.
@coderabbitai

coderabbitai Bot commented Jun 5, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: bebb336e-ddd0-49be-b262-bb780c0132f6

📥 Commits

Reviewing files that changed from the base of the PR and between fd41a94 and d03b164.

📒 Files selected for processing (1)
  • newton/tests/test_sensor_tiled_camera_heightfield.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • newton/tests/test_sensor_tiled_camera_heightfield.py

📝 Walkthrough

Walkthrough

This PR fixes SensorTiledCamera rendering for heightfield shapes by treating heightfields as triangulated meshes. BVH support is extended for HFIELD, RenderContext remaps heightfields to meshes before kernel dispatch, ray-tracing code is simplified by removing HFIELD branches, and the fix is validated with an integration test.

Changes

Heightfield rendering via mesh-based path

Layer / File(s) Summary
BVH heightfield support
newton/_src/geometry/bvh.py
is_supported_shape_type, compute_shape_local_bounds, and compute_shape_bvh_bounds are updated to recognize GeoType.HFIELD and compute bounds using the same mesh-based triangulated-terrain logic as MESH.
RenderContext HFIELD→MESH remapping
newton/_src/sensors/warp_raytrace/render_context.py
A new shape_render_type array is introduced in RenderContext to remap HFIELD entries to MESH during init_from_model. The render() method uses this remapped array for kernel dispatch, ensuring heightfields follow the mesh ray-intersection path without changes to downstream ray-tracing code.
Ray-tracing kernel alignment
newton/_src/sensors/warp_raytrace/raytrace.py
HFIELD-specific branches are removed from closest-hit, depth-only closest-hit, and first-hit functions. Clarifying comments document that RenderContext handles HFIELD→MESH remapping, so heightfields flow through existing MESH intersection paths.
Integration test
newton/tests/test_sensor_tiled_camera_heightfield.py
A CUDA-gated test verifies SensorTiledCamera can render a flat 3×3 heightfield. The test constructs geometry, configures the sensor, renders a depth image, and validates hit coverage and depth bounds.
Changelog
CHANGELOG.md
Documents the fix in the "Fixed" section, describing the BVH inclusion of heightfields via the triangulated mesh render path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • newton-physics/newton#2971: Both PRs refactor GeoType.HFIELD handling to route through the mesh-based (wp.Mesh) path by removing HFIELD-specific branches, aligning a tiled-camera rendering fix with a raycasting refactor.
  • newton-physics/newton#2544: Both PRs modify BVH support in newton/_src/geometry/bvh.py, with the retrieved PR introducing a new public BVH builder API while this PR extends BVH to handle GeoType.HFIELD bounds computation.

Suggested reviewers

  • eric-heiden
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: treating heightfields as mesh shapes in the tiled-camera render path. It directly corresponds to the core fix across BVH, RenderContext, and raytrace modules.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@adenzler-nvidia

adenzler-nvidia commented Jun 5, 2026

Copy link
Copy Markdown
Member Author

benchmarks one instance of the regression

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
newton/tests/test_sensor_tiled_camera_heightfield.py (2)

57-62: ⚡ Quick win

Tighten the hit-coverage assertion.

This setup should only lose a small edge fraction, so hit > res * res // 2 would still pass if a large chunk of the heightfield vanished. A near-full-coverage threshold will make this regression test much harder to fool.

Proposed assertion tightening
-        self.assertGreater(
-            hit, res * res // 2, msg=f"heightfield should fill most of the view; only {hit}/{res * res} pixels hit"
-        )
+        min_expected_hits = int(res * res * 0.9)
+        self.assertGreaterEqual(
+            hit,
+            min_expected_hits,
+            msg=f"heightfield should fill nearly the whole view; only {hit}/{res * res} pixels hit",
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@newton/tests/test_sensor_tiled_camera_heightfield.py` around lines 57 - 62,
Replace the weak coverage check (self.assertGreater(hit, res * res // 2)) with a
stricter requirement so almost all pixels must hit the heightfield; e.g.,
require hit >= int(res * res * 0.95) (or another high fraction like 0.9) and
update the failure message to reflect the new threshold; locate the assertion
referencing variables hit and res in test_sensor_tiled_camera_heightfield.py and
change the numeric threshold and message accordingly.

53-55: ⚡ Quick win

Remove the explicit sync before depth.numpy().

.numpy() already blocks for the device-to-host copy, so Line 53 just adds an extra barrier and violates the Warp-array guideline.

As per coding guidelines, "Never call wp.synchronize() or wp.synchronize_device() right before .numpy() on a Warp array, as .numpy() performs a synchronous device-to-host copy that completes all outstanding work".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@newton/tests/test_sensor_tiled_camera_heightfield.py` around lines 53 - 55,
Remove the redundant explicit synchronization before converting the Warp array
to NumPy: in the test function (test_sensor_tiled_camera_heightfield) delete the
wp.synchronize() call that appears immediately before the depth.numpy() call
(the lines where wp.synchronize() is called then d = depth.numpy()[0, 0]);
.numpy() already performs a synchronous device-to-host copy, so simply call
depth.numpy() directly without the preceding wp.synchronize().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@newton/tests/test_sensor_tiled_camera_heightfield.py`:
- Around line 57-62: Replace the weak coverage check (self.assertGreater(hit,
res * res // 2)) with a stricter requirement so almost all pixels must hit the
heightfield; e.g., require hit >= int(res * res * 0.95) (or another high
fraction like 0.9) and update the failure message to reflect the new threshold;
locate the assertion referencing variables hit and res in
test_sensor_tiled_camera_heightfield.py and change the numeric threshold and
message accordingly.
- Around line 53-55: Remove the redundant explicit synchronization before
converting the Warp array to NumPy: in the test function
(test_sensor_tiled_camera_heightfield) delete the wp.synchronize() call that
appears immediately before the depth.numpy() call (the lines where
wp.synchronize() is called then d = depth.numpy()[0, 0]); .numpy() already
performs a synchronous device-to-host copy, so simply call depth.numpy()
directly without the preceding wp.synchronize().

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 1c7f06d5-aa54-4977-be1f-a9395ff10f83

📥 Commits

Reviewing files that changed from the base of the PR and between 46de552 and fd41a94.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • newton/_src/geometry/bvh.py
  • newton/_src/sensors/warp_raytrace/raytrace.py
  • newton/_src/sensors/warp_raytrace/render_context.py
  • newton/tests/test_sensor_tiled_camera_heightfield.py

Tighten the coverage assertion from "> half the frame" to ">= 80%",
and drop the wp.synchronize() before .numpy() (which already performs
a synchronizing device-to-host copy).

The terrain covers the whole frame, but a stable ~10-15% of rays miss
along triangle edges (non-watertight mesh_query_ray) regardless of
resolution or camera offset, so a 90-95% threshold would be flaky;
the depth-value assertions remain the primary correctness check.
@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ All tests successful. No failed tests found.

📢 Thoughts on this report? Let us know!

@adenzler-nvidia
adenzler-nvidia enabled auto-merge June 5, 2026 15:02
@adenzler-nvidia adenzler-nvidia added this to the 1.3 Release milestone Jun 5, 2026
@adenzler-nvidia
adenzler-nvidia added this pull request to the merge queue Jun 5, 2026
Merged via the queue into newton-physics:main with commit c4c759e Jun 5, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants