Getting Normals from a height map(C#)

Started by
3 comments, last by JDUK 19 years, 6 months ago
thanks to the superb tutorial @ http://users.pandora.be/riemer/index.html i have managed to get a heightmap reading program running :) Now i need a way to calculate normals for the wireframe mesh I could use a link to a tutorial or if its simple enough to explain then please feel free :) If its of any help here is my code:

using System;
using System.IO;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.DirectX.DirectInput;

namespace TerrainMapper
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;
		private Microsoft.DirectX.Direct3D.Device device = null;
		private float angle = 0.3f;
		private float UD = 0.3f;
		private VertexBuffer vb;
		private  IndexBuffer ib = null;
		private int TWIDTH = 128;
		private int THEIGHT = 128;
		private  int [] indices;
		private int[,] HeightMap;
		private FileStream fs;
		private BinaryReader br;
		private CustomVertex.PositionColored[] verts;
		private bool FullScreen = true;
		private Microsoft.DirectX.DirectInput.Device keyb;
        		
		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();
			this.SetStyle(ControlStyles.AllPaintingInWmPaint | ControlStyles.Opaque, true);
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(292, 273);
			this.Name = "Form1";
			this.Text = "Form1";
			this.Load += new System.EventHandler(this.Form1_Load);

		}
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]	
		public void InitializeGraphics()
		{
			//Setup Device Perameters
			PresentParameters prams = new PresentParameters();
			//ShowProgram in window
			prams.Windowed = ! FullScreen;
			prams.SwapEffect = SwapEffect.Discard;
			if ( FullScreen)
			{
				prams.BackBufferCount = 1;
				prams.BackBufferFormat = Format.X8R8G8B8; 
				prams.BackBufferWidth = 800;
				prams.BackBufferHeight = 600;
			}
			//Enable Depth(z) buffering
			
			
			// Create Device, Set Hardware mode and software vertex processing
			device = new Microsoft.DirectX.Direct3D.Device(0,Microsoft.DirectX.Direct3D.DeviceType.Hardware,this,CreateFlags.SoftwareVertexProcessing, prams);
			//WireFrame Mode
			device.RenderState.FillMode = FillMode.WireFrame;
			//No culling
			device.RenderState.CullMode = Cull.None;		
		}
		protected override void OnPaint(System.Windows.Forms.PaintEventArgs e)
		{
			// Set background to black
			device.Clear(ClearFlags.Target, Color.Black, 1.0f,0);
			device.BeginScene();
			device.VertexFormat = CustomVertex.PositionColored.Format;
			//Read from Vetex Buffer
			device.SetStreamSource(0,vb,0);
			//Tell device where the Index Buffer is;
			device.Indices = ib;
			device.Transform.World = Matrix.Translation(-THEIGHT/2, -TWIDTH/2,0)*Matrix.RotationZ(angle)*Matrix.RotationX(UD);
			device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0, TWIDTH*THEIGHT,0, indices.Length/3);
			//Draws from Index Buffer.
			device.EndScene();
			device.Present();
			this.Invalidate();	
			ReadKeyboard();
		}
		private void SetupCamera()
		{
			device.Transform.Projection = Matrix.PerspectiveFovLH((float)Math.PI/4, this.Width/this.Height, 1f, 150f);
			device.Transform.View = Matrix.LookAtLH(new Vector3(0,-80,90), new Vector3(0,-5,0), new Vector3(0,1,0));
			//Switch of lighting as there are no normals to reflect light
            device.RenderState.Lighting = false;
			//Switch culling off 
			device.RenderState.CullMode = Cull.None;			
		}
		
		private void Form1_Load(object sender, System.EventArgs e)
		{
		
		}
		public void LoadMapData()
		{
			//Setup HeightMap data
			HeightMap = new int[TWIDTH,THEIGHT];
			//Open a Filestream
			fs = new FileStream(@"..\..\HeightMap.RAW",FileMode.Open, FileAccess.Read);
			//Create a binary reader
			br = new BinaryReader(fs);
			//Loop through .RAW file and assign data to HeightMap
			for(int x = 0; x< THEIGHT; x++)
			{
				for(int y = 0; y <TWIDTH; y++)
				{
					int h =(int)(br.ReadByte()/5);
					HeightMap[TWIDTH-1-y,THEIGHT-1-x] = h;
				}
			}
			br.Close();
		}
		public void VertexSetup()
		{
			//Setup Vertex Buffer
			vb = new VertexBuffer(typeof(CustomVertex.PositionColored), TWIDTH*THEIGHT,device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);
			//vb.Created += new EventHandler (this.OnVbCreate);
			//OnVbCreate(vb,null);
			//Setup Vertex Array
			verts =  new CustomVertex.PositionColored[TWIDTH*THEIGHT];
			//Move through loop asigning values to verticies
			for (int x=0;x<TWIDTH;x++)
			{
				for (int y=0; y<THEIGHT;y++)
				{
					verts[x+y*TWIDTH].SetPosition(new Vector3(x, y, HeightMap[x,y]));
					verts[x+y*TWIDTH].Color = Color.White.ToArgb();
				}
			}
			//Consine data to vertex buffer
			vb.SetData(verts,0,LockFlags.None);
		}		
		public void IndexSetup()
		{
			//Setup Index Buffer
			ib = new IndexBuffer (typeof(int), (TWIDTH-1)*(THEIGHT-1)*6, device, Usage.WriteOnly, Pool.Default);
			//ib.Created+= new EventHandler(this.OnIbCreate);
			//OnIbCreate(ib,null);
			//Setup array of indexed vertexes
			indices = new int [(TWIDTH-1)*(THEIGHT-1)*6];
			// Loop through and assign indexed vertexes
			for (int x=0;x<TWIDTH-1;x++)
			{
				for (int y=0; y<THEIGHT-1;y++)
				{
					indices[(x+y*(TWIDTH-1))*6] = (x+1)+(y+1)*TWIDTH;
					indices[(x+y*(TWIDTH-1))*6+1] = (x+1)+y*TWIDTH;
					indices[(x+y*(TWIDTH-1))*6+2] = x+y*TWIDTH;
					indices[(x+y*(TWIDTH-1))*6+3] = (x+1)+(y+1)*TWIDTH;
					indices[(x+y*(TWIDTH-1))*6+4] = x+y*TWIDTH;
					indices[(x+y*(TWIDTH-1))*6+5] = x+(y+1)*TWIDTH;
				}
			}
			ib.SetData(indices, 0, LockFlags.None);
		}

		public void InitializeKeyboard()
		{
			keyb = new Microsoft.DirectX.DirectInput.Device(SystemGuid.Keyboard);
			keyb.SetCooperativeLevel(this, CooperativeLevelFlags.Background | CooperativeLevelFlags.NonExclusive);
			keyb.Acquire();
		}
		private void ReadKeyboard()
		{
			KeyboardState keys = keyb.GetCurrentKeyboardState();
			if (keys[Key.LeftArrow])
			{
				angle+=0.03f;
			}
			if (keys[Key.RightArrow])
			{
				angle-=0.03f;
			} 
			if (keys[Key.UpArrow])
			{
				UD+=0.03f;
			}
			if (keys[Key.DownArrow])
			{
				UD-=0.03f;
			} 
			if (keys[Key.Escape])
			{
				this.Close();
			}
		}
		
		static void Main() 
		{
			using(Form1 frm = new Form1())
			{							
				frm.InitializeGraphics();
				frm.InitializeKeyboard();
				frm.SetupCamera();
				frm.LoadMapData();
				frm.VertexSetup();
				frm.IndexSetup();
				frm.Show();
				try
				{
					Application.Run(frm);
				}
				catch(Exception e)
				{
				}
				
			}
		}	
	}
}



Advertisement
Hello again.

I couldn't recall any great terrain normal tutorials, but I googled this and it looks pretty good, and to the point.

If you know anything about vector math, you'll be way ahead. Basically, by constructing two vectors from the three points of a triangle and doing a cross product operation, you can obtain a vector that is perpindicular to both vectors on the triangle, or normal to the triangle. Even if you don't understand the concept of vectors, there's some reduced math given on the above page, although I suggest you learn about vectors in the long run.

The problem you run into with a separate face normal for each triangle is that each polygon shows up distinctly in the presence of a light source. The solution is to specify vertex normals. This is analogous to drawing a triangle with each vertex having a different color. The result is a blending of light reflectivity across each triangle, making the terrain [appear] smoother.

Long story short, each vertex normal is a sum of each surrounding triangle's face normal.

Sorry if that was too much/not enough information. Maybe someone else has a better article bookmarked somewhere, since I don't know how good this one is.

Edit: And don't forget to normalize your vectors!
Thanks PiE3 your a Star & you get a big 5 rating from me :)
Your spot on with all the links you've been posting... Top Stuff.
Quote:Original post by JDUK
Thanks PiE3 your a Star & you get a big 5 rating from me :)
Your spot on with all the links you've been posting... Top Stuff.


No problem.

I almost forgot about the Vertex3 class you can use with Managed DirectX. You can do cross product multiplication and normalization within the Vector3 class, although I still suggest you learn what's behind it.

Good Luck!
Ok im back after reading the links you sent me and the material on normals in "3D games Realtime rendering and software technology".

I have read a program that compiles and works that that uses this to calculate the normals
for ( int i = 1; i < numHeight-1; i ++)	{		for ( int j = 1; j < numWidth-1; j ++)		{		// Calculate the real normals by using the cross product of the vertex' neighbours		Vector3 X = Vector3.Subtract(verts[i*numWidth+j+1].Position,verts[i*numWidth+j-1].Position);		Vector3 Z = Vector3.Subtract(verts[(i+1)*numWidth+j].Position,verts[(i-1)*numWidth+j].Position);		Vector3 Normal = Vector3.Cross(Z,X);		Normal.Normalize();		verts[i*numWidth+j].Normal = Normal;				}		}


From what i have read the calculation u need to perform beforeyou get the cross porduct is:

v1 = t2 - t1
v2 = t3 - t1


so i would assume in the above code that when assigning data to X & Z that you would:
Vector3.Subtract(t2,t1)
&
Vector3.Subtract(t3,t1)

t1 being the same in both calculaitons, BUT thier not . The program the code above is from compiles and looks great.

So have i misunderstood the

v1 = t2 - t1
v2 = t3 - t1

part or is the above code doing it a different way?

I could REALLY use some help clearing this up.

Thanks.

[/source]

This topic is closed to new replies.

Advertisement