Procedural Terrain With Minko 2

Being able to create procedural geometry is very important in a 3D engine. Minko is special because it offers 2 ways to work with the 3D geometry:

  • The streams API: low level but the fastest solution. You have to create and fill the buffers yourself which becomes rapidly annoying and hard to read.
  • The vertex iterators: a high level dynamic API, slow but very very easy to work with.

In this post I will explain how you can create a 3D procedural terrain in just a few lines of code.

TL;DR

You can get the full source code for this example in minko-examples on github.

The Heightmap

A “heightmap” is a texture where each pixel will be used to determine the height of the terrain. Flash offers a very simple and easy way to create a heightmap: the Perlin noise algorithm. It’s available through the BitmapData.perslinNoise() method.

This method will create a greyscale random texture. You can play with the different arguments to get the result you want:

var heightmap : BitmapData = new BitmapData(SIZE, SIZE, false, 0);

heightmap.perlinNoise(200, 200, 8, Math.random() * 0xffffffff, false, true, 7, true);

Here is an example of what you’ll get:

The Geometry

When you have the heightmap, you just have to create a 3D grid. With Minko, you can use the QuadGeometry class. It can be instanciated with arbitrary width/height subdivisions. Then you just have to change the height of the vertices according to the heightmap:

var terrainGeometry : Geometry	= new QuadGeometry(false, SIZE, SIZE, StreamUsage.DYNAMIC);
var vertices : VertexIterator = new VertexIterator(terrainGeometry.getVertexStream());

for each (var vertex : VertexReference in vertices)
{
	var x : uint = (vertex.x + 0.5) * (SIZE - 1);
	var y : uint = (vertex.y + 0.5) * (SIZE - 1);

	vertex.z = heightmap.getPixel(x, y) / 0xffffff;
}

The interesting part here is that this very piece of code will always work. The x, y, and z components can even be stored in different buffers. It doesn’t matter as long as they exist. You can see how the VertexIterator makes it really easy to read/write the vertices components without having to deal with how they were built and how they are actually setup underneath.

The counterpart is that this code is not as fast as the low level stream API. But it most cases – like here – the possible performance hint is not really an issue.

You can wrap this Geometry up in a mesh object to add it to the scene:

terrain = new Mesh(
	terrainGeometry,
	{
		lightEnabled	: true,
		diffuseColor	: 0xffffffff,
	}
);

terrain.transform
	.appendRotation(Math.PI * 0.5, Vector4.X_AXIS)
	.appendTranslation(0, 0.5, 0)
	.appendUniformScale(100);

scene.addChild(terrain);

Here is what you will get (with the light enabled on the scene):

The Shader

A completely white terrain is not very interesting. Let’s add some colors! To do this, we will use a technique called “texture splatting”. It’s very simple: we select the diffuse texture according to the height of the vertex. Here is a very simple (and hugly) implementation:

// here z is 'up' because the QuadGeometry is in the (x, y) plane
uv = float2(vertexUV.x, vertexXYZ.z);

And here is the full shader with the directional lighting copy/pasted from the BasicShader:

public class TerrainShader extends BasicShader
{
	public function TerrainShader(target : RenderTarget = null, priority : Number = 0.0)
	{
		super(target, priority);
	}
	
	override protected function getPixelColor() : SFloat
	{
		var diffuseMap	: SFloat	= meshBindings.getTextureParameter('diffuseMap');
		// texture spatting
		var uv		: SFloat	= interpolate(float2(vertexUV.x, vertexXYZ.z));
	
		var diffuse	: SFloat	= sampleTexture(diffuseMap, uv);
		
		// directional lighting
		if (sceneBindings.getConstant('lightEnabled', false)
			&& meshBindings.getConstant('lightEnabled', false))
		{
			var lightDirection	: SFloat = sceneBindings.getParameter("lightDirection", 3);
			var normal		: SFloat = normalize(
				interpolate(
					float4(multiply3x3(vertexNormal, localToWorldMatrix), 1)
				)
			);
			var lambert		: SFloat = saturate(negate(dotProduct3(
				normal,
				normalize(lightDirection)
			)));
			
			lambert.scaleBy(sceneBindings.getParameter("lightDiffuse", 1));
			
			var lightColor		: SFloat = add(
				// ambient
				multiply(
					sceneBindings.getParameter("lightAmbient", 1),
					sceneBindings.getParameter("lightAmbientColor", 3)
				),
				// diffuse
				multiply(
					lambert,
					sceneBindings.getParameter("lightDiffuseColor", 3)
				)
			);
			
			diffuse = float4(multiply(diffuse.rgb, lightColor), diffuse.a);
		}
		
		return diffuse;
	}
}

And here is an example of texture to use as the ‘diffuseMap’ property of our mesh:

Conclusion And Future Works

It’s very nice to be able to work so easily with the geometry! The VertexIterator makes everything very simple when you have to work with 3D geometry.

The splatting function in the vertex shader is really hugly. It needs a lot of refinement.

The water “simulation” can also be improved. It’s just a blue quad and adding reflections, refraction, waves and caustics would greatly enhance the final result.

Adding a ray/triangle intersection would also make it possible to edit the terrain using the mouse. It’s something very cool and it should be easy to do so… stay tuned!

In the meantime, you can get the full source code for this example in minko-examples on github. If you have questions or suggestions, you can post in the comments or on Aerys Answers!

11 thoughts on “Procedural Terrain With Minko 2

  1. Promethe Post author

    I’m glad you like it guys.
    I’m a little busy right now to work on the new features but there is a lot of amazing stuff going on.

    Stay tuned!

    Reply
  2. Simon Gladman

    Amazing – that means I can create 3d meshes from cellular autamita generated by Pixel Bender.

    Fantastic work guys, looks like my free time next week is not free any more!

    Simon

    Reply
    1. Promethe Post author

      I can create 3d meshes from cellular autamita generated by Pixel Bender

      That’s even better : you can do your cellular automata on the GPU using AS3 shaders :)
      If you manged to do it with Pixel Bender, it should be esay to port it using AS3 shaders.
      If you need some help, I’ll be happy to help and you can ask questions on Answers.

      Actually, I think a cellular automata would greatly benefit from hardware acceleration so that would be awesome to make it work with AS3 shaders!

      Reply
    1. Promethe Post author

      That’s awesome! But you’re using only a fraction of the possibilities here.
      Your “step()” method and your PixelBender shader should be implemented on the GPU too using an AS3 shader. This way, your entire process will be hardware accelerated.

      Reply
  3. Pingback: Minko Weekly Roundup #1 | Jean-Marc Le Roux

  4. Pingback: New Minko Feature: ByteArray Streams | Jean-Marc Le Roux

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>