We just enhanced our terrain system and the results were so good I thought I would share the technique with the list. With this technique you can dynamically increase or decrease terrain quality to match hardware capability and you can render textured seamless meshes representing miles of terrain. This actually improved our framerates while increasing the quality of the rendered terrain. I have not tested this yet on lower end hardware, but I had a test case last night showing 32kilometers of terrain, 5 km of trees, sky, etc at 110 fps on a Geforce 4.
I have been corresponding with Thatcher Ulrich, who published an article on adaptive quad trees in Gamasutra (http://www.gamasutra.com/features/20000228/ulrich_01.htm). The idea which I had was to combine our scenegraph optimized terrain paging system with the adaptive nature of his algorithm. As with most OpenGL algorithms, his code was built to recalculate the terrain on every single frame, and there is no capability to intelligently texture the terrain (short of draping some huge big texture over the whole thing). Our previous system was described to the java3d list in the e-mail entitled "RE: ROAM and GPU Usage". So to recap our static system, we keep a quad tree in memory. Leaf nodes in the quad tree have a corresponding BranchGroup which hold the shape of a patch (texture and geometry). Up to now we have been building the patches offline and then pages them in as needed. With this new technique we build the geometry for the patches dynamically, but still load the textures from an offline source. The technique has three pieces: Heightfield management, geometry construction and rendering. 1. Heightfield management. Thatcher's adaptive trees have the ability to hold both sparse and detailed heightmap infomation within the same tree. So it is possible to store data at 128m resolution for the whole map, and then lay down 16m data for another portion of the map. His algorithm can also bilinearly interpolate between heightnodes to produce nice data on demand. To change an arbitrary terrain dataset into something usable by Thatcher's algorithms we needed to have a sample size which is a power of 2. So we wrote a converter which takes heightmaps and builds bezier splines to represent the terrain. We then query the splines for any x,z value and get a fairly accurate resampling which preserves terrain details which we would have lost had we linearly interpolated. We can then cull static data from the tree. Thatcher can scan the tree and remove "irrelevent" height samples to reduce memory usage. We have been seeing about 40 percent reduction of memory using this. 2. Thatcher's algorithm works in two passes. The first pass adjusts the quadtree according to a detail parameter and the location of the view. This will enable and disable quads and vertices throughout the tree very rapidly. It is then possible to query the tree for the triangles which are needed to render. It does this by starting at the top and examining the quad. Each quad has up to 9 vertices. Each quad is divided up into a maximum of 4 quadrants, but if the quadrant is disabled then it is rendered using 2 triangles, otherwise that quad is rendered with up to 8 triangles. His article explains this in detail, so I will not explain that more. The nice thing about the algorithm is that it is possible to render a part of the tree as long as it is power-of-two aligned. 3. You could of course render the terrain by building one big geometry array with all the triangles generated by Thatcher's algorithm, but that has two problems. You would have to cull FoV by triangles or else you would be sending lots of terrain to the card every frame that could not even see. You could not use display lists because the terrain would change on every frame. The next problem is texturing. At a minimum you want a 2:1 texel-meter texture ratio + multi texturing for a nice look. That would require something like a 16k x 16k texture to place over a 32k x 32k terrain. We texture Magicosm at 1:1 texel-meter ratio + detail multi-texturing. So in our system the LodTree has a quad tree of LodNodes. The construction of this tree mirrors the scenegraph. We start out with LodNode representing the top of the tree, and then there are 4 children representing quadrants. We have rules determining how close you can be to a certain lod level before it is forced to subdivide. After you move a few meters we re-check these bounding spheres against the bouning boxes of the patches. If they intersect then we sub-divide. After it is determined that it should not divide any more it loads in the texture for that patch/level from a paging file. Now in the past we would also load the geometry from the file, attach the texture and then put it into a shape3d and add to the scene (in transactions of course to maintain integrity). So we made a small change to incorporate adaptive quadtrees. We created a QuadNode class which extends branchgroup. We extended Thatcher's system by creating something called a QuadRenderer, which are targets for rendering triangles into. Each QuadRenderer holds the bounds that should be rendered for and some data buffers. It also has a single public method called render(). When you ask the adaptive quad tree to render you pass it a QuadRenderer class. It uses the bounds as it traverses the tree until it finds the parent quad which exactly matches the bounds in the QuadRenderer class. When each quad is rendered it is costructed in a maximum of 8 triangles using 9 vertices. So the vertices are "rendered" into a vertex array in the QuadRenderer and then the triangles are defined using indexes into the vertices. Once each quad has "rendered" its triangles the render() method of the QuadRenderer is called to process them. In our QuadNode we created an inner class extending QuadRenderer and overrode the render() method. The QuadNode uses a GeomContainer to hold the geometry for the patch. GeomContainers are our standard dynamic geometry containers which can render using by-reference, NIO buffers, Interlaced, etc. It has a standard interface and it contains a GeometryArray. When you construct it you have to give it a class implementing GeomUpdateInterface. So we have the QuadNode implement this interface and create a GeomContainer inside the node to hold the geometry. When the QuadNode.update() is called, it calls the GeomContainer.update() which in-turn calls the updateData() method on the geometry array, which in turn calls the updateGeom method of the class implementing the GeomUpdateInterface, which in this case is the QuadNode. This method can now saftly update the geometry. It does this by calling passing the QuadRenderer into Thatcher's quadtree. As each render() call is made against the innerclass QuadRenderer, it examines the triangles in the buffers and transfers them into GeomContainer. It also calculates texture coordinates and vertex colors if needed. 4. So each LodNode patch becomes a dynamically updatable mesh with no gaps or seams. If the tree is rebuilt to change the larger Lod levels then the new patches are re-rendered into new QuadNodes to perfectly match their sibling and cousin quads. In addition, we are recalculting the QuadNodes every few steps by calling QuadTree.update(view,detail) and then updating each active QuadNode. This update uses the same texture since it is not a rebalancing of the tree, just a tweaking of the mesh. So since Thatcher's algorithm uses a view dependent reduction mechanism, as we move around the detail right around us is improved. It is even possible to add procedural mesh detail at the local level so that you have transient geometry showing cracks or bumps, but with no overhead to the heightmap. The advantages of this technique are manyfold. By placing each patch into shape3d nodes you get FoV culling for free on patches. In addition with isFrequent(false) for geometry updates you can even get your quads to be pushed to display lists. Each patch can get its own texture which means you can have effectively LOD texturing across all the levels. With just a few improvements you could even recycle QuadNode's so that we never have to remove them from the scene, just update them or make them invisible... a more efficient operation than removing a branchgroup. David Yazel =========================================================================== To unsubscribe, send email to [EMAIL PROTECTED] and include in the body of the message "signoff JAVA3D-INTEREST". For general help, send email to [EMAIL PROTECTED] and include in the body of the message "help".