Geomipmapping, cracks and triangle fans (now with pics)

Started by
11 comments, last by Fingers_ 18 years, 5 months ago
I am in the process of implementing the geomipmapping algorithm, and I am now trying to solve the problems of the "cracks" (T-junctions or geometry gaps) who appears when two nearby patches have different LODs, it is advised in the paper to use "two triangle fans for this edge". A few questions to everyone who have already implemented this : 1) What kind of primitives do you use when rendering terrain ? I use triangle lists because triangle strips means making a lot of draw primitive quads for a single patchs (my patches are made of 32x32 quads), and triangle fans are not an option. 2) The paper advices not to render the edge of the higher level-patch (replacing it with the triangle fan). But in order not to render an edge, I have to split my DrawPrimitive calls for a patch, thus losing a lot of performance ! I don't get what I should do in this situation (of course rendering the edge's geometry twice is not an option) Thanks for your replies. EDIT: for those of you who don't know what geometry gaps are, have a look at (1) The cracks clearly visible on the terrain because of (2) the LOD variations [Edited by - bleyblue2 on October 28, 2005 4:58:22 AM]
Advertisement
Quote:1) What kind of primitives do you use when rendering terrain ? I use triangle lists because triangle strips means making a lot of draw primitive quads for a single patchs (my patches are made of 32x32 quads), and triangle fans are not an option.


My patches consist of one single indexed triangle strip. You'll have to hack in some extra vertices to make them all CW/CCW though. But it works for me, and it saves me a lot of memory and GPU bandwidth.

Quote:2) The paper advices not to render the edge of the higher level-patch (replacing it with the triangle fan). But in order not to render an edge, I have to split my DrawPrimitive calls for a patch, thus losing a lot of performance ! I don't get what I should do in this situation (of course rendering the edge's geometry twice is not an option)

Well, I don't know this paper, but I'll tell you what I did.
I rendered the patches based on their LOD but made sure that the difference in LOD between two neighbouring patches was never more or less than one level. By using a different list of vertex indices I made sure that the outer vertices (the edges) of the patch with the lowest LOD were all "divided" into two vertices so they would nicely fit next to vertices of the other patch.
This way I only needed a very simple system to avoid the cracks. I didn't need to fill the gaps with extra vertices (well, in a way I did, but they were already integrated in the patch) and I only needed to switch between list of indices without changing the vertices themselves.

I realize this is not the clearest of explanations, I really should provide some pictures with it. However, am not able to that right now. I can do it later, if you'd like/need that.
Quote:Original post by WanMaster
I rendered the patches based on their LOD but made sure that the difference in LOD between two neighbouring patches was never more or less than one level. By using a different list of vertex indices I made sure that the outer vertices (the edges) of the patch with the lowest LOD were all "divided" into two vertices so they would nicely fit next to vertices of the other patch.
That's a fairly common hack. I don't like it at all, however, because it basically causes a cascade of unnecessary LoD rise on the terrain, as patch LoDs are increased to be within the necessary delta.

Anyway, bleyblue2, your objections are fairly astute and accurate. As it turns out, your use of a indexed triangle list (an indexed strip would be fine too) makes things fairly easy. What it comes down to, is that we need to patch up the edges by altering the indices, while rendering the exact same index buffer.

A rough description of this is as follows:
Using the LoDs of the current patch and the neighbor, if the current patch's LoD is more detailed than the neighbor's, determine the vertices on the edge that are "cracking" -- that is, vertices that should not be rendered at the edge because doing so would create a crack or T junction. Identify the indices that reference a cracking vertex, and modify that index to use the nearest non-cracking vertex instead. Do not bother modifying the total primitive/index count. In this process, the extra triangles at the edges of higher LoD patches will become degenerate and will not affect the rendering process. Repeat for each neighbor. At the end, depending on your exact construction and moving algorithm, you will have structures on the edges that look like de Boer's triangle fans, but have been baked into the actual triangle list. Still an extra draw call, no extra geometry has been added (*cough* skirts), and extremely efficient to do if you cache the modified indices (and even better if you limit the frequency which LoDs can change for any given patch -- this also helps visual quality). I have code which does this, but I think you'd be well served by working it out on your own. I can provide hints along the way, or if you really just want to see the result code, that's fine too.

It's also possible to increase detail at the edges of lower detail patches, but it's less straightforward, since it involves adding geometry instead of degenerating it.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
Thanks both for you replies - it's always a pleasure to have such quick answers.

Promit : I'm very interested in how you set up your triangle strips; maybe you could provide some code or an explanation ? As for the gaps "the paper" (I meant de Boer's paper, sorry not to have mentioned that, but I thought everyone implementing geomipmapping would have read it) advices not to add geometry, but rather to change the indices to omit some - still, I have to give you credit for your methode, esp. if you found it yourself, but I recommend you to read "Fast Terrain Rendering Using Geometrical MipMapping", Willem H. de Boer, where you might find some hints in how to improve your current implementation. Thanks a lot anyway !

Wanmaster : As stated before, I'm interested in the setup of the triangle strips, I expect you have to duplicate some vertices like Promit, but I recognize the cost might be worth it. Thanks you for re-explaining the paper's method, I understand it a lot more now. Still, feel free to post supplementary pictures, as it isn't the

Nevertheless, since you both seem to have successfully implemented a terrain rendering algorithm, I have to ask a few other questions :

In either way, you seem to be updating your indices for every patch who has changed LOD (or whose neighbor has) for each frame. Isn't the lock costly ? I don't think it's possible to do otherwise, though.
Wanmaster : when locking your Index Buffer, do you update all the indices or just the ones that have changed ? I'm speaking about the difference at the Lock() level, do you Lock the entire LOD-relative IB or do multiple locks for each index ? Do you keep the same IB from a patch to another (with the same LODs, of course) and modify it or do you create an IB for each "special" patch (i.e. patches whose cracks need to be fixed) ? Since there are also as many IBs as LODs, I suspect you might use the latter (creating all the IBs combinations at run time) as you say you switch between lists of indices but isn't it memory-expensive ? (since terrain is not the only thing I have to render, I'd like to keep it as lightweight as possible). Moreover the former (updating only parts of a single LOD-relative IB or using a single dynamic IB for each LOD, which would be modified whether the lower-level patch is on north, south, east...) seems pretty inefficient, but. To put it simple, which method are you using to create/modify your index buffers ?

As this is my last step toward a complete implementation of geomipmapping, I would appreciate any comments/advices from anyone.

Thanks in advance.
Quote:Original post by bleyblue2
Promit : I'm very interested in how you set up your triangle strips; maybe you could provide some code or an explanation ? As for the gaps "the paper" (I meant de Boer's paper, sorry not to have mentioned that, but I thought everyone implementing geomipmapping would have read it) advices not to add geometry, but rather to change the indices to omit some - still, I have to give you credit for your methode, esp. if you found it yourself, but I recommend you to read "Fast Terrain Rendering Using Geometrical MipMapping", Willem H. de Boer, where you might find some hints in how to improve your current implementation. Thanks a lot anyway .


Read what I said again. My method has precisely the same results as the triangle fans that de Boer describes towards the end of the paper, and which you allude to. de Boer is a little vague on exactly what he does, whether the triangle fans he's talking about are explicitly rendered, or merely implicitly generated by modifying indices.
Quote:The only thing it does is changing
connectivity (or indexing) of vertices for the higher detail
GeoMipMap.
Quote:A fast way of rendering this is by
using two triangle-fans for this edge,[...]

But I think that what I described is probably identical to what de Boer does -- implicit creation of triangle fans by degenerating extra triangles on the borders.

Quote:
In either way, you seem to be updating your indices for every patch who has changed LOD (or whose neighbor has) for each frame. Isn't the lock costly ? I don't think it's possible to do otherwise, though.

Locking an index buffer is almost free, except for the race condition that occurs when the GPU is actually using the indices. This won't happen often, and it certainly won't happen reliably. (Furthermore, if you lock your vertex buffer using discard/write-only locks -- glBufferData or D3DLOCK_DISCARD -- this will never impact performance.) The more costly operation is actually modifying the index buffer, which is why I suggest caching and limiting LoD change frequency so that the performance costs are amortized.
SlimDX | Ventspace Blog | Twitter | Diverse teams make better games. I am currently hiring capable C++ engine developers in Baltimore, MD.
An alternative is to use a "skirt", rather than stiching the two patches together. It works well and it is much simpler, and the geometry is static. The skirt is simply a single strip that goes around the entire edge of each patch and hangs straight down. If there are any cracks, it will fill them in. A minor drawback is a small "cliff" where the skirt fills in a crack, but it is not really noticeable.
John BoltonLocomotive Games (THQ)Current Project: Destroy All Humans (Wii). IN STORES NOW!
Promit : I have well understood the method you are using, thank you again ! (even though I will be doing like De Boer, because I do not have the time to measure the performance nor to implement every method). But maybe you could explain to me how you setup your triangle strips ? As for the IBs, by locking them I was implying modifications, and I have already enough to do with my fillrate, I want the terrain as lightweight as possible, etc. Could you explain what you mean by "caching" and how you code it though ? I agree on minimizing the LODs transitions, but only the user really decides...

JohnBolton : This skirt seems rather interesting, as it would be very simple to draw and code ! but I think the LOD changes would be too noticeable though - but maybe you could show me some screenshots/demos ? Thanks.
I just check to see if the neighboring chunk of terrain is farther from the viewer, and if it is at a lower LOD than the one I'm testing against. If it is, I hack in the extra verts/tris to stitch the terrain together. I am just GL_TRIANGLES'ing the triangles through the PIPE. Nothing fancy, but it runs great.
Wudan, you are using the same method as JohnBolton's, i.e. changing the terrain's geometry by adding triangles who don't follow the heightfield. It might be a good method, but I'm rather doing like De Boers and fill the gaps while allowing the terrain to follow the heightfield. Thanks anyway!
There is a very simple precalculation method, possibly mentioned before. The map should be divided into alternating big and small strips. The result should look like a rectangle grid on paper, with the lines being the small strips. First the detail levels for each big patch should be calulated. Then each small strip, that separates two big areas, should get 2 triangle lists for each detail level. One for continous detail and one for detail level change. During the rendering, the appropriate strip should be rendered.

Two examples:
normal: patch A (detail 1) - strip B (detail 1) - patch C (detail 1)
changing: patch A (detail 1) - strip B (detail 1to2) - patch C (detail 2)

The detail stacks:
patch A (1, 2, 3)
strip B (1, 1to2, 2, 2to3, 3)
patch C (1, 2, 3)

This way all detail levels can be calculated and stored before drawing, and no runtime updates are needed. If the detail changes more than one level in a single step, then the number of transitional strips per detail level would be equal to the maximal detail change allowed. This extra data is much less than the data required for storing the big patches, and precalculating and storing them will result in an improved performance. There is a very simple way to make the detail stacks, by first generating the whole terrain lods, then cutting out the big patches, and filling in the single triangle gaps with the strips. This algorithm only generates triangle strips or simple triangle lists.

Viktor

This topic is closed to new replies.

Advertisement