Friday, January 27, 2006

Struts, Struts, Struts

Ok -- struts -- what are they? how do I get rid of them? and why are they there?



Ever seen something like the above picture? You've inserted your own image, particularly one that has been converted to a QuadTileSet (QTS) -- e.g. with dstile -- and then display it, and you get this bizarre under structure, seemingling holding up your picture off the ground. These are what I call struts.

Why are struts there?
Struts are part of WorldWind code base ironically to solve another problem: intra-tile seams/cracks. Imagine if you were to take a white sheet of paper, cut it into 50 equal tiles, and then reassemble that piece of paper into its original shape on top of a flourescent orange desktop. No matter how good you are at putting those tiled pieces of paper together, you are bound to see through the tiles to the underlying desk, particularly if you were to take a look at it from various directions. Imagine this piece of paper was crumbled up first, then "flattened" again, and the texture of the "terrain" of the paper would make the problem even worse.

Well, that "even worse" example is exactly what the 3D rendering engine has to do inside WorldWind and your graphics card. You eventually start to see through the spaces in between the tiles. Plus, the tile to terrain mapping varies from one QTS to the next, exacerbating the problem. Each point is calculated uniquely, with potentially different parameters -- leading to disconnect between tiles.

Struts were a novel solution for this problem. Effectively, they created a similarly colored structure at the corners of each tile projected down to the spherical geoid that WW uses as the "sea level" earth. Furthermore, the z buffer is cleared before displaying the QTS layer, allowing you to see the struts on top of what should otherwise be displayed underneath it.

If your imagery is largely continuous, then this really presents no problem, as there will be no location where you can see the imagery from the side. That's why it works great for the primary data sources. For your own imagery, however (such as the example above), you'll hit the problem.

More info can be found at: http://www.worldwindcentral.com/chat/irclog/worldwind-dev/2006-01-19.log.

Quick Fix: Removing Struts
Let's say you want to prevent the struts from drawing. Well, Chris put in a simple quick fix for removing the struts from view. With 1.3.3.1, WorldWind supports alpha blended layers. As you can imagine, alpha blended layers with struts can cause havoc to a scene. Therefore, WW is set to remove struts from the render loop when an image is in any way transparent. Therefore, as ddh and mazop have suggested, switch your layer's opacity to 254 and set the blending to bothsrcalpha. This is the quick and easy fix with no programming necessary. Note: in doing this, however, you've just traded the struts problem for the underlying seams problem -- which will now rear its ugly head.

Another way to remove the struts is just to modify the code to expose a UseStruts property off of the QTS. This UseStruts property should map to a UseStruts boolean inside the QuadTileArgs setting. Then, in your QuadTile::CreateElevatedMesh() routine, simply add the check for the UseStruts boolean:

if ((!QuadTileArgs.UseStruts m_CurrentOpacity <>&&
heightData != null) {
radius = layerRadius +
heightData[terrainLatitudeIndex + latitudePoint,
terrainLongitudeIndex + longitudePoint] *
verticalExaggeration;
} else {
radius = meshBaseRadius;
}


Other solutions

The above trick, just trades one problem for another. The solution space to the strut problem is divided into two camps: #1) use the struts, but make them not visible, and #2) removing the struts altogether, and solving the underlying seams problem a different way.

Ideas for #1: Making the struts invisible
Here are my brainstormed ideas on how to make the struts still present but just not visible on non continuous imagery:

  • A quick hack would be to not clear the Zbuffer, and let the previous terrain hide it. Problem is, you start to have zbuffer fighting from data placed on top of each other. Plus, you will always have to have underlying data.
  • What if we implement a global ImageAccessor that behaves more like the TerrainAccessor in WW? Here, we have one global image, on which textures are rendered. Then, we just logically replace the image space for that pixel area. Then, at the final render, we map that accumulated screen-sized texture down to the mesh. The downside with this is that it moves the heavy lifting out of the GPU -- effectively modeling your own frame buffer. Overkill and not a good idea.
  • What about just shortening the struts, or even varying their length based on the obliquity of the camera?
  • What about some funky experimentation with backface culling? I noticed that the fact that the struts do have backface culling doesn't seem to interrupt their ability to solve the problem when they should be being backfaced culled. What if we turned on back and front culling!? possible?

Ideas for #2: Solving the Seams problem a different way
More ideas, this time to solve the seams through a different method. Note: these all assume that you've removed the struts.

  • Use vertex shaders on the edge vertices of a tile, similar to what Hoppe proposes.
  • The seams are exacerbated by the tiles being drawn on a grid different than the source mesh data. If you were to match your QTS parameters of your imagery to that of the terrain data, you should have imagery/texture tiles being mapped one-to-one (at least on the edges) to the underlying terrain/mesh tiles. Then, interpolation becomes almost irrelevant, and in theory, the seams disappear. (not sure if this really works though -- more thought needed)
  • Similarly, if you map your imagery to the underlying "reference" imagery texture QTS tile parameters, then you should be guaranteed to generate the exact same altitude values for each mesh vertex. Then, make the Zbuffer to not be cleared. Then, you should see identical z values stored for your mesh, and your imagery's texture should replace the reference imagery (i.e. z-fighting won't be an issue). This works assuming a) only one reference imagery underneath, and b) the two layers have their Update()'s called relatively simultaneously. This solution hints more at the need for a common grid. More thought is needed -- and keep in mind this reminder from DDH: "the TerrainAccessor is only used to get a elevation from a lat/lon, it's not part of the mesh system. but the problem is that the QTS layers are not defined in a single well-defined grid, they can be arbitrarily defined."
  • Similarly, can we draw directly onto the landsat mesh, instead of creating our own?
  • I'm not sure the tiles are always using the TerrainAccessor at the best resolution. This difference could easily cause disparities in the mesh, exacerbating the seams. Look into possible fixes like one that always force the highest resolution terrain tile (on GetElevationAt()) with the highest #of samples per degree for all mesh points on the edge vertices of a tile. More thought needed - not sure if it does this already.
  • Also, each tile may be calling the TerrainAccessor with different values -- those in the distance may be using a lower samples-per-degree number than nearby tiles. Where these tiles butt up against each other will show seams. Perhaps force a mesh-wide tile level inside QTArgs?
  • Similarly, I've noticed that the seams go away if you've got better imagery at a higher tile level. This makes me continue to think that having one or two more levels of finer grain mesh and/or imagery than your current visible would alleviate much of the problem. This can be easily implemented using QTSubset.
  • Similarly again, what if we were to use QTSubset to draw something else underneath, e.g. a quad that is slightly larger than the tile, and a colors that match the four corners. Hacky, yes.
  • There are hints from Mashi that there is a bug in the WW code that makes the seams worse than they should be. From dpatton: "150 samples in a terrain tile, and Chris was using 150 as an upper bound, rather than 149(i.e. 0-149)". This would cause a problem -- and worth looking into. I can't imagine it would solve it completely -- but would help make the tiles line up tighter.

What am I personally going to do to solve it? For me, with higher resolution data, the seams actually are manageable, as long as you wait for the higher res terrain+imagery tiles to come in. Thus, I've just disabled the strut code as above -- which appears to handle the problem sufficiently for short term needs. I think the best next-round solution is to force higher res on the mesh/texture -- or at least figure out conclusively why it is solving the seams problem for me. This would start to lean towards some of the solutions proposed in #2 above.

1 Comments:

Anonymous Patrick Murris, Montreal said...

Sorry to say i actually contributed to making the seams problem worse and struts even more crucial.

I produced the 'optimized' mesh you can see since 1.3.3, and adjacent tiles will likely follow different optimization routes with the suppression of none matching vertices at their border.

This will not show much if the mesh is close to the underlying elevation map (best fit), but if a large tolerance is used (to save polygons ie) seams will be more visible and struts more desirable.

Just to say that struts are some kind of hack, but it may not be easy to get read of them without putting a tight constraint on the mesh creation process.

10:18 PM  

Post a Comment

<< Home