Procedural Mountains and Slacking Off
Goblinson Crusoe rpg isometric turn based lazy slacker
ApochPiQ's Dude, Just Chill Out entry made me feel bad and dirty. I've been neglecting GC lately, and I've been neglecting this blog as well.
Since the last time I updated about GC, I have gotten quite a bit done. However, so much of it has just been bookkeeping, general clean-up, some refactoring, and so forth. I fixed some bugs in the general framework, tidied up some use cases, etc... For now, I haven't done anything further with the YAML-based variant of the framework, and am continuing to work in Lua. I just... I like Lua. It feels right to me, after so many years of working with it. C++ just feels so cold and unyielding, and I don't really like working with it much anymore, except for those times that I have to.
I've got a sort of TODO list of things I want to blog about (it's pretty long), and hopefully I'll get to them in the near future. Time. Man, there just isn't enough of it. With one kid at 10 months and another one on the way, set to arrive in July, plus work and wife and winter, there just isn't any time.
Anyway, in order that this update not be empty and completely useless, I'll just talk about mountains. I've started out writing numerous posts about procedural generation and how it has benefitted me in building GC, but each time the post starts to balloon into this gigantic thing. Procedural generation is awesome, but it's a big topic as well. So I'll just talk about mountains instead.
Cellular noise. I'm sure that almost everyone has heard of it or seen it. It's an old stand-by in the procedural world. In some places, it might be called Worley noise or Voronoi noise. Cellular noise is built by seeding the coordinate space with a scattering of randomized points, then for each location in the space you determine the nearest seed point. Variants of cellular noise might assign a certain value to all points that "belong" to a given seed point, or might calculate the distance to the nearest seed point, or the distance to the nearest N seed points, and use these values in various forms.
Of particular interest to me in mountain-making is the variant of cellular noise that is often labeled as F2-F1. In this case, F1 and F2 are the distances to the nearest and second nearest seed points. By subtracting the first nearest distance from the second nearest distance, you end up with a rather remarkable pattern:
Looks like mountains to me. A little too smooth, perhaps, a bit too crystalline, but that is easily remedied with just a little bit of fractal fBm distortion applied in the X and Y axes:
There we go. However, that is mountains (plural) and I basically want just mountain (singular). Or, at least, I need an isolated chunk of mountain, and not the whole mountain range. So I bring in my old amigo, FuzzyDisk. FuzzyDisk is a cross section of a sphere in which values start at 1 at the center and fade out to 0 at radius. I can bias the output of fuzzy disk a little bit to tighten up the transition, and obtain a mask that looks something like this:
A simple multiplication operation later, and I have isolated my mountain piece:
Now the fun begins. I have set up in my project's workspace folder a .blend file for Blender. It includes the standard Goblinson Crusoe light setup, and some tri-planar materials for various rocks, dirts, cliffs, etc... One of these materials looks something like this:
Looks a little ugly, but essentially what it does is this:
1) Tri-planar map a stone material to the bulk of the mountain.
2) Mix the stone material with a white snow material based on
b) dot-product of the surface normal with the vertical axis
3) Mix the stone/snow mix with a greenish material at the base of the mountain, again based on a) and b) above
4) Mix between the final color and black based on elevation to "snip" out the lowest portions of the mesh, isolating the mountain pieces
5) Mix between white and black using the same elevation factor as (4), and use the result as the alpha channel.
This material is applied to the mesh, which is constructed by subdividing a plane and applying the mountain heightmap as a displacement modifier, and tweaking the scale of the displacement to suit:
Then, the thing is rendered. The result will be a mountain sprite that can be imported into GC. The sprite is anti-aliased, and smoothly blended at the base to blend well with the ground terrain. Here is a mountain sprite:
I'll do a bunch of variants, pack them into a material and import them into the game. Here is what they look like in game:
Simple as pie. The process isn't fully automated (I do still have to import the heightmap into Blender and hit render) but it is very fast. I can generate numerous variants by re-seeding the cellular generator, and by tweaking the material and textures I can create variants for different types of mountains. It's quick and easy, and it certainly results in better mountains than I would ever be able (or willing) to create by hand like a real artist would.
There. Now I don't feel bad for having posted another worthless post. This was only partially worthless. Conscience soothed, back to slacking off.