Drape vector polygons onto terrain via GPU lookup#13627
Conversation
|
Thank you for the pull request, @danielzhong! ✅ We can confirm we have a CLA on file for you. |
| int indexEnd = int(texelFetch(u_vectorPolygonGridCellIndicesTexture, ivec2(cellIndex + 2, 0), 0).r); | ||
| int indexStart = cellIndex == 0 | ||
| ? 0 | ||
| : int(texelFetch(u_vectorPolygonGridCellIndicesTexture, ivec2(cellIndex + 1, 0), 0).r); |
There was a problem hiding this comment.
Something I didn't think about on the first PR - have we determined that we'll never exceed the max texture size for the grid cell indices? It looks like, right now, it's expected to be a single row.
Seems reasonable to me that a given tile wouldn't need >4k grid cells, but just checking
There was a problem hiding this comment.
Yep, fixed! The header texture now grows vertically too instead of being a single row, I did the same for GS few months ago
| int indexEnd = int(texelFetch(u_vectorPolygonGridCellIndicesTexture, ivec2(cellIndex + 2, 0), 0).r); | ||
| int indexStart = cellIndex == 0 |
There was a problem hiding this comment.
I know it's alluded to above, but I think it would be helpful to leave a short comment that the texture coords are offset by 1, to account for the grid width + height headers. (And, importantly: offset by 1 and not by 2, because the indexStart for cell 0 is implicit, not stored explicitly).
| // One extra iteration (i == indexEnd) flushes the final primitive group. | ||
| for (int i = indexStart; i <= indexEnd; i++) | ||
| { | ||
| int primitiveIndex = -1; | ||
| vec4 edge = vec4(0.0); | ||
| if (i < indexEnd) | ||
| { | ||
| ivec2 edgeUv = vectorIndexToUv(i, edgeTextureSize); | ||
| edge = texelFetch(u_vectorPolygonEdgeTexture, edgeUv, 0); | ||
| primitiveIndex = int(texelFetch(u_vectorPolygonEdgePrimitiveIndicesTexture, edgeUv, 0).r); | ||
| } | ||
|
|
||
| if (primitiveIndex != currentPrimitive) | ||
| { | ||
| if (currentPrimitive >= 0 && inside) | ||
| { | ||
| ivec2 primitiveUv = vectorIndexToUv(currentPrimitive, primitiveTextureSize); | ||
| vec4 fillColor = texelFetch(u_vectorColorTexture, primitiveUv, 0); | ||
| baseColor = fillColor * vec4(fillColor.aaa, 1.0) + baseColor * (1.0 - fillColor.a); | ||
| } | ||
| currentPrimitive = primitiveIndex; | ||
| inside = false; | ||
| } | ||
|
|
||
| // Even-odd rule with a horizontal +x ray; the half-open interval | ||
| // (> vs <=) counts a ray through a shared vertex exactly once. | ||
| if (i < indexEnd && (edge.y > vectorUv.y) != (edge.w > vectorUv.y)) | ||
| { | ||
| float t = (vectorUv.y - edge.y) / (edge.w - edge.y); | ||
| float xIntersect = edge.x + t * (edge.z - edge.x); | ||
| if (vectorUv.x < xIntersect) | ||
| { | ||
| inside = !inside; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
I find it a little difficult to digest this loop; the nested conditionals and the last-index-special-case are making already complex code more complicated. I think by moving the special-last-index case out of the loop and using some helper functions, this could be more readable (this is just a suggestion, and it could probably be improved even more):
// Composite a polygon's fill over baseColor when this pixel is inside it. A
// negative index (empty cell or first iteration) or an outside pixel is a no-op.
vec4 vectorCompositePolygonFill(vec4 baseColor, int primitiveIndex, bool inside, ivec2 primitiveTextureSize)
{
if (!inside || primitiveIndex < 0)
{
return baseColor;
}
ivec2 primitiveUv = vectorIndexToUv(primitiveIndex, primitiveTextureSize);
vec4 fillColor = texelFetch(u_vectorColorTexture, primitiveUv, 0);
return fillColor * vec4(fillColor.aaa, 1.0) + baseColor * (1.0 - fillColor.a);
}
// True if a horizontal +x ray from p crosses the edge. The half-open interval
// (> vs <=) counts a ray through a shared vertex exactly once.
bool vectorEdgeCrossesRay(vec4 edge, vec2 p)
{
if ((edge.y > p.y) == (edge.w > p.y))
{
return false;
}
float t = (p.y - edge.y) / (edge.w - edge.y);
float xIntersect = edge.x + t * (edge.z - edge.x);
return p.x < xIntersect;
}
vec4 vectorPolygonRender(vec2 vectorUv, vec4 baseColor)
{
...
for (int i = indexStart; i < indexEnd; i++)
{
ivec2 edgeUv = vectorIndexToUv(i, edgeTextureSize);
vec4 edge = texelFetch(u_vectorPolygonEdgeTexture, edgeUv, 0);
int primitiveIndex = int(texelFetch(u_vectorPolygonEdgePrimitiveIndicesTexture, edgeUv, 0).r);
// A new primitive means the previous group is complete: composite it,
// then start counting the new one fresh.
if (primitiveIndex != currentPrimitive)
{
baseColor = vectorCompositePolygonFill(baseColor, currentPrimitive, inside, primitiveTextureSize);
currentPrimitive = primitiveIndex;
inside = false;
}
if (vectorEdgeCrossesRay(edge, vectorUv))
{
inside = !inside;
}
}
// The last primitive group has no trailing edge to trigger its composite.
baseColor = vectorCompositePolygonFill(baseColor, currentPrimitive, inside, primitiveTextureSize);| collection, | ||
| collectionData, | ||
| rectangle, | ||
| width, |
There was a problem hiding this comment.
I don't think we need to pass width, since we already have rectangle. Could just call rectangle.width getter directly.
Description
Follow-up to #13577, which added GPU-lookup draping for clamped polylines on
terrain. This PR extends the same architecture to polygon fills and
points
Issue number and link
Testing plan
Author checklist
CONTRIBUTORS.mdCHANGES.mdwith a short summary of my changeAI acknowledgment
If yes, I used the following Tools(s) and/or Service(s):
GithubCopilot
If yes, I used the following Model(s):
Claude Fable 5
PR Dependency Tree
This tree was auto-generated by Charcoal