-
Notifications
You must be signed in to change notification settings - Fork 3.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimum step size and lighting for cylinder- and ellipsoid-shaped voxels #11875
Conversation
Thank you for the pull request, @jjhembd! ✅ We can confirm we have a CLA on file for you. |
shaderUniforms.eccentricitySquared = 1.0 - axisRatio * axisRatio; | ||
shaderUniforms.evoluteScale = Cartesian2.fromElements( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This evoluteScale
used to be computed in single precision on the shader, in the nearestPointOnEllipse
function in convertUvToEllipsoid.glsl
. It is the difference of two very similar values, so it is safer to compute it in double precision on the CPU to avoid subtractive cancellation.
vec4 entry = intersectionMax(shapeIntersection.entry, voxelEntry); | ||
|
||
float firstExit = minComponent(distanceToExit); | ||
float stepSize = clamp(firstExit, fixedStep * 0.02, fixedStep); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the 0.02
come from? Should this be configurable, or at least declared as a constant?
#if defined(JITTER) | ||
float hash(vec2 p) | ||
{ | ||
vec3 p3 = fract(vec3(p.xyx) * 50.0); // magic number = hashscale |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know this was existing code that was just moved to this file in this PR, but would it be possible to better explain what these magic numbers are in a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't been able to trace where this hash function came from. It is similar to one of the functions in a popular shadertoy example, but most uses that I have seen use a different value for the hashscale. See for example the hash12
function in this gist.
I added a link to the shadertoy in a comment.
float radius = length(position.xy); // [0, 1] | ||
vec3 radial = normalize(vec3(position.xy, 0.0)); | ||
|
||
// TODO: why??? Why not the local z in [-1, 1] ?? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you able to address this TODO?
@@ -829,43 +829,4 @@ describe("Scene/VoxelBoxShape", function () { | |||
); | |||
}).toThrowDeveloperError(); | |||
}); | |||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a relevant replacement spec for the Jacobian approach? (It's OK if there's not.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, all step size math has been moved to the shader.
@jjhembd I'm seeing some artifacts in the center of the screen when zoomed out for both box and cyllinder types, but not ellipsoid: They seem to flicker, even when the camera is not moving. Maybe some kind of z-fighting? |
This should be fixed. Let me know if it works on your end now! |
Thanks @jjhembd, the cylinder is looking good. However, I still seem to see some artifacts with the box shape. They persist despite adjust the step size in the voxel inspector, but do change appearance when the value is changed. |
As discussed offline: we're not able to reproduce this artifact on a Windows machine. I've opened #11894 to track the bug. |
Thanks @jjhembd! This should be good to go then. |
Description
This PR implements an optimum step size calculation for cylinder- and ellipsoid-shaped voxels. This generalizes the work done in #11050 for box-shaped voxels, using a new approach based on the partial derivatives of the coordinate transform from world space to shape space.
The partial derivative approach also enables a simpler method to derive the surface normal of the current voxel. This extends the functionality of #11076 to all shape types.
Background
Ray marching is performed in a Cartesian space, which has been scaled to a "UV" space where the range [0, 1] spans the bounds of the shape. At each step of the marching, the voxel shaders convert the Cartesian "UV" ray position into the UV coordinates of the shape, whether BOX (
convertUvToBox.glsl
), CYLINDER (convertUvToCylinder.glsl
), or ELLIPSOID (convertUvToEllipsoid
).We then need to estimate two things:
The step length could simply be some fraction of the average voxel size. (This is the behavior for CYLINDER and ELLIPSOID prior to this PR.) However, we need to balance two factors:
How surface normals and optimum step size are computed
Prior to this PR, both normals and optimum step size for BOX voxels were computed by constructing the bounding box of the current cell, and performing a new ray-shape intersection with that bounding box. This intersection yielded both the normal of the incident face, and the distance to the far side of the voxel. Using the distance to the far side as the next step length ensured that the step would be as long as possible, without skipping across any voxels.
Intersections with individual voxel cells suffer from two problems:
This PR uses the partial derivatives of the coordinate transform to get a linear estimate of the surface normal and step length.
The derivatives of the coordinate transform describe how fast the shape coordinate is changing for a small change in Cartesian position. As an example, consider the Jacobian matrix of the partial derivatives of the transform from Cartesian coordinates$(x, y, z)$ to ellipsoidal coordinates $(\phi, \theta, h)$ :
This matrix is evaluated at the current Cartesian ray position, and then multiplied with the ray direction to obtain the gradient (the rate of change) of the ellipsoidal coordinates along the ray:
Once we know how fast each ellipsoidal coordinate is changing for a small step along the ray, it is relatively simple to estimate the step length needed to reach the next cell boundary.
The surface normal at the point of entry into the voxel is aligned with the coordinate vector along which the ray last crossed a cell boundary. Conveniently, the rows of the Jacobian matrix are scaled versions of the local ellipsoidal coordinate vectors in Cartesian space. We can therefore simply normalize the appropriate row, and use the sign of the gradient, to obtain the surface normal at the point of intersection. For example, if the most-recently-crossed cell boundary (along the ray direction) is a boundary in longitude ($\phi$ ), the relevant surface normal is $-sign(J \vec{d}) \frac{J_{123}}{|| J_{123} ||}$
Key code changes
computeApproximateStepSize
method (and associated specs) fromVoxelShape
, since all step size calculations are now done in the shader.IntersectBox.glsl
to use a simpler logic, more similar to the other shapes. Note that theintersectBox
function is no longer used for individual voxel intersections. It is only used for the initial shape intersection to find the shape bounds.VoxelUtils.glsl
.\getStepSize
function inVoxelFS.glsl
to use the same Jacobian-based method for all shape types.convertUvToBox.glsl
,convertUvToCylinder.glsl
, andconvertUvToEllipsoid.glsl
to return the Jacobian matrix of partial derivatives, along with the converted shape space coordinate.For the
convertUvTo???.glsl
changes: the oldconvertUvToShapeUvSpace
methods converted from Cartesian space, to shape space, to a UV shape space (spanning [0, 1]), all in one method. This has now been broken into two steps:convertUvToShapeSpaceDerivative
, which transforms the coordinate to shape space and computes the partial derivatives of the transformation.convertShapeToShapeUvSpace
, which converts from the native shape space to a UV space spanning [0, 1].Testing plan
Load this local Sandcastle and verify that all 3 shape types are rendered with sharp edges and lighting.
Author checklist
CONTRIBUTORS.md
CHANGES.md
with a short summary of my change