• Announcements

    • khawk

      Download the Game Design and Indie Game Marketing Freebook   07/19/17

      GameDev.net and CRC Press have teamed up to bring a free ebook of content curated from top titles published by CRC Press. The freebook, Practices of Game Design & Indie Game Marketing, includes chapters from The Art of Game Design: A Book of Lenses, A Practical Guide to Indie Game Marketing, and An Architectural Approach to Level Design. The GameDev.net FreeBook is relevant to game designers, developers, and those interested in learning more about the challenges in game development. We know game development can be a tough discipline and business, so we picked several chapters from CRC Press titles that we thought would be of interest to you, the GameDev.net audience, in your journey to design, develop, and market your next game. The free ebook is available through CRC Press by clicking here. The Curated Books The Art of Game Design: A Book of Lenses, Second Edition, by Jesse Schell Presents 100+ sets of questions, or different lenses, for viewing a game’s design, encompassing diverse fields such as psychology, architecture, music, film, software engineering, theme park design, mathematics, anthropology, and more. Written by one of the world's top game designers, this book describes the deepest and most fundamental principles of game design, demonstrating how tactics used in board, card, and athletic games also work in video games. It provides practical instruction on creating world-class games that will be played again and again. View it here. A Practical Guide to Indie Game Marketing, by Joel Dreskin Marketing is an essential but too frequently overlooked or minimized component of the release plan for indie games. A Practical Guide to Indie Game Marketing provides you with the tools needed to build visibility and sell your indie games. With special focus on those developers with small budgets and limited staff and resources, this book is packed with tangible recommendations and techniques that you can put to use immediately. As a seasoned professional of the indie game arena, author Joel Dreskin gives you insight into practical, real-world experiences of marketing numerous successful games and also provides stories of the failures. View it here. An Architectural Approach to Level Design This is one of the first books to integrate architectural and spatial design theory with the field of level design. The book presents architectural techniques and theories for level designers to use in their own work. It connects architecture and level design in different ways that address the practical elements of how designers construct space and the experiential elements of how and why humans interact with this space. Throughout the text, readers learn skills for spatial layout, evoking emotion through gamespaces, and creating better levels through architectural theory. View it here. Learn more and download the ebook by clicking here. Did you know? GameDev.net and CRC Press also recently teamed up to bring GDNet+ Members up to a 20% discount on all CRC Press books. Learn more about this and other benefits here.
  • entries
  • comments
  • views

More Procedural Voxel World Generation

Sign in to follow this  
Followers 0


EDIT2: Fixed. Thanks, Michael Tanczos.EDIT: Forgive my dumbness, but anyone know how I can get the image attachments to actually appear in the body of the post?

Been awhile. I wanted to follow up on the previous post a little bit, but it's been busy lately; I've been to Mexico for a cruise, and have been spending time back home in Wyoming looking for a job. Time to get out of Arizona, methinks; don't think I can take another summer.

In the last TL;DR post, I talked about using a series of noise functions to generate Minecraftian levels. I got as far as creating the landscape and adding caves. I want to flesh out the system a little bit more, tweak the terrain turbulence function a bit, and I want to demonstrate how the underlying gradient function can be used to generate terrain layers and add features.

I have changed somewhat the method I am using for visualization of the output. Initially, I was using PolyVox to build a mesh from a voxel field, and exporting it as an .OBJ for import into Blender to apply lighting and render. For this post, though, I wrote a script that dumps a voxel field to a mesh file ready to be included in a POVRay scene file instead. The script indexes each voxelt and assigns materials to each block face accordingly. It's a slow process, running as it does in Lua and with no optimizations, but on the whole it has sped up the generation of renders, allowing me to re-render just with the click of a button, rather than the tedious process of importing to Blender as an intermediate step. In order to work with the "new" system, I modified the system to ensure that the output of the final function is in the appropriate range. Specifically, a voxel field maps a module by truncating the output of the module to unsigned integer and casting to 8-bit unsigned char, so the output needs to be in the range of [0,255]. This holds with most Minecraft-type clones, as most will use an unsigned char to denote block types.

For the purposes of this discussion, I am interested only in terrain formation. Placement of vegetation (trees, grass, etc) or mobs is not an aspect of this. Particularly, adding vegetation tends to be an explicit process, performed by analyzing terrain, exposure to the sky, proximity of water, etc... and not an implicit process, able to be expressed as a simple function.

[subheading]Building the Ground Layer Functions[/subheading]

The types of ground I am going to model are: dirt, standard stone, semi-rare stone, rare stone, and bedrock. The ground strata are arranged such that the very bottom layer is bedrock which can not be dug, the very top layer is dirt, and the strata of stone is a conglomerate of stone, semi-rare and rare such that the deeper one goes the higher the likelihood of encountering rare stone. Semi-rare appears at all elevation strata, and can sometimes even be found at the surface, but is more rare than standard stone; rare stone is encountered only at depths just above the bedrock. Bedrock acts as a basement layer, unaffected by turbulence in order to protect it and ensure that no openings into the void are allowed.

I am going to build each piece up modularly, combining the results into a whole. The end goal is a function that will tell me the type of block in a gradient ranging from open, to dirt, to stone, to bedrock, with a distribution of rare and semi-rare stone in between. The trick, when faced with something like this, is to break it down into smaller tasks. In particular, the tasks we need to model are:

1) The distribution of rare stone vs. other types of stone
2) The distribution of semi-rare stone vs. normal stone
3) The distribution of dirt vs. stone
4) The distribution of ground vs. open
5) The distribution of bedrock vs. everything else
6) Re-integration of ground shape and caves as outlined in the previous journal entry

Before we begin knocking out these tasks, let's lay down some ground work. First, we'll set up some constants representing the different block types we can use:

{name="Open", type="constant", constant=1},
{name="Dirt", type="constant", constant=2},
{name="Stone", type="constant", constant=3},
{name="SemiRare", type="constant", constant=4},
{name="Rare", type="constant", constant=5},
{name="Bedrock", type="constant", constant=6},

{name="Constant1", type="constant", constant=1},
{name="Constant0", type="constant", constant=0},
Let's also set up a gradient. We're going to set it up just slightly differently than we did before, taking into account the new system. By default, a voxel field is mapped from a (1x1x1) sized area of the function, starting at a specified point. So we'll set up the gradient to range from 0.5 to 0, and we'll remap the output range of this function to [0,1]:

{name="MainGradient", type="gradient", y1=0, y2=0.5},
{name="MainGradientRemap", type="scaleoffset", source="MainGradient", scale=0.5, offset=0.5},

The result of this is a gradient function that outputs 0 for any location where Y>0.5, 1 for any location where Y<0, and a gradient scale in between those points. When mapping to a voxel field this means that the top half of the voxel cube will be open, and the bottom half will be ground, and establishes the ground plane for us to work with.

And now, let's first tackle the distribution of semi-rare vs. normal stone, since this is pretty simple.

{name="SemiRareFBM", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=4, frequency=2},
{name="SemiRareFBMRemap", type="scaleoffset", source="SemiRareFBM", scale=0.5, offset=0.5},
{name="SemiRareSelect", type="select", main_source="SemiRareFBMRemap", low_source="Stone", high_source="SemiRare", threshold=SEMIRARE_DENSITY, falloff=0},

This little bit just sets up an FBM fractal and uses it to select between either Stone or SemiRare. Due to the remapping function, the output of the fractal is (roughly) in the range [0,1] and the amount of SemiRare stone distributed is adjustable via the SEMIRARE_DENSITY parameter.


Next, let's model the distribution of rare stone. Scattering rare stone is similar to scattering semi-rare, except since rare only occurs at depth we need to modify the distribution FBM fractal by multiplying it with the main gradient. We can throw in a few parameters to help make this process adjustable. Note, of course, that as whenever you are working with fractals, you have to expect some experimentation in the selection of parameter values. Here is the code to set up a distribution for rare stone:

{name="RareFBM", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=3, frequency=3},
{name="RareFBMRemap", type="scaleoffset", source="RareFBM", scale=0.5, offset=0.5},
{name="RareFBMScale", type="scaleoffset", source="RareFBMRemap", scale=RARE_GRADIENT_SCALE, offset=0},
{name="RareMult", type="combiner", combiner_type="MULTIPLY", source_0="RareFBMScale", source_1="MainGradientRemap"},
{name="RareMultScale", type="scaleoffset", source="RareMult", scale=RARE_DENSITY, offset=0},
{name="RareSelect", type="select", main_source="RareMultScale", low_source="SemiRareSelect", high_source="Rare", threshold=0.5, falloff=0},

We set up a fractal FBM as with semi-rare, do some finagling of it to remap the output, then multiply it by our main gradient. The output of this we use as a selector to choose between either the stone/semi-rare hybrid function above, or Rare stone. The result looks somewhat like this (image on left is with stone/semi-rare omitted)



As you can see, the deposits of Rare cluster near the bottom of the cube.

The next step is to differentiate stone from dirt and ground from open. We accomplish this by using a chain of selectors:

{name="DirtStoneSelect", type="select", main_source="MainGradientRemap", low_source="Dirt", high_source="RareSelect", threshold=DIRT_THRESHOLD, falloff=0},
{name="GroundSelect", type="select", main_source="MainGradientRemap", low_source="Open", high_source="DirtStoneSelect", threshold=0.000001, falloff=0},

The final output of this module chain shows us our layers of ground:


[subheading]Applying Ground Shape[/subheading]

Now that we have a function to define ground layers, we can re-integrate ground deformation using a Y-axis turbulence function and a noise source. As described in the previous journal post, the ground shape function is a 3D function that acts as a Y-axis turbulence source, "pushing" each point in the voxel volume either toward or away from the threshold line, having the effect of sort of whipping the surface of the ground into a froth. At low frequencies and low turbulence strength values, this method results in fairly interesting rolling terrain, but as turbulence power is increased (to create taller mountains, for instance), or as the frequency or number of octaves is changed, the terrain can grow increasingly unrealistic and convoluted. This might not always be desirable, so in order to bring this under control I am going to add another module to scale the Y-coordinate component of the input to the GroundShape function by an arbitrary amount. As this amount approaches 0, the function acts more and more like a "conventional" heightmap function, perturbing the ground up and down as an entire column, rather than perturbing each voxel by its own amount.

{name="GroundShape", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=4, frequency=2},
{name="GroundYScale", type="scaledomain", source="GroundShape", scaley=GROUND_YSCALE},
{name="GroundTurb", type="turbulence", main_source="GroundSelect", y_axis_source="GroundYScale", y_power=0.5},

This uses just a simple FBM source for turbulence as before, with an adjustable Y-scaling parameter to modify how frothy the ground surface can be. Here is a sequence of images that show how adjusting Y-scale alters the behavior of the turbulence so that it approaches that of a "standard" heightmap.





As you can see, by tweaking the Y-scale you can bring unruly functions somewhat under control. It is even possible, using additional functions and multiple ground shape sources combined via Select or Blend functions, to generate non-homogenous terrain functions that can vary from smoothly rolling plains to wildly convoluted "badlands" terrain with a high turbulence and high Y-scale.

[subheading]Bringing Back the Caves[/subheading]

And, of course, we can bring back the caves.

{name="CaveShape1", type="fractal", fractal_type="RIDGEDMULTI", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=1, frequency=2},
{name="CaveBase1", type="select", main_source="CaveShape1", low_source="Constant0", high_source="Constant1", threshold=1-CAVE_SIZE, falloff=0},

{name="CaveShape2", type="fractal", fractal_type="RIDGEDMULTI", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=1, frequency=2, seed=1323},
{name="CaveBase2", type="select", main_source="CaveShape2", low_source="Constant0", high_source="Constant1", threshold=1-CAVE_SIZE, falloff=0},
{name="CaveMult", type="combiner", combiner_type="MULTIPLY", source_0="CaveBase1", source_1="CaveBase2"},

{name="CaveTurbX", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=3, frequency=3, seed=1001},
{name="CaveTurbY", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=3, frequency=3, seed=1201},
{name="CaveTurbZ", type="fractal", fractal_type="FBM", basis_type="GRADIENT", interp_type="QUINTIC", num_octaves=3, frequency=3, seed=1301},
{name="CaveTurb", type="turbulence", main_source="CaveMult", x_axis_source="CaveTurbX", y_axis_source="CaveTurbY", z_axis_source="CaveTurbZ", x_power=0.25, y_power=0.25, z_power=0.25},

{name="CaveInvert", type="scaleoffset", source="CaveTurb", scale=-1, offset=1},
{name="CaveSelect", type="select", main_source="CaveInvert", low_source="Open", high_source="GroundTurb", threshold=0.5, falloff=0},

The final stage is to perform a select using the main gradient in order to add a thin layer of bedrock at the very bottom of the world:

{name="BedrockSelect", type="select", main_source="MainGradientRemap", low_source="CaveSelect", high_source="Bedrock", threshold=BEDROCK_THRESHOLD, falloff=0},

This ensures that nobody can dig down to the abyss.

Here are a few samples for your perusal:




Sign in to follow this  
Followers 0


So yeah, my bad.. I screwed up in configuring the software right so attachments weren't working. Hopefully they should work fine for you from here on out. mea culpa

Share this comment

Link to comment

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now