-
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
Fix billboards when clamped to ground with depthTestAgainstTerrain #6621
Conversation
@hpinkos, thanks for the pull request! Maintainers, we have a signed CLA from @hpinkos, so you can review this at any time.
I am a bot who helps you make Cesium awesome! Contributions to my configuration are welcome. 🌍 🌎 🌏 |
Thanks for helping me with this @bagnell! What's the best way to write tests for this kind of thing? |
I haven't seen the code yet, but I imagine you need to mock the globe so the clamp to ground just returns some constant height value. Also, see the ground primitive tests |
@bagnell I tried to write some tests, but everything breaks down trying to do a render test with height referenced billboards because it relies on the I think this is ready for review. If you had other ideas for how to add tests let me know |
#ifdef CLAMP_TO_GROUND | ||
if (v_eyeDepth > -2000.0) { | ||
vec2 adjustedST = v_textureCoordinates - v_textureOffset.xy; | ||
adjustedST = adjustedST / (v_textureOffset.z - v_textureOffset.x, v_textureOffset.w - v_textureOffset.y); |
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'm surprised this compiled. Add use the vec2
constructor for the denominator.
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.
Thanks haha. I hardly ever glsl so I'm not very familiar with the syntax
vec2 st1 = ((v_dimensions.xy * (v_depthLookupTextureCoordinate1 - adjustedST)) + gl_FragCoord.xy) / czm_viewport.zw; | ||
float logDepthOrDepth = czm_unpackDepth(texture2D(czm_globeDepthTexture, st1)); | ||
vec4 eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); | ||
float globeDepth1 = eyeCoordinate.z / eyeCoordinate.w; |
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.
Make this and the above two lines a function.
eyeCoordinate = czm_windowToEyeCoordinates(gl_FragCoord.xy, logDepthOrDepth); | ||
float globeDepth2 = eyeCoordinate.z / eyeCoordinate.w; | ||
|
||
if (globeDepth2 > v_eyeDepth + czm_epsilon5) |
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.
One would think that all of these if
s would increase performance for fragments that don't execute them, but, unfortunately, that's not the case. It would look cleaner if you compute all of the globe depths and then have one if
combining all four conditionals for the discard.
@@ -152,6 +166,14 @@ void main() | |||
|
|||
vec2 imageSize = vec2(floor(temp), compressedAttribute2.w); | |||
|
|||
#ifdef CLAMP_TO_GROUND | |||
v_textureOffset = textureOffset; | |||
v_depthLookupTextureCoordinate1 = (0.0, 0.9) - depthLookupST; //the origin |
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 sure these are right? Say the anchor point is the bottom right, then wouldn't you want to test vec2(-1.0, 1.0)
instead of vec2(1.0)
?
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.
For the vec*
constructors, a single argument duplicates it for all components.
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.
Yeah, you're right. I think I forgot to test different horizontal origins, this only works for center.
@bagnell ready for another look. var viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain()
});
viewer.scene.globe.depthTestAgainstTerrain = true;
var e = viewer.entities.add({
position : Cesium.Cartesian3.fromDegrees(-122.1958, 46.1915),
label : {
text : 'Clamped to ground',
heightReference : Cesium.HeightReference.CLAMP_TO_GROUND,
horizontalOrigin : Cesium.HorizontalOrigin.CENTER,
verticalOrigin : Cesium.VerticalOrigin.BOTTOM,
fillColor : Cesium.Color.BLACK,
showBackground : true,
backgroundColor : new Cesium.Color(1, 1, 1, 0.7),
backgroundPadding : new Cesium.Cartesian2(8, 4)
}
});
viewer.trackedEntity = e; |
Look for Did you test this with all of the other billboard options? You'll have to account for |
@bagnell as far as I can tell, this is working correctly with things like The problem with the label is that each letter is checking the bounds of that letter instead of checking the bounds of the entire label. That's why each letter flickers individually. |
Can you add another attribute for the size of the whole label? |
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.
@hpinkos do you want to write a short tech blog post about this?
Source/Scene/BillboardCollection.js
Outdated
@@ -302,7 +311,9 @@ define([ | |||
BufferUsage.STATIC_DRAW, // SCALE_BY_DISTANCE_INDEX | |||
BufferUsage.STATIC_DRAW, // TRANSLUCENCY_BY_DISTANCE_INDEX | |||
BufferUsage.STATIC_DRAW, // PIXEL_OFFSET_SCALE_BY_DISTANCE_INDEX | |||
BufferUsage.STATIC_DRAW // DISTANCE_DISPLAY_CONDITION_INDEX | |||
BufferUsage.STATIC_DRAW, // DISTANCE_DISPLAY_CONDITION_INDEX | |||
BufferUsage.STATIC_DRAW, // TEXTURE_OFFSET |
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.
Extra whitespace?
Source/Scene/BillboardCollection.js
Outdated
usage : buffersUsage[TEXTURE_OFFSET] | ||
}, { | ||
index : attributeLocations.dimensions, | ||
componentsPerAttribute : 2, |
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 anyway to pack this into one component and perhaps store as z
in DISTANCE_DISPLAY_CONDITION_INDEX
? Haven't looked closely, but perhaps precision requirements are not stiff here and fewer bits can be used?
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 way to pack a Cartesian2
into one float? I'm not quite sure how to do this
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.
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.
There are no bit-wise operators in GLSL but you can shift bits by multiplying powers of 2. See any of the writeCompressedAttrib*
functions. So if you want to pack a Cartesian2
into a float, you'll have 12 bits for each component. Is that enough precision? It would look something like this for packing:
var LEFT_SHIFT12 = Math.pow(2.0, 12.0);
var width = Math.floor(Math.clamp(width, 0.0, LEFT_SHIFT12));
var height = Math.floor(Math.clamp(height, 0.0, LEFT_SHIFT12));
var dimensions = width * LEFT_SHIFT12 + height;
Then, reverse it in the vertex shader to unpack.
Source/Scene/BillboardCollection.js
Outdated
@@ -1176,6 +1197,10 @@ define([ | |||
} | |||
|
|||
var disableDepthTestDistance = billboard.disableDepthTestDistance; | |||
if (billboard.heightReference === HeightReference.CLAMP_TO_GROUND && disableDepthTestDistance === 0.0) { | |||
disableDepthTestDistance = 2000.0; |
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.
Should this be configurable at the scene level? I think there are other billboard/label/depth-ish options there.
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.
It's configurable by setting disableDepthTestDistance
to a different value
#ifdef VECTOR_TILE | ||
attribute float a_batchId; | ||
#endif | ||
|
||
varying vec2 v_textureCoordinates; | ||
#ifdef CLAMP_TO_GROUND |
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 going to use a huge amount of memory bandwidth.
Looks easy to pack some varyings together. Also perhaps some can be derived from others or more should be computed in the fragment shader if they are trivial?
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, use more precise names - if possible - than 1
, 2
, and 3
for texture coordinates.
@@ -10,11 +10,25 @@ attribute vec4 eyeOffset; // eye offset in mete | |||
attribute vec4 scaleByDistance; // near, nearScale, far, farScale | |||
attribute vec4 pixelOffsetScaleByDistance; // near, nearScale, far, farScale | |||
attribute vec3 distanceDisplayConditionAndDisableDepth; // near, far, disableDepthTestDistance | |||
#ifdef CLAMP_TO_GROUND | |||
attribute vec4 textureOffset; // the min and max x and y values for the texture coordinates |
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.
Should this be named something more precise like textureCoordinatesBounds
?
v_depthLookupTextureCoordinate1 = vec2(1.0) - depthOrigin; //the origin | ||
if (v_depthLookupTextureCoordinate1.y == 1.0) //vertical origin is top | ||
{ | ||
v_depthLookupTextureCoordinate2 = vec2(0.0, 0.0); //bottom left |
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.
Just vec2(0.0)
float epsilonEyeDepth = v_eyeDepth + czm_epsilon5; | ||
|
||
// negative values go into the screen | ||
if (globeDepth1 > epsilonEyeDepth && globeDepth2 > epsilonEyeDepth && globeDepth3 > epsilonEyeDepth) |
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.
Make this a separate if
statement after each call to getGlobeDepth
.
On WebGL implementations with dynamic branches, it could exit early and avoid the memory bandwidth for the unnecessary texture reads.
Also, if not already done, order the calls to getGlobeDepth
in the order of most likely to fail the depth test - I imagine origin first.
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 had it that way initially but @bagnell said it wouldn't work that way. Is dynamic branches something that's only supported in certain browsers?
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.
On a lot of GPUs with WebGL 1 support, there is no dynamic branches, but @pjcozzi is right. Early exits for GPUs with dynamic branches would be faster.
CHANGES.md. |
e9f7d3f
to
da3593d
Compare
@bagnell this is ready for another review |
{ | ||
if (v_originTextureCoordinateAndTranslate.y == 0.0) | ||
{ | ||
v_originTextureCoordinateAndTranslate.y = 0.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.
Why 0.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.
It had better results than testing the bottom edge when I was first playing around with this, but I'll revisit it because I'm not sure if that's still the case
czm_writeLogDepth(); | ||
|
||
#ifdef CLAMP_TO_GROUND | ||
if (v_eyeDepthAndDistance.x > -v_eyeDepthAndDistance.y) { |
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.
How much of a pain would it be to add this to both the vertex and fragment shader?
We would want it to execute in the vertex shader when ContextLimits.maximumVertexTextureImageUnits > 0
; otherwise, execute in the fragment shader. That condition just means we can read from textures in the vertex shader. The reason we would want to do this is for performance. Instead of discarding the fragment, you would put the vertex behind the eye so that it gets clipped like we do for distance display conditions or translucency by distance.
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'm going to do this in a separate PR because the math is different since there's no gl_FragCoord
in the vertex shader. I'll open an issue
The code looks good to me. The biggest thing would be executing that code in the vertex shader when possible. Does anyone else want to test? |
65b7cff
to
493b8ce
Compare
@bagnell I think I've finally worked out all the kinks. Labels were actually looking up pixels in the completely wrong location because the horizontal origin passed in is always "LEFT", so I had to pass in the horizontal offset the user set separately. There is still occasionally a 1px discrepancy that can lead to this: I'm honestly not sure how to fix it. I think it's a rounding error. The texture lookup is literally 1px different and it doesn't happen all of the time. |
This crashes in IE since it doesn't have depth texture support. |
@bagnell thanks for catching that. I changed IE so it has the behavior currently in master. I also tested Firefox and Edge and they both work great. And all the tests pass locally. Anything else? |
I still see the missing glyphs when the origin is left center. I also see all the glyphs disappear but the background is still visible when the origin is right center. |
Dagnabbit. I thought I checked all the weird properties but I guess I missed some stuff 😓 |
@bagnell I fixed |
To fix |
@bagnell This should be ready for another look But I believe I've fixed everything that was really wrong. I tested a combination of different origins, scale, rotation and it seems to have pretty good behavior. |
Be careful with this, clamping pixels can lead to very undesired behavior (especially when the text is moving). That's the main reason we stopped clamping to screen space. Not saying it applies here, just something to be aware of. |
@mramato thanks for noting that. I should be able to make the location of the depth lookup consistent without effecting the position of the billboards. |
@bagnell nope, that didn't seem to help with the label letters. Do you have any ideas? I feel like it only happens if you're trying really hard and might not be a huge issue in normal application use. Regardless, the behavior here is way better than what's in master now. |
This looks good to me. I wasn't able to reproduce the missing letters at all where the last time I commented I could reproduce it reliably. I'm merging this but don't forget about #6699. |
Is there a reason that this change only affects billboards with HeightReference.CLAMP_TO_GROUND? I'm having the same issues with billboards that are very close to (but not clamped to) the ground, so I had hoped that those would be fixed by this change. On a side note, in my specific case, I was looking for a way to hide billboards based on ONLY the anchor point's visibility. Perhaps that could be an option in the future! |
@esparano good point, this would also be useful for labels "on" 3D buildings, etc. @hpinkos how hard is it to make this a separate boolean? If easy, maybe do this soon; otherwise, submit an issue. Should it default to true? Is there ever a case when it should be false...hmm, might not need a flag at all if everyone likes the new depth testing...I dunno. |
Fixes #2694
Before:
After:
HeightReference.CLAMP_TO_GROUND
Test code: