diff --git a/CHANGES.md b/CHANGES.md index e3c3dfd5bf01..f3914708767f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,10 @@ - Fixes issue with `BingMapsImageryProvider` where given culture option is ineffective [#11695](https://github.com/CesiumGS/cesium/issues/11695) - Fixed a bug where dynamic geometries caused the Scene to continuously render when running in requestRenderMode [#6631](https://github.com/CesiumGS/cesium/issues/6631) +##### Additions :tada: + +- Surface normals are now computed for clipping and shape bounds in VoxelEllipsoidShape and VoxelCylinderShape. [#11847](https://github.com/CesiumGS/cesium/pull/11847) + ### 1.115 - 2024-03-01 #### @cesium/engine diff --git a/packages/engine/Source/Scene/VoxelCylinderShape.js b/packages/engine/Source/Scene/VoxelCylinderShape.js index 44fd80315036..4fd5395848ca 100644 --- a/packages/engine/Source/Scene/VoxelCylinderShape.js +++ b/packages/engine/Source/Scene/VoxelCylinderShape.js @@ -96,9 +96,8 @@ function VoxelCylinderShape() { * @readonly */ this.shaderUniforms = { - cylinderUvToRenderBoundsScale: new Cartesian3(), - cylinderUvToRenderBoundsTranslate: new Cartesian3(), - cylinderUvToRenderRadiusMin: 0.0, + cylinderRenderHeightMinMax: new Cartesian2(), + cylinderRenderRadiusMinMax: new Cartesian2(), cylinderRenderAngleMinMax: new Cartesian2(), cylinderUvToShapeUvRadius: new Cartesian2(), cylinderUvToShapeUvHeight: new Cartesian2(), @@ -113,10 +112,7 @@ function VoxelCylinderShape() { */ this.shaderDefines = { CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined, - CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX: undefined, CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined, - CYLINDER_HAS_RENDER_BOUNDS_HEIGHT: undefined, - CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT: undefined, CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined, CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined, CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined, @@ -143,17 +139,6 @@ function VoxelCylinderShape() { } const scratchScale = new Cartesian3(); -const scratchBoundsTranslation = new Cartesian3(); -const scratchBoundsScale = new Cartesian3(); -const scratchBoundsScaleMatrix = new Matrix3(); -const scratchTransformLocalToBounds = new Matrix4(); -const scratchTransformUvToBounds = new Matrix4(); - -const transformUvToLocal = Matrix4.fromRotationTranslation( - Matrix3.fromUniformScale(2.0, new Matrix3()), - new Cartesian3(-1.0, -1.0, -1.0), - new Matrix4() -); /** * Update the shape's state. @@ -339,11 +324,7 @@ VoxelCylinderShape.prototype.update = function ( epsilonAngleDiscontinuity ); - const renderIsDefaultMaxRadius = renderMaxRadius === defaultMaxRadius; const renderIsDefaultMinRadius = renderMinRadius === defaultMinRadius; - const renderIsDefaultHeight = - renderMinHeight === defaultMinHeight && - renderMaxHeight === defaultMaxHeight; const renderIsAngleReversed = renderMaxAngle < renderMinAngle; const renderAngleRange = renderMaxAngle - renderMinAngle + renderIsAngleReversed * defaultAngleRange; @@ -376,22 +357,16 @@ VoxelCylinderShape.prototype.update = function ( shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true; shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount; intersectionCount += 1; - - shaderUniforms.cylinderUvToRenderRadiusMin = - renderMaxRadius / renderMinRadius; - } - if (!renderIsDefaultMaxRadius) { - shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX"] = true; } + shaderUniforms.cylinderRenderRadiusMinMax = Cartesian2.fromElements( + renderMinRadius, + renderMaxRadius, + shaderUniforms.cylinderRenderRadiusMinMax + ); + if (renderMinRadius === renderMaxRadius) { shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true; } - if (!renderIsDefaultHeight) { - shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT"] = true; - } - if (renderMinHeight === renderMaxHeight) { - shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT"] = true; - } if (!shapeIsDefaultRadius) { shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS"] = true; @@ -442,42 +417,11 @@ VoxelCylinderShape.prototype.update = function ( shaderUniforms.cylinderUvToShapeUvHeight ); } - - if (!renderIsDefaultMaxRadius || !renderIsDefaultHeight) { - const heightScale = 0.5 * (renderMaxHeight - renderMinHeight); - const scaleLocalToBounds = Cartesian3.fromElements( - 1.0 / renderMaxRadius, - 1.0 / renderMaxRadius, - 1.0 / (heightScale === 0.0 ? 1.0 : heightScale), - scratchBoundsScale - ); - // -inverse(scale) * translation // affine inverse - // -inverse(scale) * 0.5 * (minHeight + maxHeight) - const translateLocalToBounds = Cartesian3.fromElements( - 0.0, - 0.0, - -scaleLocalToBounds.z * 0.5 * (renderMinHeight + renderMaxHeight), - scratchBoundsTranslation - ); - const transformLocalToBounds = Matrix4.fromRotationTranslation( - Matrix3.fromScale(scaleLocalToBounds, scratchBoundsScaleMatrix), - translateLocalToBounds, - scratchTransformLocalToBounds - ); - const transformUvToBounds = Matrix4.multiplyTransformation( - transformLocalToBounds, - transformUvToLocal, - scratchTransformUvToBounds - ); - shaderUniforms.cylinderUvToRenderBoundsScale = Matrix4.getScale( - transformUvToBounds, - shaderUniforms.cylinderUvToRenderBoundsScale - ); - shaderUniforms.cylinderUvToRenderBoundsTranslate = Matrix4.getTranslation( - transformUvToBounds, - shaderUniforms.cylinderUvToRenderBoundsTranslate - ); - } + shaderUniforms.cylinderRenderHeightMinMax = Cartesian2.fromElements( + renderMinHeight, + renderMaxHeight, + shaderUniforms.cylinderRenderHeightMinMax + ); if (shapeIsAngleReversed) { shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED"] = true; @@ -501,7 +445,7 @@ VoxelCylinderShape.prototype.update = function ( shaderUniforms.cylinderRenderAngleMinMax = Cartesian2.fromElements( renderMinAngle, renderMaxAngle, - shaderUniforms.cylinderAngleMinMax + shaderUniforms.cylinderRenderAngleMinMax ); } diff --git a/packages/engine/Source/Scene/VoxelEllipsoidShape.js b/packages/engine/Source/Scene/VoxelEllipsoidShape.js index 6feee8480519..d80c282fb0fe 100644 --- a/packages/engine/Source/Scene/VoxelEllipsoidShape.js +++ b/packages/engine/Source/Scene/VoxelEllipsoidShape.js @@ -100,11 +100,10 @@ function VoxelEllipsoidShape() { ellipsoidShapeUvLongitudeMinMaxMid: new Cartesian3(), ellipsoidUvToShapeUvLongitude: new Cartesian2(), ellipsoidUvToShapeUvLatitude: new Cartesian2(), - ellipsoidRenderLatitudeCosSqrHalfMinMax: new Cartesian2(), + ellipsoidRenderLatitudeSinMinMax: new Cartesian2(), ellipsoidInverseHeightDifferenceUv: 0.0, ellipseInnerRadiiUv: new Cartesian2(), - ellipsoidInverseInnerScaleUv: 0.0, - ellipsoidInverseOuterScaleUv: 0.0, + clipMinMaxHeight: new Cartesian2(), }; /** @@ -127,8 +126,6 @@ function VoxelEllipsoidShape() { ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF: undefined, ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF: undefined, ELLIPSOID_HAS_SHAPE_BOUNDS_LATITUDE: undefined, - ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MAX: undefined, - ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN: undefined, ELLIPSOID_HAS_SHAPE_BOUNDS_HEIGHT_FLAT: undefined, ELLIPSOID_IS_SPHERE: undefined, ELLIPSOID_INTERSECTION_INDEX_LONGITUDE: undefined, @@ -150,7 +147,6 @@ const scratchScale = new Cartesian3(); const scratchRotationScale = new Matrix3(); const scratchShapeOuterExtent = new Cartesian3(); const scratchRenderOuterExtent = new Cartesian3(); -const scratchRenderInnerExtent = new Cartesian3(); const scratchRenderRectangle = new Rectangle(); /** @@ -270,16 +266,6 @@ VoxelEllipsoidShape.prototype.update = function ( ); const shapeMaxExtent = Cartesian3.maximumComponent(shapeOuterExtent); - const renderInnerExtent = Cartesian3.add( - radii, - Cartesian3.fromElements( - renderMinHeight, - renderMinHeight, - renderMinHeight, - scratchRenderInnerExtent - ), - scratchRenderInnerExtent - ); const renderOuterExtent = Cartesian3.add( radii, Cartesian3.fromElements( @@ -441,16 +427,6 @@ VoxelEllipsoidShape.prototype.update = function ( shapeIsLatitudeMinOverHalf; const shapeHasLatitude = shapeHasLatitudeMax || shapeHasLatitudeMin; - // Height - const renderHasMinHeight = !Cartesian3.equals( - renderInnerExtent, - Cartesian3.ZERO - ); - const renderHasMaxHeight = !Cartesian3.equals( - renderOuterExtent, - Cartesian3.ZERO - ); - const { shaderUniforms, shaderDefines } = this; // To keep things simple, clear the defines every time @@ -484,27 +460,14 @@ VoxelEllipsoidShape.prototype.update = function ( // Intersects an outer ellipsoid for the max height. shaderDefines["ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX"] = intersectionCount; intersectionCount += 1; + shaderDefines["ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN"] = intersectionCount; + intersectionCount += 1; - if (renderHasMinHeight) { - shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN"] = true; - shaderDefines[ - "ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN" - ] = intersectionCount; - intersectionCount += 1; - - // The inverse of the percent of space that is taken up by the inner ellipsoid, relative to the shape bounds - // 1.0 / (1.0 - thickness) // thickness = percent of space that is between the min and max height. - // 1.0 / (1.0 - (shapeMaxHeight - renderMinHeight) / shapeMaxExtent) - // shapeMaxExtent / (shapeMaxExtent - (shapeMaxHeight - renderMinHeight)) - shaderUniforms.ellipsoidInverseInnerScaleUv = - shapeMaxExtent / (shapeMaxExtent - (shapeMaxHeight - renderMinHeight)); - } - - if (renderHasMaxHeight) { - shaderDefines["ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MAX"] = true; - shaderUniforms.ellipsoidInverseOuterScaleUv = - shapeMaxExtent / (shapeMaxExtent - (shapeMaxHeight - renderMaxHeight)); - } + shaderUniforms.clipMinMaxHeight = Cartesian2.fromElements( + (renderMinHeight - shapeMaxHeight) / shapeMaxExtent, + (renderMaxHeight - shapeMaxHeight) / shapeMaxExtent, + shaderUniforms.clipMinMaxHeight + ); // The percent of space that is between the inner and outer ellipsoid. const thickness = (shapeMaxHeight - shapeMinHeight) / shapeMaxExtent; @@ -680,18 +643,12 @@ VoxelEllipsoidShape.prototype.update = function ( } } - const minCosHalfAngleSqr = Math.pow( - Math.cos(CesiumMath.PI_OVER_TWO - Math.abs(renderMinLatitude)), - 2.0 - ); - const maxCosHalfAngleSqr = Math.pow( - Math.cos(CesiumMath.PI_OVER_TWO - Math.abs(renderMaxLatitude)), - 2.0 - ); - shaderUniforms.ellipsoidRenderLatitudeCosSqrHalfMinMax = Cartesian2.fromElements( - minCosHalfAngleSqr, - maxCosHalfAngleSqr, - shaderUniforms.ellipsoidRenderLatitudeCosSqrHalfMinMax + const sinMinLatitude = Math.sin(renderMinLatitude); + const sinMaxLatitude = Math.sin(renderMaxLatitude); + shaderUniforms.ellipsoidRenderLatitudeSinMinMax = Cartesian2.fromElements( + sinMinLatitude, + sinMaxLatitude, + shaderUniforms.ellipsoidRenderLatitudeSinMinMax ); } diff --git a/packages/engine/Source/Scene/VoxelRenderResources.js b/packages/engine/Source/Scene/VoxelRenderResources.js index 53562a65e39e..ac9bb204f8c9 100644 --- a/packages/engine/Source/Scene/VoxelRenderResources.js +++ b/packages/engine/Source/Scene/VoxelRenderResources.js @@ -132,17 +132,17 @@ function VoxelRenderResources(primitive) { ]); } else if (shapeType === "CYLINDER") { shaderBuilder.addFragmentLines([ + convertUvToCylinder, IntersectLongitude, IntersectCylinder, Intersection, - convertUvToCylinder, ]); } else if (shapeType === "ELLIPSOID") { shaderBuilder.addFragmentLines([ + convertUvToEllipsoid, IntersectLongitude, IntersectEllipsoid, Intersection, - convertUvToEllipsoid, ]); } diff --git a/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl b/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl index 8f0de318348d..4a7369641e3f 100644 --- a/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl +++ b/packages/engine/Source/Shaders/Voxels/IntersectCylinder.glsl @@ -1,14 +1,12 @@ // See IntersectionUtils.glsl for the definitions of Ray, NO_HIT, -// setIntersection, setIntersectionPair, setShapeIntersection +// RayShapeIntersection, setSurfaceIntersection, setShapeIntersection, +// intersectIntersections // See IntersectLongitude.glsl for the definitions of intersectHalfPlane, // intersectFlippedWedge, intersectRegularWedge /* Cylinder defines (set in Scene/VoxelCylinderShape.js) #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN -#define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX #define CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT -#define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT -#define CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF #define CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF @@ -20,114 +18,87 @@ */ // Cylinder uniforms -#if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT) - uniform vec3 u_cylinderUvToRenderBoundsScale; - uniform vec3 u_cylinderUvToRenderBoundsTranslate; -#endif -#if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN) && !defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT) - uniform float u_cylinderUvToRenderRadiusMin; -#endif +uniform vec2 u_cylinderRenderRadiusMinMax; +uniform vec2 u_cylinderRenderHeightMinMax; #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE) uniform vec2 u_cylinderRenderAngleMinMax; #endif -vec2 intersectUnitCylinder(Ray ray) +/** + * Find the intersection of a ray with the volume defined by two planes of constant z + */ +RayShapeIntersection intersectHeightBounds(in Ray ray, in vec2 minMaxHeight, in bool convex) { - vec3 o = ray.pos; - vec3 d = ray.dir; + float zPosition = ray.pos.z; + float zDirection = ray.dir.z; - float a = dot(d.xy, d.xy); - float b = dot(o.xy, d.xy); - float c = dot(o.xy, o.xy) - 1.0; - float det = b * b - a * c; + float tmin = (minMaxHeight.x - zPosition) / zDirection; + float tmax = (minMaxHeight.y - zPosition) / zDirection; - if (det < 0.0) { - return vec2(NO_HIT); - } + // Normals point outside the volume + float signFlip = convex ? 1.0 : -1.0; + vec4 intersectMin = vec4(0.0, 0.0, -1.0 * signFlip, tmin); + vec4 intersectMax = vec4(0.0, 0.0, 1.0 * signFlip, tmax); - det = sqrt(det); - float ta = (-b - det) / a; - float tb = (-b + det) / a; - float t1 = min(ta, tb); - float t2 = max(ta, tb); + bool topEntry = zDirection < 0.0; + vec4 entry = topEntry ? intersectMax : intersectMin; + vec4 exit = topEntry ? intersectMin : intersectMax; - float z1 = o.z + t1 * d.z; - float z2 = o.z + t2 * d.z; - - if (abs(z1) >= 1.0) - { - float tCap = (sign(z1) - o.z) / d.z; - t1 = abs(b + a * tCap) < det ? tCap : NO_HIT; - } - - if (abs(z2) >= 1.0) - { - float tCap = (sign(z2) - o.z) / d.z; - t2 = abs(b + a * tCap) < det ? tCap : NO_HIT; - } - - return vec2(t1, t2); + return RayShapeIntersection(entry, exit); } -vec2 intersectUnitCircle(Ray ray) { - vec3 o = ray.pos; - vec3 d = ray.dir; +/** + * Find the intersection of a ray with a right cylindrical surface of a given radius + * about the z-axis. + */ +RayShapeIntersection intersectCylinder(in Ray ray, in float radius, in bool convex) +{ + vec2 position = ray.pos.xy; + vec2 direction = ray.dir.xy; - float t = -o.z / d.z; - vec2 zPlanePos = o.xy + d.xy * t; - float distSqr = dot(zPlanePos, zPlanePos); + float a = dot(direction, direction); + float b = dot(position, direction); + float c = dot(position, position) - radius * radius; + float determinant = b * b - a * c; - if (distSqr > 1.0) { - return vec2(NO_HIT); + if (determinant < 0.0) { + vec4 miss = vec4(normalize(ray.dir), NO_HIT); + return RayShapeIntersection(miss, miss); } - return vec2(t, t); + determinant = sqrt(determinant); + float t1 = (-b - determinant) / a; + float t2 = (-b + determinant) / a; + float signFlip = convex ? 1.0 : -1.0; + vec4 intersect1 = vec4(normalize(position + t1 * direction) * signFlip, 0.0, t1); + vec4 intersect2 = vec4(normalize(position + t2 * direction) * signFlip, 0.0, t2); + + return RayShapeIntersection(intersect1, intersect2); } -vec2 intersectInfiniteUnitCylinder(Ray ray) +/** + * Find the intersection of a ray with a right cylindrical solid of given + * radius and height bounds. NOTE: The shape is assumed to be convex. + */ +RayShapeIntersection intersectBoundedCylinder(in Ray ray, in float radius, in vec2 minMaxHeight) { - vec3 o = ray.pos; - vec3 d = ray.dir; - - float a = dot(d.xy, d.xy); - float b = dot(o.xy, d.xy); - float c = dot(o.xy, o.xy) - 1.0; - float det = b * b - a * c; - - if (det < 0.0) { - return vec2(NO_HIT); - } - - det = sqrt(det); - float t1 = (-b - det) / a; - float t2 = (-b + det) / a; - float tmin = min(t1, t2); - float tmax = max(t1, t2); - - return vec2(tmin, tmax); + RayShapeIntersection cylinderIntersection = intersectCylinder(ray, radius, true); + RayShapeIntersection heightBoundsIntersection = intersectHeightBounds(ray, minMaxHeight, true); + return intersectIntersections(ray, cylinderIntersection, heightBoundsIntersection); } void intersectShape(Ray ray, inout Intersections ix) { - #if defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX) || defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT) - ray.pos = ray.pos * u_cylinderUvToRenderBoundsScale + u_cylinderUvToRenderBoundsTranslate; - ray.dir *= u_cylinderUvToRenderBoundsScale; - #else - // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1]. - // Direction is scaled as well to be in sync with position. - ray.pos = ray.pos * 2.0 - 1.0; - ray.dir *= 2.0; - #endif + // Position is converted from [0,1] to [-1,+1] because shape intersections assume unit space is [-1,+1]. + // Direction is scaled as well to be in sync with position. + ray.pos = ray.pos * 2.0 - 1.0; + ray.dir *= 2.0; - #if defined(CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT) - vec2 outerIntersect = intersectUnitCircle(ray); - #else - vec2 outerIntersect = intersectUnitCylinder(ray); - #endif + RayShapeIntersection outerIntersect = intersectBoundedCylinder(ray, u_cylinderRenderRadiusMinMax.y, u_cylinderRenderHeightMinMax); - setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect); + setShapeIntersection(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MAX, outerIntersect); - if (outerIntersect.x == NO_HIT) { + if (outerIntersect.entry.w == NO_HIT) { return; } @@ -148,15 +119,14 @@ void intersectShape(Ray ray, inout Intersections ix) // Note: If initializeIntersections() changes its sorting function // from bubble sort to something else, this code may need to change. - vec2 innerIntersect = intersectInfiniteUnitCylinder(ray); - setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter - setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter - setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit - setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit + RayShapeIntersection innerIntersect = intersectCylinder(ray, 1.0, false); + setSurfaceIntersection(ix, 0, outerIntersect.entry, true, true); // positive, enter + setSurfaceIntersection(ix, 1, innerIntersect.entry, false, true); // negative, enter + setSurfaceIntersection(ix, 2, innerIntersect.exit, false, false); // negative, exit + setSurfaceIntersection(ix, 3, outerIntersect.exit, true, false); // positive, exit #elif defined(CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN) - Ray innerRay = Ray(ray.pos * u_cylinderUvToRenderRadiusMin, ray.dir * u_cylinderUvToRenderRadiusMin); - vec2 innerIntersect = intersectInfiniteUnitCylinder(innerRay); - setIntersectionPair(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MIN, innerIntersect); + RayShapeIntersection innerIntersect = intersectCylinder(ray, u_cylinderRenderRadiusMinMax.x, false); + setShapeIntersection(ix, CYLINDER_INTERSECTION_INDEX_RADIUS_MIN, innerIntersect); #endif #if defined(CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF) diff --git a/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl b/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl index 51336a91714b..f6e246deb4e9 100644 --- a/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl +++ b/packages/engine/Source/Shaders/Voxels/IntersectEllipsoid.glsl @@ -1,5 +1,5 @@ -// See IntersectionUtils.glsl for the definitions of Ray, Intersections, -// setIntersection, setIntersectionPair, INF_HIT, NO_HIT +// See IntersectionUtils.glsl for the definitions of Ray, NO_HIT, INF_HIT, Intersections, +// RayShapeIntersection, setSurfaceIntersection, setShapeIntersection // See IntersectLongitude.glsl for the definitions of intersectHalfPlane, // intersectFlippedWedge, intersectRegularWedge @@ -14,8 +14,6 @@ #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF #define ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF -#define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MAX -#define ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN #define ELLIPSOID_INTERSECTION_INDEX_LONGITUDE #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX #define ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN @@ -26,133 +24,199 @@ #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LONGITUDE) uniform vec2 u_ellipsoidRenderLongitudeMinMax; #endif -#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF) - uniform vec2 u_ellipsoidRenderLatitudeCosSqrHalfMinMax; -#endif -#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MAX) - uniform float u_ellipsoidInverseOuterScaleUv; -#endif -#if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN) - uniform float u_ellipsoidInverseInnerScaleUv; -#endif +uniform vec2 u_ellipsoidRenderLatitudeSinMinMax; +uniform vec2 u_clipMinMaxHeight; -vec2 intersectZPlane(Ray ray) -{ - float o = ray.pos.z; - float d = ray.dir.z; - float t = -o / d; - float s = sign(o); - - if (t >= 0.0 != s >= 0.0) return vec2(t, +INF_HIT); - else return vec2(-INF_HIT, t); -} +RayShapeIntersection intersectZPlane(in Ray ray, in float z) { + float t = -ray.pos.z / ray.dir.z; -vec2 intersectUnitSphere(Ray ray) -{ - vec3 o = ray.pos; - vec3 d = ray.dir; + bool startsOutside = sign(ray.pos.z) == sign(z); + bool entry = (t >= 0.0) != startsOutside; - float b = dot(d, o); - float c = dot(o, o) - 1.0; - float det = b * b - c; + vec4 intersect = vec4(0.0, 0.0, z, t); + vec4 farSide = vec4(normalize(ray.dir), INF_HIT); - if (det < 0.0) { - return vec2(NO_HIT); + if (entry) { + return RayShapeIntersection(intersect, farSide); + } else { + return RayShapeIntersection(-1.0 * farSide, intersect); } - - det = sqrt(det); - float t1 = -b - det; - float t2 = -b + det; - float tmin = min(t1, t2); - float tmax = max(t1, t2); - - return vec2(tmin, tmax); } -vec2 intersectUnitSphereUnnormalizedDirection(Ray ray) +RayShapeIntersection intersectHeight(in Ray ray, in float relativeHeight, in bool convex) { - vec3 o = ray.pos; - vec3 d = ray.dir; - - float a = dot(d, d); - float b = dot(d, o); - float c = dot(o, o) - 1.0; - float det = b * b - a * c; - - if (det < 0.0) { - return vec2(NO_HIT); + // Scale the ray by the ellipsoid axes to make it a unit sphere + // Note: approximating ellipsoid + height as an ellipsoid + vec3 radiiCorrection = u_ellipsoidRadiiUv / (u_ellipsoidRadiiUv + relativeHeight); + vec3 position = ray.pos * radiiCorrection; + vec3 direction = ray.dir * radiiCorrection; + + float a = dot(direction, direction); // ~ 1.0 (or maybe 4.0 if ray is scaled) + float b = dot(direction, position); // roughly inside [-1.0, 1.0] when zoomed in + float c = dot(position, position) - 1.0; // ~ 0.0 when zoomed in. + float determinant = b * b - a * c; // ~ b * b when zoomed in + + if (determinant < 0.0) { + vec4 miss = vec4(normalize(direction), NO_HIT); + return RayShapeIntersection(miss, miss); } - det = sqrt(det); - float t1 = (-b - det) / a; - float t2 = (-b + det) / a; + determinant = sqrt(determinant); + + // Compute larger root using standard formula + float signB = b < 0.0 ? -1.0 : 1.0; + // The other root may suffer from subtractive cancellation in the standard formula. + // Compute it from the first root instead. + float t1 = (-b - signB * determinant) / a; + float t2 = c / (a * t1); float tmin = min(t1, t2); float tmax = max(t1, t2); - return vec2(tmin, tmax); + float directionScale = convex ? 1.0 : -1.0; + vec3 d1 = directionScale * normalize(position + tmin * direction); + vec3 d2 = directionScale * normalize(position + tmax * direction); + + return RayShapeIntersection(vec4(d1, tmin), vec4(d2, tmax)); } -vec2 intersectDoubleEndedCone(Ray ray, float cosSqrHalfAngle) +/** + * Given a circular cone around the z-axis, with apex at the origin, + * find the parametric distance(s) along a ray where that ray intersects + * the cone. + * The cone opening angle is described by the squared cosine of + * its half-angle (the angle between the Z-axis and the surface) + */ +vec2 intersectDoubleEndedCone(in Ray ray, in float cosSqrHalfAngle) { vec3 o = ray.pos; vec3 d = ray.dir; - float a = d.z * d.z - dot(d, d) * cosSqrHalfAngle; - float b = d.z * o.z - dot(o, d) * cosSqrHalfAngle; - float c = o.z * o.z - dot(o, o) * cosSqrHalfAngle; - float det = b * b - a * c; + float sinSqrHalfAngle = 1.0 - cosSqrHalfAngle; + + float aSin = d.z * d.z * sinSqrHalfAngle; + float aCos = -dot(d.xy, d.xy) * cosSqrHalfAngle; + float a = aSin + aCos; - if (det < 0.0) { + float bSin = d.z * o.z * sinSqrHalfAngle; + float bCos = -dot(o.xy, d.xy) * cosSqrHalfAngle; + float b = bSin + bCos; + + float cSin = o.z * o.z * sinSqrHalfAngle; + float cCos = -dot(o.xy, o.xy) * cosSqrHalfAngle; + float c = cSin + cCos; + // determinant = b * b - a * c. But bSin * bSin = aSin * cSin. + // Avoid subtractive cancellation by expanding to eliminate these terms + float determinant = 2.0 * bSin * bCos + bCos * bCos - aSin * cCos - aCos * cSin - aCos * cCos; + + if (determinant < 0.0) { return vec2(NO_HIT); + } else if (a == 0.0) { + // Ray is parallel to cone surface + return (b == 0.0) + ? vec2(NO_HIT) // Ray is on cone surface + : vec2(-0.5 * c / b, NO_HIT); } - det = sqrt(det); - float t1 = (-b - det) / a; - float t2 = (-b + det) / a; + determinant = sqrt(determinant); + + // Compute larger root using standard formula + float signB = b < 0.0 ? -1.0 : 1.0; + float t1 = (-b - signB * determinant) / a; + // The other root may suffer from subtractive cancellation in the standard formula. + // Compute it from the first root instead. + float t2 = c / (a * t1); float tmin = min(t1, t2); float tmax = max(t1, t2); return vec2(tmin, tmax); } -vec4 intersectFlippedCone(Ray ray, float cosSqrHalfAngle) { +/** + * Given a point on a conical surface, find the surface normal at that point. + */ +vec3 getConeNormal(in vec3 p, in bool convex) { + // Start with radial component pointing toward z-axis + vec2 radial = -abs(p.z) * normalize(p.xy); + // Z component points toward opening of cone + float zSign = (p.z < 0.0) ? -1.0 : 1.0; + float z = length(p.xy) * zSign; + // Flip normal if shape is convex + float flip = (convex) ? -1.0 : 1.0; + return normalize(vec3(radial, z) * flip); +} + +void intersectFlippedCone(in Ray ray, in float cosHalfAngle, out RayShapeIntersection intersections[2]) { + float cosSqrHalfAngle = cosHalfAngle * cosHalfAngle; vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle); + vec4 miss = vec4(normalize(ray.dir), NO_HIT); + vec4 farSide = vec4(normalize(ray.dir), INF_HIT); + + // Initialize output with no intersections + intersections[0].entry = -1.0 * farSide; + intersections[0].exit = farSide; + intersections[1].entry = miss; + intersections[1].exit = miss; + if (intersect.x == NO_HIT) { - return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT); + return; } - vec3 o = ray.pos; - vec3 d = ray.dir; + // Find the points of intersection float tmin = intersect.x; float tmax = intersect.y; - float zmin = o.z + tmin * d.z; - float zmax = o.z + tmax * d.z; - - // One interval - if (zmin < 0.0 && zmax < 0.0) return vec4(-INF_HIT, +INF_HIT, NO_HIT, NO_HIT); - else if (zmin < 0.0) return vec4(-INF_HIT, tmax, NO_HIT, NO_HIT); - else if (zmax < 0.0) return vec4(tmin, +INF_HIT, NO_HIT, NO_HIT); - // Two intervals - else return vec4(-INF_HIT, tmin, tmax, +INF_HIT); + vec3 p0 = ray.pos + tmin * ray.dir; + vec3 p1 = ray.pos + tmax * ray.dir; + + vec4 intersect0 = vec4(getConeNormal(p0, true), tmin); + vec4 intersect1 = vec4(getConeNormal(p1, true), tmax); + + bool p0InShadowCone = sign(p0.z) != sign(cosHalfAngle); + bool p1InShadowCone = sign(p1.z) != sign(cosHalfAngle); + + if (p0InShadowCone && p1InShadowCone) { + // no valid intersections + } else if (p0InShadowCone) { + intersections[0].exit = intersect1; + } else if (p1InShadowCone) { + intersections[0].entry = intersect0; + } else { + intersections[0].exit = intersect0; + intersections[1].entry = intersect1; + intersections[1].exit = farSide; + } } -vec2 intersectRegularCone(Ray ray, float cosSqrHalfAngle) { +RayShapeIntersection intersectRegularCone(in Ray ray, in float cosHalfAngle, in bool convex) { + float cosSqrHalfAngle = cosHalfAngle * cosHalfAngle; vec2 intersect = intersectDoubleEndedCone(ray, cosSqrHalfAngle); + vec4 miss = vec4(normalize(ray.dir), NO_HIT); + vec4 farSide = vec4(normalize(ray.dir), INF_HIT); + if (intersect.x == NO_HIT) { - return vec2(NO_HIT); + return RayShapeIntersection(miss, miss); } - vec3 o = ray.pos; - vec3 d = ray.dir; + // Find the points of intersection float tmin = intersect.x; float tmax = intersect.y; - float zmin = o.z + tmin * d.z; - float zmax = o.z + tmax * d.z; - - if (zmin < 0.0 && zmax < 0.0) return vec2(NO_HIT); - else if (zmin < 0.0) return vec2(tmax, +INF_HIT); - else if (zmax < 0.0) return vec2(-INF_HIT, tmin); - else return vec2(tmin, tmax); + vec3 p0 = ray.pos + tmin * ray.dir; + vec3 p1 = ray.pos + tmax * ray.dir; + + vec4 intersect0 = vec4(getConeNormal(p0, convex), tmin); + vec4 intersect1 = vec4(getConeNormal(p1, convex), tmax); + + bool p0InShadowCone = sign(p0.z) != sign(cosHalfAngle); + bool p1InShadowCone = sign(p1.z) != sign(cosHalfAngle); + + if (p0InShadowCone && p1InShadowCone) { + return RayShapeIntersection(miss, miss); + } else if (p0InShadowCone) { + return RayShapeIntersection(intersect1, farSide); + } else if (p1InShadowCone) { + return RayShapeIntersection(-1.0 * farSide, intersect0); + } else { + return RayShapeIntersection(intersect0, intersect1); + } } void intersectShape(in Ray ray, inout Intersections ix) { @@ -161,91 +225,73 @@ void intersectShape(in Ray ray, inout Intersections ix) { ray.pos = ray.pos * 2.0 - 1.0; ray.dir *= 2.0; - #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MAX) - Ray outerRay = Ray(ray.pos * u_ellipsoidInverseOuterScaleUv, ray.dir * u_ellipsoidInverseOuterScaleUv); - #else - Ray outerRay = ray; - #endif - // Outer ellipsoid - vec2 outerIntersect = intersectUnitSphereUnnormalizedDirection(outerRay); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect); + RayShapeIntersection outerIntersect = intersectHeight(ray, u_clipMinMaxHeight.y, true); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MAX, outerIntersect); // Exit early if the outer ellipsoid was missed. - if (outerIntersect.x == NO_HIT) { + if (outerIntersect.entry.w == NO_HIT) { return; } // Inner ellipsoid - #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_HEIGHT_MIN) - Ray innerRay = Ray(ray.pos * u_ellipsoidInverseInnerScaleUv, ray.dir * u_ellipsoidInverseInnerScaleUv); - vec2 innerIntersect = intersectUnitSphereUnnormalizedDirection(innerRay); - - if (innerIntersect == vec2(NO_HIT)) { - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN, innerIntersect); - } else { - // When the ellipsoid is large and thin it's possible for floating point math - // to cause the ray to intersect the inner ellipsoid before the outer ellipsoid. - // To prevent this from happening, clamp innerIntersect to outerIntersect and - // sandwich the inner ellipsoid intersection inside the outer ellipsoid intersection. - - // Without this special case, - // [outerMin, outerMax, innerMin, innerMax] will bubble sort to - // [outerMin, innerMin, outerMax, innerMax] which will cause the back - // side of the ellipsoid to be invisible because it will think the ray - // is still inside the inner (negative) ellipsoid after exiting the - // outer (positive) ellipsoid. - - // With this special case, - // [outerMin, innerMin, innerMax, outerMax] will bubble sort to - // [outerMin, innerMin, innerMax, outerMax] which will work correctly. - - // Note: If initializeIntersections() changes its sorting function - // from bubble sort to something else, this code may need to change. - - // In theory a similar fix is needed for cylinders, however it's more - // complicated to implement because the inner shape is allowed to be - // intersected first. - innerIntersect.x = max(innerIntersect.x, outerIntersect.x); - innerIntersect.y = min(innerIntersect.y, outerIntersect.y); - setIntersection(ix, 0, outerIntersect.x, true, true); // positive, enter - setIntersection(ix, 1, innerIntersect.x, false, true); // negative, enter - setIntersection(ix, 2, innerIntersect.y, false, false); // negative, exit - setIntersection(ix, 3, outerIntersect.y, true, false); // positive, exit - } - #endif - - // Flip the ray because the intersection function expects a cone growing towards +Z. - #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) || defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) - Ray flippedRay = outerRay; - flippedRay.dir.z *= -1.0; - flippedRay.pos.z *= -1.0; - #endif + RayShapeIntersection innerIntersect = intersectHeight(ray, u_clipMinMaxHeight.x, false); + + if (innerIntersect.entry.w == NO_HIT) { + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_HEIGHT_MIN, innerIntersect); + } else { + // When the ellipsoid is large and thin it's possible for floating point math + // to cause the ray to intersect the inner ellipsoid before the outer ellipsoid. + // To prevent this from happening, clamp innerIntersect to outerIntersect and + // sandwich the inner ellipsoid intersection inside the outer ellipsoid intersection. + + // Without this special case, + // [outerMin, outerMax, innerMin, innerMax] will bubble sort to + // [outerMin, innerMin, outerMax, innerMax] which will cause the back + // side of the ellipsoid to be invisible because it will think the ray + // is still inside the inner (negative) ellipsoid after exiting the + // outer (positive) ellipsoid. + + // With this special case, + // [outerMin, innerMin, innerMax, outerMax] will bubble sort to + // [outerMin, innerMin, innerMax, outerMax] which will work correctly. + + // Note: If initializeIntersections() changes its sorting function + // from bubble sort to something else, this code may need to change. + innerIntersect.entry.w = max(innerIntersect.entry.w, outerIntersect.entry.w); + innerIntersect.exit.w = min(innerIntersect.exit.w, outerIntersect.exit.w); + setSurfaceIntersection(ix, 0, outerIntersect.entry, true, true); // positive, enter + setSurfaceIntersection(ix, 1, innerIntersect.entry, false, true); // negative, enter + setSurfaceIntersection(ix, 2, innerIntersect.exit, false, false); // negative, exit + setSurfaceIntersection(ix, 3, outerIntersect.exit, true, false); // positive, exit + } // Bottom cone #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_UNDER_HALF) - vec2 bottomConeIntersection = intersectRegularCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection); + RayShapeIntersection bottomConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeSinMinMax.x, false); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection); #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_EQUAL_HALF) - vec2 bottomConeIntersection = intersectZPlane(flippedRay); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection); + RayShapeIntersection bottomConeIntersection = intersectZPlane(ray, -1.0); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN, bottomConeIntersection); #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MIN_OVER_HALF) - vec4 bottomConeIntersection = intersectFlippedCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.x); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 0, bottomConeIntersection.xy); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 1, bottomConeIntersection.zw); + RayShapeIntersection bottomConeIntersections[2]; + intersectFlippedCone(ray, u_ellipsoidRenderLatitudeSinMinMax.x, bottomConeIntersections); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 0, bottomConeIntersections[0]); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MIN + 1, bottomConeIntersections[1]); #endif // Top cone #if defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_UNDER_HALF) - vec4 topConeIntersection = intersectFlippedCone(flippedRay, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 0, topConeIntersection.xy); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 1, topConeIntersection.zw); + RayShapeIntersection topConeIntersections[2]; + intersectFlippedCone(ray, u_ellipsoidRenderLatitudeSinMinMax.y, topConeIntersections); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 0, topConeIntersections[0]); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX + 1, topConeIntersections[1]); #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_EQUAL_HALF) - vec2 topConeIntersection = intersectZPlane(ray); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection); + RayShapeIntersection topConeIntersection = intersectZPlane(ray, 1.0); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection); #elif defined(ELLIPSOID_HAS_RENDER_BOUNDS_LATITUDE_MAX_OVER_HALF) - vec2 topConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeCosSqrHalfMinMax.y); - setIntersectionPair(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection); + RayShapeIntersection topConeIntersection = intersectRegularCone(ray, u_ellipsoidRenderLatitudeSinMinMax.y, false); + setShapeIntersection(ix, ELLIPSOID_INTERSECTION_INDEX_LATITUDE_MAX, topConeIntersection); #endif // Wedge diff --git a/packages/engine/Source/Shaders/Voxels/IntersectionUtils.glsl b/packages/engine/Source/Shaders/Voxels/IntersectionUtils.glsl index c10465ec0bf5..00a6112dc60d 100644 --- a/packages/engine/Source/Shaders/Voxels/IntersectionUtils.glsl +++ b/packages/engine/Source/Shaders/Voxels/IntersectionUtils.glsl @@ -20,6 +20,38 @@ struct RayShapeIntersection { vec4 exit; }; +vec4 intersectionMin(in vec4 intersect0, in vec4 intersect1) +{ + if (intersect0.w == NO_HIT) { + return intersect1; + } else if (intersect1.w == NO_HIT) { + return intersect0; + } + return (intersect0.w <= intersect1.w) ? intersect0 : intersect1; +} + +vec4 intersectionMax(in vec4 intersect0, in vec4 intersect1) +{ + return (intersect0.w >= intersect1.w) ? intersect0 : intersect1; +} + +RayShapeIntersection intersectIntersections(in Ray ray, in RayShapeIntersection intersect0, in RayShapeIntersection intersect1) +{ + bool missed = (intersect0.entry.w == NO_HIT) || + (intersect1.entry.w == NO_HIT) || + (intersect0.exit.w < intersect1.entry.w) || + (intersect0.entry.w > intersect1.exit.w); + if (missed) { + vec4 miss = vec4(normalize(ray.dir), NO_HIT); + return RayShapeIntersection(miss, miss); + } + + vec4 entry = intersectionMax(intersect0.entry, intersect1.entry); + vec4 exit = intersectionMin(intersect0.exit, intersect1.exit); + + return RayShapeIntersection(entry, exit); +} + struct Intersections { // Don't access these member variables directly - call the functions instead. @@ -57,7 +89,7 @@ vec4 encodeIntersectionType(vec4 intersection, int index, bool entry) // Use defines instead of real functions because WebGL1 cannot access array with non-constant index. #define setIntersection(/*inout Intersections*/ ix, /*int*/ index, /*float*/ t, /*bool*/ positive, /*bool*/ enter) (ix).intersections[(index)] = vec4(0.0, float(!positive) * 2.0 + float(!enter) + 1.0, 0.0, (t)) #define setIntersectionPair(/*inout Intersections*/ ix, /*int*/ index, /*vec2*/ entryExit) (ix).intersections[(index) * 2 + 0] = vec4(0.0, float((index) > 0) * 2.0 + 1.0, 0.0, (entryExit).x); (ix).intersections[(index) * 2 + 1] = vec4(0.0, float((index) > 0) * 2.0 + 2.0, 0.0, (entryExit).y) -#define setSurfaceIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec4*/ intersection) (ix).intersections[(index)] = intersection; +#define setSurfaceIntersection(/*inout Intersections*/ ix, /*int*/ index, /*vec4*/ intersection, /*bool*/ positive, /*bool*/ enter) (ix).intersections[(index)] = encodeIntersectionType((intersection), int(!positive), (enter)) #define setShapeIntersection(/*inout Intersections*/ ix, /*int*/ index, /*RayShapeIntersection*/ intersection) (ix).intersections[(index) * 2 + 0] = encodeIntersectionType((intersection).entry, (index), true); (ix).intersections[(index) * 2 + 1] = encodeIntersectionType((intersection).exit, (index), false) #if (INTERSECTION_COUNT > 1) diff --git a/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl b/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl index 495a746ae4ba..5960de4874e0 100644 --- a/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl +++ b/packages/engine/Source/Shaders/Voxels/VoxelFS.glsl @@ -32,17 +32,20 @@ float hash(vec2 p) } #endif -vec4 getStepSize(in SampleData sampleData, in Ray viewRay, in RayShapeIntersection shapeIntersection) { +vec4 getStepSize(in SampleData sampleData, in Ray viewRay, in RayShapeIntersection shapeIntersection, in float currentT) { #if defined(SHAPE_BOX) Box voxelBox = constructVoxelBox(sampleData.tileCoords, sampleData.tileUv); RayShapeIntersection voxelIntersection = intersectBox(viewRay, voxelBox); - vec4 entry = shapeIntersection.entry.w >= voxelIntersection.entry.w ? shapeIntersection.entry : voxelIntersection.entry; + vec4 entry = intersectionMax(shapeIntersection.entry, voxelIntersection.entry); float exit = min(voxelIntersection.exit.w, shapeIntersection.exit.w); float dt = (exit - entry.w) * RAY_SCALE; return vec4(normalize(entry.xyz), dt); #else float dimAtLevel = pow(2.0, float(sampleData.tileCoords.w)); - return vec4(viewRay.dir, u_stepSize / dimAtLevel); + float shapeEntryT = shapeIntersection.entry.w * RAY_SCALE; + float constantStep = u_stepSize / dimAtLevel; + vec3 normal = (currentT < shapeEntryT + constantStep) ? shapeIntersection.entry.xyz : viewRay.dir; + return vec4(normalize(normal), constantStep); #endif } @@ -93,20 +96,20 @@ void main() discard; } - float currT = shapeIntersection.entry.w * RAY_SCALE; + float currentT = shapeIntersection.entry.w * RAY_SCALE; float endT = shapeIntersection.exit.w; - vec3 positionUv = viewPosUv + currT * viewDirUv; + vec3 positionUv = viewPosUv + currentT * viewDirUv; vec3 positionUvShapeSpace = convertUvToShapeUvSpace(positionUv); // Traverse the tree from the start position TraversalData traversalData; SampleData sampleDatas[SAMPLE_COUNT]; traverseOctreeFromBeginning(positionUvShapeSpace, traversalData, sampleDatas); - vec4 step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection); + vec4 step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, currentT); #if defined(JITTER) float noise = hash(screenCoord); // [0,1] - currT += noise * step.w; + currentT += noise * step.w; positionUv += noise * step.w * viewDirUv; #endif @@ -115,7 +118,7 @@ void main() setStatistics(fragmentInput.metadata.statistics); #endif - vec4 colorAccum =vec4(0.0); + vec4 colorAccum = vec4(0.0); for (int stepCount = 0; stepCount < STEP_COUNT_MAX; ++stepCount) { // Read properties from the megatexture based on the traversal state @@ -158,11 +161,11 @@ void main() } // Keep raymarching - currT += step.w; + currentT += step.w; positionUv += step.w * viewDirUv; // Check if there's more intersections. - if (currT > endT) { + if (currentT > endT) { #if (INTERSECTION_COUNT == 1) break; #else @@ -171,9 +174,9 @@ void main() break; } else { // Found another intersection. Resume raymarching there - currT = shapeIntersection.entry.w * RAY_SCALE; + currentT = shapeIntersection.entry.w * RAY_SCALE; endT = shapeIntersection.exit.w; - positionUv = viewPosUv + currT * viewDirUv; + positionUv = viewPosUv + currentT * viewDirUv; } #endif } @@ -182,7 +185,7 @@ void main() // This is similar to traverseOctreeFromBeginning but is faster when the ray is in the same tile as the previous step. positionUvShapeSpace = convertUvToShapeUvSpace(positionUv); traverseOctreeFromExisting(positionUvShapeSpace, traversalData, sampleDatas); - step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection); + step = getStepSize(sampleDatas[0], viewRayUv, shapeIntersection, currentT); } // Convert the alpha from [0,ALPHA_ACCUM_MAX] to [0,1]