So this isn't my first time at this rodeo, not by any means. The answers I came up with for the raytraced planets, though, don't necessarily work with a more traditional renderer like this one (and I can't really swear that I fully solved all the problems, anyway -- the planets drawn by the raytracer still suffered from substantial artifacts, partly due to how I projected the terrain information onto the sphere, and partly due to inherent hassles with the midpoint-displacement fractal.) I couldn't really take that process and port it to the new engine, not if I was going to render the terrain in traditional fashion by wrapping a texture onto the planet.
I pondered all kinds of alternatives. Should I generate a flat (square) map of the planet, manually distort it, then wrap it around the sphere? Should I maybe use a cube map? Perhaps I could create a texture that defines good distorted texture coordinates, then use that to create second-order texture coordinates used to map the actual data. You get this idea: this is a hard problem. In fact, this is one of the hard problems. Creating a flat representation of the curved Earth is something that has all kinds of vital applications, and a zillion different ways have been proposed to do it, and they all have flaws. The fundamental issue is that the surfaces are too dissimilar. You can't turn one into the other without cutting it and distorting it. You can push the cuts and distortions around to a certain degree, but like pushing down a lump in a carpet it'll just pop up elsewhere. We deal with the situation in the real world by ensuring that distortion is mostly in places no one cares about, like the poles, and cuts run through oceans instead of countries, but I can't make too many assumptions about what parts of these planets will get looked at.
So yeah: this is what mapmakers describe in technical terms as "a pain in the butt." Any square texture will be annoyingly distorted and the math to avoid that just gets more and more complex. It's too bad, because the sphere itself is a perfect, even triangulation. If only I had some sort of representation kind of like the raytracer had, where the "texture" representing the surface of the planet had one point for every point on the sphere...
Oh hey! I do! It's called the vertex shader! I can compute an altitude for each point on the sphere, then feed it as an array of arbitrary per-vertex data into the vertex shader. The vertex shader will then in turn pass it along to the pixel shader, in the process interpolating between the altitude points for the actual pixel that gets drawn. I even know an excellent method for building fractal continents on a sphere -- I implemented something like this as part of my college computer graphics course, more years ago than I care to remember. Score! This should work perfectly! So I implemented it -- the hardest, most epithet-inducing part being convincing the vertex shader to accept my arbitrary data; it turns out that, in OpenGL, if you start rendering a display list it kills any such assignments you've already made -- added some simplistic stuff to just paint fullbright colors based on altitude, and the result was...
Well, uh. Hmm. If this was 1996 that would be pretty cool, but it's not really all I was hoping for. On the plus side, there's no distortion, but on the minus side the major problem is obviously the straight lines in between points where fractal data exists. There just isn't enough data on the triangulated sphere to create really high-quality imagery. I could crank up the number of triangles in the sphere, but the triangle count goes up very fast as the triangulation level increases, and one problem with this particular fractal is that the expense of calculating it goes up linearly with the number of points involved.
So I'd sort of solved the projection problem, but now I have a data problem. How to improve the resolution here? Traditionally, the way to add lots of fine detail to a surface is a detail texture, another texture that's much smaller and tiles repeatedly, blended on top of the main texture. But if I'm trying to tile the sphere with a square texture I'm right back where I started trying to project a flat map onto a sphere. Dangit!
At this point I decided to back up a bit. I'm not quite at the same point I was before; previously I was trying to wrap a single texture onto the sphere, whereas now I want to tile a texture multiple times onto a sphere. I consulted Professor Google to see if there were any clever way to do this, and to my surprise I found something: a paper pointing out the in retrospect obvious point (but then, all the good ones are obvious in retrospect, aren't they?) that if you take one of the simple shapes used to approximate the sphere, namely an octahedron -- think D-8 -- and unfold it you'll notice that it is made up of four diamond shapes. And a diamond is of course just a parallelogram. And a parallelogram is just a skewed rectangle.
So here's the thread to follow! Rectangle to diamond to octahedron to triangulated sphere. It should be possible to lay out textures on the diamonds in such a fashion that they join up perfectly at every edge. I actually hand-made a little piece of papercraft to prove this to myself; I'd attach a photo here, but it got crumpled up by accident so it wouldn't look very impressive. Instead, here's a representation of it in the engine.
Yes, as expected, the textures join up -- although not neatly, as is still obvious if we go ahead and add triangles to turn the icosahedron into a sphere:
The texture is kinked where it crosses the border between original icosahedron triangles. But on the other hand, it does tile without the outrageous distortion seen when using ordinary map projections:
So what the heck, let's keep going. What happens if we whip up a noise texture in Photoshop, tile it onto the sphere, and in the pixel shader use that texture's brightness value to nudge the altitude sent along from the vertex shader a bit?
Hey. Not half bad. Maybe not even a quarter bad! Compare to the image up at the top of this seemingly endless LJ post: we've got some extremely convincing variation in the coastlines and the borders between climate zones. We even have enough data that it looks good when we're closer to the planet, and the graphics card's linear filtering on the detail texture smooths it out further:
So needless to say I'm pretty chuffed about all this.
Now, not all problems are solved with this method. I didn't just want to use detail texturing to tweak the borders; I want to tile some normals onto the sphere, so we see lots of localized rough terrain. But if we actually get a good look at the detail texture, the places where it "kinks" and/or reflects as it crosses the original icosahedron triangle borders stand out noticeably. There are a couple of possible approaches to resolve this. If it was possible to effectively use two spheres, one of which is rotated a certain amount, I could choose the detail texture from whichever one is less distorted at a particular point. I could also come up with a more clever way of distorting the texture coordinates on the original diamonds, such as basing them on the distance from the poles; this would improve the kinking, although not the reflection. I could use two detail textures designed to tile with each other, instead of just one that's reflected on alternate diamonds; this would fix the reflection, although not the kinking. And it might also be that with a correctly chosen detail texture, this problem won't be noticeable, but I really don't think I'd get that lucky.
Summary: More work remains to be done in this area.