flopoloco's picture

Terrain Heightmapping

A simple example on how to do simple heightmapping with OpenTK, enjoy...

Height Mapping In OpenToolkit

AttachmentSize
OpenTKHeightmap.png61.08 KB
OpenTKTutorials.rar470.36 KB

Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
objarni's picture

Hoho! Nice.

flopoloco's picture

Thanks, preety speedy also if you noticed :P

objarni's picture

How do you pick the Color for each Quad? Or what kind of primitives is it?

flopoloco's picture

I have loaded all the pixels of the height map in a 2D array (512x512), I use these rgb values (of the image file) for two purposes, (first) getting the height and (second) the color value for each specific rendering step. Points are very easy to render with in a rendering loop, you simply add a GLVertex in it and you are OK. The tricky part on the other hand for rendering Quads and Lines successully, is that you will have to advance a step in order to form the slopes correctly. By meaning that will have to do 2 or 4 GLVertex calls in one loop, that's why you will have to advance the array indices.

For instance, let's say: I currently am in the 16th index of the array, I use a step of 8 and I want to draw a line. Then I do this.

GL.Begin(BeginMode.Lines);
GL.Vertex2(x, y); // current location
GL.Vertex2(x+step, y+step); // get values of the the next step.
GL.End();

Same goes for quads...

I hope I explain this properly (I am not much experienced).

objarni's picture

So is it quads, triangles or something else..?

puklaus's picture

Heh, pick the code and see ;)

btw i read somewhere that triangels are faster than quads but triangle lists are faster..one of my terrain renderer uses triangle lists and it uses lod.. i think vbo is even faster.

flopoloco's picture

The screenshot has quads :), this example however is not considered good for real time rendering... it definitely needs optimizations (like lod, and culling)...

ruba's picture

Hi,
I have this error

 this.Keyboard.KeyUp += new EventHandler(TutorialTerrain_KeyUp);
			this.Mouse.ButtonDown += new EventHandler(TutorialTerrain_ButtonDown);

Error 2 No overload for 'TutorialTerrain_ButtonDown' matches delegate 'System.EventHandler' c:\users\ruba\documents\visual studio 2010\Projects\WindowsFormsApplication2\WindowsFormsApplication2\TutorialTerrain.cs 83 29 WindowsFormsApplication2

Error 1 No overload for 'TutorialTerrain_KeyUp' matches delegate 'System.EventHandler' c:\users\ruba\documents\visual studio 2010\Projects\WindowsFormsApplication2\WindowsFormsApplication2\TutorialTerrain.cs 82 36 WindowsFormsApplication2

and when I tried to run the .exe file, I got a blank blue window :(
Please help me

thanks a lot.

flopoloco's picture

Hi there ruba, I have updated the code. OpenTK API has changed a bit since I created this example, I did not expect it to "break" :D

I can't edit the uploaded files in the initial post, use the text above instead. Works fine with OpenTK1.0!

TutorialTerrain.cs

using System;
using System.Drawing;
using System.IO;
using OpenTK;
using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using OpenTK.Input;
 
namespace OpenTKTutorials
{
 
	/// <summary>
	/// Terrain Example created by flopoloco
	/// Credits: [] Nehe for the knowledge
	/// 		 [] OpenTK Team for the library
	/// 
	/// How this application works:
	/// 1. Load a grayscale heightmap image
	/// 2. Store infomation of image useful for rendering the 3D terrain (a 2D height array, and dimensions of image)
	/// 3. Dynamically render the terrain / Interact with application
	/// </summary>
	public class TutorialTerrain : GameWindow
	{
		// Store width and height of terrain
		Size size;
		// Array used for height information (Pixel color of the image is the height value)
		byte [,] heightTable;	
 
		// Current rendering state
		BeginMode terrainRenderStyle = BeginMode.Quads;	
 
		// Terrain rendering detail (the less the better/slower)
		int renderSteps = 10;							
		public int RenderSteps
		{
			get { return renderSteps; }
			set
			{
				if (value < 1) renderSteps = 1;
				else if (value > 5000) renderSteps = 5000;
				else renderSteps = value;
			}
		}
 
		// Camera
		float cameraPositionX = 0f, cameraPositionY = 300.0f, cameraPositionZ = 500f;
 
		// Help box strings
		string tutTxt = "Page Up/Down\t: Camera height\n"+
						"Arrow keys\t: Camera position\n"+
						"1\t: Points rendering\n"+
						"2\t: Lines rendering\n"+
						"3\t: Quads rendering\n"+
						"+\t: Increase terrain quality\n"+
						"-\t: Decrease terrain quality\n"+
						"ESC\t: Quit applicaton\n";
 
		// Update statistics information every 1 second
		System.Diagnostics.Stopwatch stopwatch = new System.Diagnostics.Stopwatch();
 
		// Count the number of opengl vertex calls for a render loop
		uint diagVertices = 0;
 
 
 
 
		public TutorialTerrain() : base(640, 480)
		{
			// Gettin input with "KeyUp" events is useful when you want to input single key presses
			// (not constantly repeated)
			this.Keyboard.KeyUp += new EventHandler<KeyboardKeyEventArgs>(TutorialTerrain_KeyUp);
			this.Mouse.ButtonDown += new EventHandler<MouseButtonEventArgs>(TutorialTerrain_ButtonDown);
			stopwatch.Start();
		}
 
 
 
 
		void TutorialTerrain_KeyUp(object sender, KeyboardKeyEventArgs e)
		{
			switch (e.Key)
			{
				// Exit the application
				case Key.Escape:
					this.Exit();
					break;
				// Switch rendering to Points
				case Key.Number1:
					this.terrainRenderStyle = BeginMode.Points;
					break;
				// Switch rendering to Lines
				case Key.Number2:
					this.terrainRenderStyle = BeginMode.Lines;
					break;
				// Switch rendering to Quads
				case Key.Number3:
					this.terrainRenderStyle = BeginMode.Quads;
					break;
				// Increase terrain quality
				case Key.Plus:
					RenderSteps -= 1; // Terrain quality gets better by descreasing the steps
					break;
				// Decrease terrain quality
				case Key.Minus:
					RenderSteps += 1; // Terrain quality gets lower by increasing the steps
					break;
			}
		}
 
 
 
 
		void TutorialTerrain_ButtonDown(object sender, MouseButtonEventArgs e)
		{
			if (e.Button == MouseButton.Right)
			{
				System.Windows.Forms.MessageBox.Show(tutTxt, "Help", System.Windows.Forms.MessageBoxButtons.OK);
			}
		}
 
 
 
 
		protected override void OnUpdateFrame(FrameEventArgs e)
		{
			// Camera position
			if (Keyboard[Key.PageUp])
				cameraPositionY += 100.0f * (float)e.Time;
 
			if (Keyboard[Key.PageDown])
				cameraPositionY -= 100.0f * (float)e.Time;
 
			if (Keyboard[OpenTK.Input.Key.Left])
				cameraPositionX -= 100.0f * (float)e.Time;
 
			if (Keyboard[OpenTK.Input.Key.Right])
				cameraPositionX += 100.0f * (float)e.Time;
 
			if (Keyboard[OpenTK.Input.Key.Up])
				cameraPositionZ -= 100.0f * (float)e.Time;
 
			if (Keyboard[OpenTK.Input.Key.Down])
				cameraPositionZ += 100.0f * (float)e.Time;
		}
 
 
 
 
		protected override void OnLoad(EventArgs e)
		{			
			// Set up opengl scene
			GL.ClearColor(Color.SteelBlue);	// Set color
			GL.PointSize(3f);				// Set size of points
			GL.Enable(EnableCap.DepthTest);	// Enable depth test capabilities
 
			// Load heightmap
			LoadHeightMap(@"..\..\heightmap.png");
		}
 
 
 
 
		protected override void OnResize(EventArgs e)
		{
			base.OnResize(e);
 
            GL.Viewport(0, 0, Width, Height);
 
            float aspect_ratio = Width / (float)Height;
            Matrix4 perpective = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, aspect_ratio, 1f, 5000f);
            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix(ref perpective);
		}
 
 
 
 
		protected override void OnRenderFrame(FrameEventArgs e)
		{
			base.OnRenderFrame(e);
 
			GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
 
			Matrix4 lookat = Matrix4.LookAt(cameraPositionX, cameraPositionY, cameraPositionZ, 0f, 0f, 0f, 0f, 1f, 0f);
 			GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadMatrix(ref lookat);
 
			// Render heightmap
			this.RenderHeightmap();
 
			// Update FPS and diagnostics on window title every 1 second
			if (stopwatch.ElapsedMilliseconds > 1000.0)
			{
				this.Title = "FPS: " + (1.0 / e.Time).ToString() + " | GLVertex3 calls: " + diagVertices.ToString() + " | Right-click for help";
				stopwatch.Reset();
				stopwatch.Start();
			}
 
			this.SwapBuffers();
		}
 
 
 
 
		/// <summary>
		/// Generates a heightmap out from an image file
		/// </summary>
		/// <param name="filepath">Path to image</param>
		private void LoadHeightMap(string filepath)
		{
			if (File.Exists(filepath))										// File exists
			{
				Bitmap bitmap = new Bitmap(filepath);						// Treat file as an image
				size = new Size(bitmap.Width, bitmap.Height);				// Store image dimensions
				heightTable = new byte[size.Width, size.Height];			// Initiate heightTable arrays
 
				// Loop through all pixels of the image and store their colors in the heightTable array
				for (int bx = 0; bx < size.Width; bx++)
				{
					for (int by = 0; by < size.Height; by++)
					{
						// Since image is grayscale, we can use any color channel
						heightTable[bx, by] = bitmap.GetPixel(bx, by).R;
					}
				}
			}
		}
 
 
 
 
		/// <summary>
		/// Simple heightmap renderer
		/// </summary>
		private void RenderHeightmap()
		{
			// Reset the vertice diagnostic variable
			diagVertices = 0;
 
			// Center terrain in the middle of the scene
			GL.Translate(-size.Width / 2, 0.0, -size.Height / 2);
 
			// Set the selected BeginMode
			GL.Begin(terrainRenderStyle);
 
			for (int bx = 0; bx < (size.Width - RenderSteps); bx += RenderSteps)
			{
				for (int bz = 0; bz < (size.Height - RenderSteps); bz += RenderSteps)
				{
					// Paint the current primitive based on the heightmap's color value
					GL.Color3(heightTable[bx, bz], heightTable[bx, bz], heightTable[bx, bz]);
 
					// If user wants to display points
					if (terrainRenderStyle == BeginMode.Points)
					{
						GL.Vertex3(bx, heightTable[bx, bz], bz);
 
						diagVertices++;
					}
 
					// If user wants to display lines
					else if (terrainRenderStyle == BeginMode.Lines)
					{
						// 0, 0 -> 1, 0
						GL.Vertex3(bx, heightTable[bx, bz], bz);
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz], bz);
						// 1, 0 -> 1, 1 
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz], bz);
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz + RenderSteps], bz + RenderSteps);
						// 1, 1 -> 0, 1
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz + RenderSteps], bz + RenderSteps);
						GL.Vertex3(bx, heightTable[bx, bz + RenderSteps], bz + RenderSteps);
						// 0, 1 -> 0, 0
						GL.Vertex3(bx, heightTable[bx, bz], bz);
						GL.Vertex3(bx, heightTable[bx, bz + RenderSteps], bz + RenderSteps);
 
						diagVertices += 8;
					}
 
					// If user wants to display quads
					else if (terrainRenderStyle == BeginMode.Quads)
					{
						// 0,0
						GL.Vertex3(bx, heightTable[bx, bz], bz);
						// 1,0
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz], bz);
						// 1,1
						GL.Vertex3(bx + RenderSteps, heightTable[bx + RenderSteps, bz + RenderSteps], bz + RenderSteps);
						// 0,1
						GL.Vertex3(bx, heightTable[bx, bz + RenderSteps], bz + RenderSteps);
 
						diagVertices += 4;
					}
				}
			}
 
			GL.End();
		}
	}
}

P.S.

janequorzar's picture

I do not mean to be a stick in the mud here, but I just have to know.. because this part is a bit confusing. I got the original code in your RAR file to work. Pretty easily actually. ( I just reset the paths to the two OpenTK files you supplied with the code. ) But I look and I see GL and GLU commands in there.

Then I look at the above code you have here, and I see that you replaced the GLU commands with Matrix4 commands.

Can you explain to me why the change or what the difference is ? So I am not sure what got "Corrected".

Keep in mind, I am not a C# noob, but I am an OpenGL noob. So GLU and GL is still new to me. I have used Matrix4 in other languages.

EDIT : Is it that these three are replacing the GLU commands ?

using OpenTK.Graphics.OpenGL;
using OpenTK.Graphics;
using OpenTK.Input;

Am I to assume that GLU is an older library and OpenTK has replaced it ?