-
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
WIP: Add PolygonClipping support #8915
Conversation
Thanks for the pull request @Samulus!
Reviewers, don't forget to make sure that:
|
Source/DataSources/ModelGraphics.js
Outdated
/** | ||
* A property specifying the {@link ClippingPolygon} used to selectively disable rendering the model. | ||
* @memberof ModelGraphics.prototype | ||
* @type {Property} |
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.
FYI, should be @type {Property|undefined}
basically any property that can be undefined has to have the |undefined
for strict mode TypeScript to work.
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.
fixed
@@ -3,6 +3,7 @@ import PolygonClippingAccelerationGrid from "../../Source/Scene/PolygonClippingA | |||
describe("Scene/PolygonClippingAcceleration", function () { | |||
it("developer error if invalid number of positions or indicies provided", function () { | |||
expect(function () { | |||
// eslint-disable-next-line no-unused-vars | |||
var empty_arrays = new PolygonClippingAccelerationGrid({ |
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 is fine, but as a tip, you can also just do return
instead of var empty_arrays =
and the test will do what you want without worrying about eslint or local vars.
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.
Also, empty_arrays
should probably be emptyArrays
. We use camel case for variable names.
We talked about precision problems for large scale clipping polygons offline already, but to demonstrate it I modified the sandcastle to draw the polygons and set the camera to look at the coast of Hawaii. The polygon and clipped globe don't perfectly match up (ignoring the height difference which is expected) and there's some jittering on the edges. |
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.
Generally looks pretty good. I'm starting to lose steam but I'll review the algorithm and shader in more detail later, and the unit tests.
Cesium.when(entity.readyPromise).then(function () { | ||
entity.model.clippingPolygon = new Cesium.ClippingPolygon.fromPolygonHierarchies( | ||
{ | ||
polygonHierarchies: [clipLeftWing, clipRightWing], | ||
splits: 30, | ||
union: false, | ||
} | ||
); | ||
}); |
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.
Entities don't have a ready promise. It should be fine to set the clippingPolygon
and not wrap in a promise.
agiHQ.readyPromise.then(function () { | ||
agiHQ.clippingPolygon = Cesium.ClippingPolygon.fromPolygonHierarchies( | ||
{ | ||
polygonHierarchies: [buildingA], | ||
union: true, | ||
splits: 127, | ||
} | ||
); | ||
}); |
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.
Unlike the previous comment, tilesets do have ready promises, but I don't think you need to wait on the ready promise before setting the clipping polygon
}); | ||
|
||
Cesium.when(entity.readyPromise).then(function () { | ||
entity.model.clippingPolygon = new Cesium.ClippingPolygon.fromPolygonHierarchies( |
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.
entity.model.clippingPolygon = new Cesium.ClippingPolygon.fromPolygonHierarchies( | |
entity.model.clippingPolygon = Cesium.ClippingPolygon.fromPolygonHierarchies( |
} | ||
} | ||
|
||
globe.clippingPolygon = new Cesium.ClippingPolygon.fromPolygonHierarchies( |
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.
globe.clippingPolygon = new Cesium.ClippingPolygon.fromPolygonHierarchies( | |
globe.clippingPolygon = Cesium.ClippingPolygon.fromPolygonHierarchies( |
CHANGES.md
Outdated
@@ -1,5 +1,9 @@ | |||
# Change Log | |||
|
|||
### 1.71.1 | |||
|
|||
- Add `ClippingPolygon` support. |
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.
Add this to 1.72.0
instead.
} | ||
} | ||
|
||
this.overlappingTriangleIndices = Float32Array.from(allOverlappingTriangles); |
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.
Same comment about Float32Array.from
not supported in IE11
py | ||
) { | ||
if (!pointInsideRect(this.boundingBox, px, py)) { | ||
return null; |
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.
Same comment about preferring undefined
this.overlappingTriangleIndices = Float32Array.from(allOverlappingTriangles); | ||
} | ||
|
||
PolygonClippingAccelerationGrid.prototype.getCellFromWorldPosition = function ( |
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.
Does this only exist for testing purposes? If so, just mark is as such. Usually we do that by adding an underscore to the function name and add a comment. PolygonClippingAccelerationGrid.prototype._getCellFromWorldPosition
" uniform vec3 u_clippingPolygonBoundingBox[4];\n" + | ||
" uniform vec2 u_clippingPolygonCellDimensions;\n" + | ||
" uniform sampler2D u_clippingPolygonAccelerationGrid;\n" + | ||
" uniform sampler2D u_clippingPolygonMeshPositions;\n" + | ||
" uniform sampler2D u_clippingPolygonOverlappingTriangleIndices;\n" + | ||
" uniform vec2 u_clippingPolygonAccelerationGridPixelDimensions;\n" + | ||
" uniform vec2 u_clippingPolygonOverlappingTrianglePixelIndicesDimensions;\n" + | ||
" uniform vec2 u_clippingPolygonMeshPositionPixelDimensions;\n" + | ||
" uniform mat4 u_clippingPolygonEyeToWorldToENU;\n" + | ||
" uniform float u_clippingPolygonMinimumZ;\n" + |
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.
Try to pack uniforms wherever possible since gl.uniform
related functions happen to be a huge expense if you look at the profiler for any CeisumJS app. Looks like some of the vec2
's can be packed at least.
|
||
function getClippingPolygonFunction(union) { | ||
return ( | ||
" uniform vec3 u_clippingPolygonBoundingBox[4];\n" + |
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.
Can these be vec2
instead? And can they be packed into two vec4
?
Follow Up ImprovementsTwo of the biggest issues with this PR right now is that it it uses a single ENU transform for all geometry, and cannot handle antipodeal rendering (it's ambiguous to the fragment shader which triangle should be used to clip if you have two triangles on opposite sides of the globe). One strategy discussed was to use a secondary global grid to partition the earth into different global cells. Each global cell would have its own ENU matrix for a significantly smaller region (e.g. each cell represents clipping triangles within a 1.1km region). Triangles in the amalgamated clipping mesh could then be placed into each of these global cells (triangles that span across multiple cells would have to be split into smaller triangles). We would also have to record if the clipping triangle belongs to the nothern / southern hemisphere and encode that in the data texture the fragment shader reads directly too, so it can disambiguate which triangle to test. Finally, the data should be encoded in a way that the initial texture lookup allows the fragment shader to be able to look up with a O(1) texture fetch:
|
Thanks again for your contribution @Samulus! No one has commented on this pull request in 30 days. Maintainers, can you review, merge or close to keep things tidy? I'm going to re-bump this in 30 days. If you'd like me to stop, just comment with |
@cesium-concierge stop |
Source/Scene/Model.js
Outdated
var newMain = | ||
"void main() {\n" + | ||
"vec4 positionEC = czm_windowToEyeCoordinates(gl_FragCoord);\n" + | ||
"positionEC /= positionEC.w;\n" + |
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.
Completely by chance, I found that removing this line removes the fuzziness that @lilleyse and myself were seeing when using logarithmic depth buffers with tilesets at certain distances.
I'm not entirely sure why, but perhaps it's related to the things that get done to the Z in the vertex shader before log depth is written here in the fragment shader.
This may make the TODO item above of avoiding clip space unnecessary.
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.
Huh, and no side effects removing this line? Does it still work if log depth is disabled?
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.
So there weren't problems with the fuzziness issue with and without log depth, but it did break the Sandcastle example in this PR... the mysteries deepen. I'll keep investigating.
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.
Apologies for butting in, but could you briefly explain the reason for dividing the position by w
in the first place? I'm not new to shaders, but this isn't clicking.
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.
@midnight-dev I'm not actually sure, it looks like other places where we use this particular overload of czm_windowToEyeCoordinates
(like for the earlier clipping planes shader code) we don't do this divide-by-w. the math in that code precedes my involvement in Cesium's clipping planes, although the history is a little murky. There's a post on Stackoverflow explaining that in gl_FragCoord
the w can help you linearize depth, and is computed from w
on gl_Position
in the vertex shader, so I suspect positionEC.w
serves a similar purpose. It's just we're already in the fragment shader, so there's maybe no need to actually divide by it?
but it did break the Sandcastle example in this PR... the mysteries deepen
@lilleyse I'm seeing for some reason that the proposed change here no longer breaks the shipping Sandcastle, which was kind of alarming.
I think I can chalk it up to some kind of browser cache thing, since I'm seeing that everything works fine in Firefox now as well.
Combined with the detail that we don't actually do this divide-by-w elsewhere in clipping code, I think it's safe to go ahead and apply this change, so imma do that.
… and for consistency with other clipping code
…t clipping planes do
fdd328a
to
3e93589
Compare
Talked offline with @Samulus - closing this for now but I'll leave the branch around since this needs a good amount of work before it's ready. |
Still a W.I.P, added some explanations:
Overview
This PR introduces a new faster alternative to clipping planes, the clipping polygon. The clipping polygon has the same visual effect as a clipping plane (selectively disabling / enabling rendering), but without the performance overhead of checking every pixel being rendered against every clipping plane.
The performance speed up is the result of using an acceleration data structure generated against the triangulated representation of the region to clip. This data structure, called the
Polygon Clipping Acceleration Grid
, is a 2D array that represents the bounding box around the triangulated clipping region partitioned intoN
cells. Cells are partially occluded by at least one triangle, totally occluded by at least one triangle, or not occluded by any triangles at all. This data structure allows the fragment shader to be less computationally expensive as it now has to perform dramatically fewer comparisons compared to clipping planes:The number of cells to generate is a trade off: Higher cell counts increase the chances that the fragment shader can exit early at the cost of taking longer to generate the initial acceleration grid on the CPU side. Not enough cells results has the opposite effect, where initial generation is fast but GPU performance is slow as many triangles will now have to be checked every frame. In my experience 34-40 splits (1156 - 1681) cells is
a good sweet spot between being extremely fast to generate CPU side and maintaining 60FPS GPU side.
Algorithm Pipeline
At a high level, the algorithm:
PolygonHierarchy[]
(∞ nested holes are supported!).PolygonHierarchy
into an individual mesh usingearcut
.positions
andindices
bufferPolygonClippingAcceleration
data structure using the amalgamatedpositions
andindices
buffersclippingAccelerationGrid
,meshPositions
, andoverlappingTriangleIndices
to the GPU.Known Issues / Future Optimizations
North America without accuracy issues), but will fall apart for clipping regions that extend around the entire globe. An update to this algorithm could change all the calculations to work with polar coordinates to fix this.
mod
and division. We might see a performance gain by storing 2D indices directly in the data texture instead of their 1D representation.ClippingPolygon
class should throw an exception explaining they oversimplified if that occured, but we could do a binary search approach to find the maximum simplification that doesn't break their mesh too.