Skip to content

Conversation

jjhembd
Copy link
Contributor

@jjhembd jjhembd commented Sep 29, 2025

Description

This PR reworks the voxel shaders to perform the raymarching in eye-space coordinates, rather than model-space. This avoids precision problems when zoomed in close to small samples in a large model space.

Most of the precision problem came from the sampling of the voxel. The traversal in Octree.glsl has been re-worked to input a two-part coordinate:

  • Tile coordinate: the integer [x, y, z, w] indices of the tile within the tileset
  • Tile UV coordinate: floating point numbers in the interval [0, 1] indicating the x, y, z positions within the tile

This new input to the traversal effectively gives us nearly double precision when sampling a large dataset.

Some parts of the code are still in a local model-centered coordinate system:

  • Calculation of the "jacobian" (partial derivatives of the transform from Cartesian to cylindrical/ellipsoidal coordinates). This matrix is more easily defined in model coordinates.
  • Clipping plane intersections
  • Many of the initial shape intersection routines

The latter 2 items could also be converted to eye coordinates with additional work. I did not do that work in this PR, mostly because of time constraints. I also haven't observed significant precision issues with those intersections, so that work does not seem urgent at this time.

Issue number and link

Fixes #12061

Testing plan

  1. Load the testing Sandcastle and verify sharp rendering. Compare to the same Sandcastle in main.
  2. Load the existing Voxel Sandcastles and verify that all shape types work, and picking works

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

Jeshurun Hembd added 29 commits July 29, 2025 09:48
Copy link

Thank you for the pull request, @jjhembd!

✅ We can confirm we have a CLA on file for you.

@lukemckinstry lukemckinstry self-requested a review September 30, 2025 17:17
Copy link
Contributor

@lukemckinstry lukemckinstry left a comment

Choose a reason for hiding this comment

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

Hi @jjhembd, I took a quick pass trying out the sandcastles.

The only issue I see is for cylinder procedural tileset, there seems to be an unexpected visual effect as I move the cursor. You cannot really see my cursor in the video, but the small flash of a tiny voxel cluster is following the cursor, and their is also a station cluster flashing on and off in the upper right next next to the voxel inspector. The same effect is visible on the main, so this may not be related to the changes in this PR. The picking behavior looks good on the other 5 examples.

Screencast.from.2025-09-30.13-16-43.mp4

edit I actually see this in Ellipsoid procedural tileset as well. It seems slightly more noticeable after zooming in or out.

Screencast.from.2025-09-30.13-28-01.mp4

float clipAmount;
float pixelWidth = czm_metersPerPixel(position);
bool breakAndDiscard = false;
for (int i = 0; i < ${clippingPlanesLength}; ++i)
Copy link
Contributor

Choose a reason for hiding this comment

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

Thanks for cleaning this up! Should this (either here or a later PR) be adjusted to use compiler define statements?

Comment on lines 50 to 65
/**
* An event triggered when a new clipping plane is added to the collection. Event handlers
* are passed the new plane and the index at which it was added.
* @type {Event}
* @default Event()
*/
this.planeAdded = new Event();

/**
* An event triggered when a new clipping plane is removed from the collection. Event handlers
* are passed the new plane and the index from which it was removed.
* @type {Event}
* @default Event()
*/
this.planeRemoved = new Event();

Copy link
Contributor

Choose a reason for hiding this comment

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

I think these should both be readonly, and therefore the default should be extraneous.

Suggested change
/**
* An event triggered when a new clipping plane is added to the collection. Event handlers
* are passed the new plane and the index at which it was added.
* @type {Event}
* @default Event()
*/
this.planeAdded = new Event();
/**
* An event triggered when a new clipping plane is removed from the collection. Event handlers
* are passed the new plane and the index from which it was removed.
* @type {Event}
* @default Event()
*/
this.planeRemoved = new Event();
/**
* An event triggered when a new clipping plane is added to the collection. Event handlers
* are passed the new plane and the index at which it was added.
* @type {Event}
* @readonly
*/
this.planeAdded = new Event();
/**
* An event triggered when a new clipping plane is removed from the collection. Event handlers
* are passed the new plane and the index from which it was removed.
* @type {Event}
* @readonly
*/
this.planeRemoved = new Event();

Copy link
Contributor

@ggetz ggetz left a comment

Choose a reason for hiding this comment

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

Thanks @jjhembd! Overall, this is good shape. The code is looking pretty clean to me. I didn't look deeply into the math itself, but the results are looking great!

Finally a reminder to update CHANGES.md.

}

vec3 scaleShapeUvToShapeSpace(in vec3 shapeUv) {
// TODO: scaling is wrong!
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you mentioned offline that this is TODO is on your radar–A reminder to address here or to open an issue.

vec3 rtu = u_cylinderEcToRadialTangentUp * positionEC;
// 2. Compute change in angular and radial coordinates. TODO: scaling camera is wrong!
vec2 dPolar = computePolarChange(rtu.xy, u_cameraShapePosition.x * u_cylinderWorldToLocalScale.x);
// TODO: this is wrong! Scaling needs to be applied in local XYZ coordinates
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here.

@jjhembd jjhembd marked this pull request as ready for review October 1, 2025 14:16
@lukemckinstry
Copy link
Contributor

I did some basic testing on existing voxel sandcastles and everything I observed matches main 👍 @jjhembd

@lukemckinstry lukemckinstry self-requested a review October 1, 2025 18:55
@jjhembd jjhembd added this pull request to the merge queue Oct 1, 2025
Merged via the queue into main with commit 8d67e9e Oct 1, 2025
9 checks passed
@jjhembd jjhembd deleted the voxel-rte branch October 1, 2025 19:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Relative-to-eye rendering for voxels
3 participants