Skip to content
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

Merged
merged 29 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
d298ab9
Decouple voxel coordinate conversion in shader
Feb 21, 2024
0600138
WIP: construct Jacobian in convertUvToEllipsoid
Feb 22, 2024
a9cc927
Fix bugs in voxel Jacobian, add QC
Feb 22, 2024
b71fc87
Use Jacobian to compute voxel size along ray
Feb 24, 2024
e2089ca
Return voxel Jacobian from coordinate conversion
Feb 24, 2024
37e211a
WIP: use jacobian to compute normal
Feb 24, 2024
901d4e2
Fix Jacobian-based voxel normal
Feb 24, 2024
73e025c
Use Jacobian to compute variable step size
Feb 24, 2024
e30bc68
Tweak step for ellipsoid voxels
Feb 25, 2024
42a5ab2
Merge branch 'main' into voxel-jacobian
Feb 25, 2024
bde7b96
Clean up ellipsoid voxel coordinate conversions
Feb 26, 2024
fbec093
Compare voxel and clipping plane normals for ellipsoids
Feb 27, 2024
49b61c8
Extend Jacobian approach to cylinder voxel steps and normals
Feb 27, 2024
68601dd
Use Jacobian approach for box voxel steps and normals
Feb 28, 2024
c0bb296
Simplify box voxel shape intersection
Feb 28, 2024
ce3844e
Clean up BOX voxel intersections
Feb 28, 2024
0c9b55d
Remove test variable from voxel fragment struct
Feb 28, 2024
9713110
Adjust voxel step size calculation
Mar 1, 2024
b3b64e3
Precompute value for ellipsoid voxel coordinate conversions
Mar 1, 2024
deccd88
Merge branch 'voxel-ellipsoid-math' into voxel-jacobian
Mar 1, 2024
474d3ea
Remove unused approximate voxel step size
Mar 4, 2024
b2d01c9
Avoid accumulating small errors in voxel raymarching
Mar 4, 2024
c7b8c0d
Clean up voxel ray marching and step calculation
Mar 5, 2024
b9711ca
Merge branch 'voxel-ellipsoid-math' into voxel-jacobian
Mar 7, 2024
2d82d76
Remove unused define from voxel shaders
Mar 7, 2024
88ac4b6
Update CHANGES.md
Mar 15, 2024
b4769e6
PR feedback
Mar 18, 2024
8d58f01
Avoid zero step size on voxel boundary
Mar 21, 2024
12d9a52
Restrict voxel step size at far side of shape
Mar 22, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
##### Additions :tada:

- Surface normals are now computed for clipping and shape bounds in VoxelEllipsoidShape and VoxelCylinderShape. [#11847](https://github.com/CesiumGS/cesium/pull/11847)
- Implemented sharper rendering and lighting on voxels with CYLINDER and ELLIPSOID shape. [#11076](https://github.com/CesiumGS/cesium/pull/11076)

### 1.115 - 2024-03-01

Expand Down
15 changes: 0 additions & 15 deletions packages/engine/Source/Scene/VoxelBoxShape.js
Original file line number Diff line number Diff line change
Expand Up @@ -427,21 +427,6 @@ VoxelBoxShape.prototype.computeOrientedBoundingBoxForSample = function (
);
};

/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
* @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
* @returns {number} The step size.
*/
VoxelBoxShape.prototype.computeApproximateStepSize = function (dimensions) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("dimensions", dimensions);
//>>includeEnd('debug');

return 1.0 / Cartesian3.maximumComponent(dimensions);
};

/**
* Defines the minimum bounds of the shape. Corresponds to minimum X, Y, Z.
*
Expand Down
61 changes: 0 additions & 61 deletions packages/engine/Source/Scene/VoxelCylinderShape.js
Original file line number Diff line number Diff line change
Expand Up @@ -660,67 +660,6 @@ VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function (
);
};

const scratchOrientedBoundingBox = new OrientedBoundingBox();
const scratchVoxelScale = new Cartesian3();
const scratchRootScale = new Cartesian3();
const scratchScaleRatio = new Cartesian3();

/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
* @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
* @returns {number} The step size.
*/
VoxelCylinderShape.prototype.computeApproximateStepSize = function (
dimensions
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("dimensions", dimensions);
//>>includeEnd('debug');

const shapeTransform = this.shapeTransform;
const minRadius = this._minimumRadius;
const maxRadius = this._maximumRadius;
const minHeight = this._minimumHeight;
const maxHeight = this._maximumHeight;
const minAngle = this._minimumAngle;
const maxAngle = this._maximumAngle;

const lerpRadius = 1.0 - 1.0 / dimensions.x;
const lerpHeight = 1.0 - 1.0 / dimensions.y;
const lerpAngle = 1.0 - 1.0 / dimensions.z;

// Compare the size of an outermost cylinder voxel to the total cylinder
const voxelMinimumRadius = CesiumMath.lerp(minRadius, maxRadius, lerpRadius);
const voxelMinimumHeight = CesiumMath.lerp(minHeight, maxHeight, lerpHeight);
const voxelMinimumAngle = CesiumMath.lerp(minAngle, maxAngle, lerpAngle);
const voxelMaximumRadius = maxRadius;
const voxelMaximumHeight = maxHeight;
const voxelMaximumAngle = maxAngle;

const voxelObb = getCylinderChunkObb(
voxelMinimumRadius,
voxelMaximumRadius,
voxelMinimumHeight,
voxelMaximumHeight,
voxelMinimumAngle,
voxelMaximumAngle,
shapeTransform,
scratchOrientedBoundingBox
);

const voxelScale = Matrix3.getScale(voxelObb.halfAxes, scratchVoxelScale);
const rootScale = Matrix4.getScale(shapeTransform, scratchRootScale);
const scaleRatio = Cartesian3.divideComponents(
voxelScale,
rootScale,
scratchScaleRatio
);
const stepSize = Cartesian3.minimumComponent(scaleRatio);
return stepSize;
};

/**
* Defines the minimum bounds of the shape. Corresponds to minimum radius, height, angle.
*
Expand Down
35 changes: 8 additions & 27 deletions packages/engine/Source/Scene/VoxelEllipsoidShape.js
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ function VoxelEllipsoidShape() {
this.shaderUniforms = {
ellipsoidRadiiUv: new Cartesian3(),
eccentricitySquared: 0.0,
evoluteScale: new Cartesian2(),
ellipsoidInverseRadiiSquaredUv: new Cartesian3(),
ellipsoidRenderLongitudeMinMax: new Cartesian2(),
ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(),
Expand Down Expand Up @@ -420,9 +421,14 @@ VoxelEllipsoidShape.prototype.update = function (
shapeMaxExtent,
shaderUniforms.ellipsoidRadiiUv
);
const axisRatio =
Cartesian3.minimumComponent(shapeOuterExtent) / shapeMaxExtent;
const { x: radiiUvX, z: radiiUvZ } = shaderUniforms.ellipsoidRadiiUv;
const axisRatio = radiiUvZ / radiiUvX;
shaderUniforms.eccentricitySquared = 1.0 - axisRatio * axisRatio;
shaderUniforms.evoluteScale = Cartesian2.fromElements(
Copy link
Contributor Author

@jjhembd jjhembd Mar 15, 2024

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.

(radiiUvX * radiiUvX - radiiUvZ * radiiUvZ) / radiiUvX,
(radiiUvZ * radiiUvZ - radiiUvX * radiiUvX) / radiiUvZ,
shaderUniforms.evoluteScale
);

// Used to compute geodetic surface normal.
shaderUniforms.ellipsoidInverseRadiiSquaredUv = Cartesian3.divideComponents(
Expand Down Expand Up @@ -816,31 +822,6 @@ VoxelEllipsoidShape.prototype.computeOrientedBoundingBoxForSample = function (
);
};

/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
* @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
* @returns {number} The step size.
*/
VoxelEllipsoidShape.prototype.computeApproximateStepSize = function (
dimensions
) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.object("dimensions", dimensions);
//>>includeEnd('debug');

const ellipsoid = this._ellipsoid;
const ellipsoidMaximumRadius = ellipsoid.maximumRadius;
const minimumHeight = this._minimumHeight;
const maximumHeight = this._maximumHeight;

const shellToEllipsoidRatio =
(maximumHeight - minimumHeight) / (ellipsoidMaximumRadius + maximumHeight);
const stepSize = (0.5 * shellToEllipsoidRatio) / dimensions.z;
return stepSize;
};

/**
* Computes an {@link OrientedBoundingBox} for a subregion of the shape.
*
Expand Down
10 changes: 1 addition & 9 deletions packages/engine/Source/Scene/VoxelPrimitive.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,12 +320,6 @@ function VoxelPrimitive(options) {
*/
this._transformNormalLocalToWorld = new Matrix3();

/**
* @type {number}
* @private
*/
this._stepSizeUv = 1.0;

// Rendering
/**
* @type {boolean}
Expand Down Expand Up @@ -1132,7 +1126,7 @@ VoxelPrimitive.prototype.update = function (frameState) {
cameraPositionWorld,
uniforms.cameraPositionUv
);
uniforms.stepSize = this._stepSizeUv * this._stepSizeMultiplier;
uniforms.stepSize = this._stepSizeMultiplier;

// Render the primitive
const command = frameState.passes.pick
Expand Down Expand Up @@ -1345,8 +1339,6 @@ function updateShapeAndTransforms(primitive, shape, provider) {
);

// Set member variables when the shape is dirty
const dimensions = provider.dimensions;
primitive._stepSizeUv = shape.computeApproximateStepSize(dimensions);
primitive._transformPositionWorldToUv = Matrix4.multiplyTransformation(
transformPositionLocalToUv,
transformPositionWorldToLocal,
Expand Down
8 changes: 7 additions & 1 deletion packages/engine/Source/Scene/VoxelRenderResources.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import combine from "../Core/combine.js";
import defined from "../Core/defined.js";
import ShaderBuilder from "../Renderer/ShaderBuilder.js";
import ShaderDestination from "../Renderer/ShaderDestination.js";
import VoxelUtils from "../Shaders/Voxels/VoxelUtils.js";
import VoxelFS from "../Shaders/Voxels/VoxelFS.js";
import VoxelVS from "../Shaders/Voxels/VoxelVS.js";
import IntersectionUtils from "../Shaders/Voxels/IntersectionUtils.js";
Expand Down Expand Up @@ -89,6 +90,7 @@ function VoxelRenderResources(primitive) {
customShader.fragmentShaderText,
"#line 0",
Octree,
VoxelUtils,
IntersectionUtils,
Megatexture,
]);
Expand Down Expand Up @@ -124,7 +126,6 @@ function VoxelRenderResources(primitive) {

const shapeType = primitive._provider.shape;
if (shapeType === "BOX") {
shaderBuilder.addDefine("SHAPE_BOX", undefined, ShaderDestination.FRAGMENT);
shaderBuilder.addFragmentLines([
convertUvToBox,
IntersectBox,
Expand All @@ -138,6 +139,11 @@ function VoxelRenderResources(primitive) {
Intersection,
]);
} else if (shapeType === "ELLIPSOID") {
shaderBuilder.addDefine(
"SHAPE_ELLIPSOID",
undefined,
ShaderDestination.FRAGMENT
);
shaderBuilder.addFragmentLines([
convertUvToEllipsoid,
IntersectLongitude,
Expand Down
10 changes: 0 additions & 10 deletions packages/engine/Source/Scene/VoxelShape.js
Original file line number Diff line number Diff line change
Expand Up @@ -130,16 +130,6 @@ VoxelShape.prototype.computeOrientedBoundingBoxForTile =
VoxelShape.prototype.computeOrientedBoundingBoxForSample =
DeveloperError.throwInstantiationError;

/**
* Computes an approximate step size for raymarching the root tile of a voxel grid.
* The update function must be called before calling this function.
*
* @param {Cartesian3} voxelDimensions The voxel grid dimensions for a tile.
* @returns {number} The step size.
*/
VoxelShape.prototype.computeApproximateStepSize =
DeveloperError.throwInstantiationError;

/**
* Defines the minimum bounds of the shape. The meaning can vary per-shape.
*
Expand Down
1 change: 1 addition & 0 deletions packages/engine/Source/Scene/processVoxelProperties.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ function processVoxelProperties(renderResources, primitive) {
shaderBuilder.addStructField(voxelStructId, "vec3", "viewDirWorld");
shaderBuilder.addStructField(voxelStructId, "vec3", "surfaceNormal");
shaderBuilder.addStructField(voxelStructId, "float", "travelDistance");
shaderBuilder.addStructField(voxelStructId, "int", "stepCount");
shaderBuilder.addStructField(voxelStructId, "int", "tileIndex");
shaderBuilder.addStructField(voxelStructId, "int", "sampleIndex");

Expand Down
68 changes: 21 additions & 47 deletions packages/engine/Source/Shaders/Voxels/IntersectBox.glsl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// See IntersectionUtils.glsl for the definitions of Ray and NO_HIT
// See convertUvToBox.glsl for the definition of convertShapeUvToUvSpace
// See IntersectionUtils.glsl for the definitions of Ray, RayShapeIntersection,
// NO_HIT, Intersections

/* Box defines (set in Scene/VoxelBoxShape.js)
#define BOX_INTERSECTION_INDEX ### // always 0
Expand All @@ -8,66 +8,40 @@
uniform vec3 u_renderMinBounds;
uniform vec3 u_renderMaxBounds;

struct Box {
vec3 p0;
vec3 p1;
};

Box constructVoxelBox(in ivec4 octreeCoords, in vec3 tileUv)
{
// Find the min/max cornerpoints of the voxel in tile coordinates
vec3 tileOrigin = vec3(octreeCoords.xyz);
vec3 numSamples = vec3(u_dimensions);
vec3 voxelSize = 1.0 / numSamples;
vec3 coordP0 = floor(tileUv * numSamples) * voxelSize + tileOrigin;
vec3 coordP1 = coordP0 + voxelSize;

// Transform to the UV coordinates of the scaled tileset
float tileSize = 1.0 / pow(2.0, float(octreeCoords.w));
vec3 p0 = convertShapeUvToUvSpace(coordP0 * tileSize);
vec3 p1 = convertShapeUvToUvSpace(coordP1 * tileSize);

return Box(p0, p1);
}

vec3 getBoxNormal(in Box box, in Ray ray, in float t)
{
vec3 hitPoint = ray.pos + t * ray.dir;
vec3 lower = step(hitPoint, box.p0);
vec3 upper = step(box.p1, hitPoint);
return normalize(upper - lower);
}

// Find the distances along a ray at which the ray intersects an axis-aligned box
// See https://tavianator.com/2011/ray_box.html
RayShapeIntersection intersectBox(in Ray ray, in Box box)
RayShapeIntersection intersectBox(in Ray ray, in vec3 minBound, in vec3 maxBound)
{
// Consider the box as the intersection of the space between 3 pairs of parallel planes
// Compute the distance along the ray to each plane
vec3 t0 = (box.p0 - ray.pos) * ray.dInv;
vec3 t1 = (box.p1 - ray.pos) * ray.dInv;
vec3 t0 = (minBound - ray.pos) / ray.dir;
vec3 t1 = (maxBound - ray.pos) / ray.dir;

// Identify candidate entries/exits based on distance from ray.pos
vec3 entries = min(t0, t1);
vec3 exits = max(t0, t1);

// The actual box intersection points are the furthest entry and the closest exit
float entryT = max(max(entries.x, entries.y), entries.z);
float exitT = min(min(exits.x, exits.y), exits.z);
vec3 directions = sign(ray.dir);

// The actual intersection points are the furthest entry and the closest exit
float lastEntry = maxComponent(entries);
bvec3 isLastEntry = equal(entries, vec3(lastEntry));
vec3 entryNormal = -1.0 * vec3(isLastEntry) * directions;
vec4 entry = vec4(entryNormal, lastEntry);

vec3 entryNormal = getBoxNormal(box, ray, entryT - RAY_SHIFT);
vec3 exitNormal = getBoxNormal(box, ray, exitT + RAY_SHIFT);
float firstExit = minComponent(exits);
bvec3 isFirstExit = equal(exits, vec3(firstExit));
vec3 exitNormal = vec3(isLastEntry) * directions;
vec4 exit = vec4(exitNormal, firstExit);

if (entryT > exitT) {
entryT = NO_HIT;
exitT = NO_HIT;
if (entry.w > exit.w) {
entry.w = NO_HIT;
exit.w = NO_HIT;
}

return RayShapeIntersection(vec4(entryNormal, entryT), vec4(exitNormal, exitT));
return RayShapeIntersection(entry, exit);
}

void intersectShape(in Ray ray, inout Intersections ix)
{
RayShapeIntersection intersection = intersectBox(ray, Box(u_renderMinBounds, u_renderMaxBounds));
RayShapeIntersection intersection = intersectBox(ray, u_renderMinBounds, u_renderMaxBounds);
setShapeIntersection(ix, BOX_INTERSECTION_INDEX, intersection);
}
10 changes: 0 additions & 10 deletions packages/engine/Source/Shaders/Voxels/IntersectionUtils.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,6 @@

#define NO_HIT (-czm_infinity)
#define INF_HIT (czm_infinity * 0.5)
#define RAY_SHIFT (0.000003163)
#define RAY_SCALE (1.003163)

struct Ray {
vec3 pos;
vec3 dir;
#if defined(SHAPE_BOX)
vec3 dInv;
#endif
};

struct RayShapeIntersection {
vec4 entry;
Expand Down
4 changes: 2 additions & 2 deletions packages/engine/Source/Shaders/Voxels/Octree.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ int getOctreeParentIndex(in int octreeIndex) {
*/
vec3 getTileUv(in vec3 shapePosition, in ivec4 octreeCoords) {
// PERFORMANCE_IDEA: use bit-shifting (only in WebGL2)
float dimAtLevel = pow(2.0, float(octreeCoords.w));
float dimAtLevel = exp2(float(octreeCoords.w));
return shapePosition * dimAtLevel - vec3(octreeCoords.xyz);
}

Expand Down Expand Up @@ -126,7 +126,7 @@ void getOctreeLeafSampleDatas(in OctreeNodeData data, in ivec4 octreeCoords, out
#endif

OctreeNodeData traverseOctreeDownwards(in vec3 shapePosition, inout TraversalData traversalData) {
float sizeAtLevel = 1.0 / pow(2.0, float(traversalData.octreeCoords.w));
float sizeAtLevel = exp2(-1.0 * float(traversalData.octreeCoords.w));
vec3 start = vec3(traversalData.octreeCoords.xyz) * sizeAtLevel;
vec3 end = start + vec3(sizeAtLevel);
OctreeNodeData childData;
Expand Down
Loading
Loading