Contact

 
Google
Web www.alanphipps.com

 
   
 
   
www.alanphipps.com

.: Basic Terrain

 
 

Although my laptop still doesn't fully support XNA, I have borrowed my brother's, so lets see if we can get a couple of tutorials done before he notices that its gone. Right, the subject of terrain is a big one, so I have decided to spread it over 2 separate tutorials. In this tutorial we will create the terrain by loading a heightmap and use colours rather than textures to paint the vertices. Also, many thanks to riemers.net for showing me how to read a bitmap using a binaryreader.

A heightmap, in this case, is a black and white image where white represents a height of 255, and black represents a height of 0. A heightmap can be created in any image processing app, I used Paint.NET and my heightmap is shown below:

Heightmap

As you can see its not that complex. Open Paint.NET, make a new photo, paint it black and distort it using a cloud effect.

Now, using the source code from the last tutorial, copy the source code folder and rename the copy to Basic Terrain, open the project and add a new class called terrainclass, in terrainclass add the following declarations:

#Region "Objects and Variables"

Private ABasicEffect As BasicEffect
Private CustomEffect As Effect
Private Offset As Integer = 0
Private XDistance As Integer = 0
Private ZDistance As Integer = 0 ' Positive Z behind you
Private VertPosColours() As VertexPositionColor = New VertexPositionColor() {}
Private Indices() As Integer = New Integer() {}
Private HeightData() As Integer = New Integer() {}

Private ib As IndexBuffer
Private vb As VertexBuffer

'A Few Temporary Variables that are not disposed, so that the garbage collector does not run
Private TempInt As Integer = 0
Private TempIntX As Integer = 0
Private TempIntY As Integer = 0
Private TempPass As EffectPass

'Type of effect to be used
Public Enum Effectype As Integer
Basic = 0
Custom = 1
End Enum

#End Region

These are the various objects and variables that will be used to draw the terrain, next we have the subs and functions:

The first functions is the one that does most of the work, Initialize:

''' <summary>
''' Initializes the terrain object. Taking the heightmap file name, with the file extension as a parameter.
''' </summary>
''' <param name="HeightMapFileName">The heightmap file name, with the file extension. Can be bmp, jpg, png.
''' Example: HeightMap.jpg, although not necessarry, should be square.</param>
''' <param name="MultiplyHeight">If True then the all heights in the heighmap will be
''' muliplied buy the DimensionMultiplier, to bring the heights in line with
''' the skybox.</param>
''' <param name="DimensionMultiplier">The vslue to multiply the skybox by.</param>
''' <param name="WhichEffectType"> The Type of effect that will be used to draw the terrain.</param>
''' <param name="CustomEffectFileName">The optional name of the custom effect file</param>
''' <param name="CustomEffectTechnique">The Technique with whcih to draw the terrain. Colored, Textured, etc.</param>

Public Sub Initialize(ByVal HeightMapFileName As String, ByVal WhichEffectType As Effectype, _
ByVal MultiplyHeight As Boolean, Optional ByVal DimensionMultiplier As Single = 1.0, _
Optional ByVal CustomEffectFileName As String = "standardeffects", Optional ByVal CustomEffectTechnique As String = "Colored")

Try
Dim Image1 As System.Drawing.Image ' An image
Dim MemStream As System.IO.MemoryStream = New System.IO.MemoryStream ' A section of memory
Dim Bitmap As Bitmap ' A bitmap object to hold the converted image

Image1 = Image.FromFile(XNAGameProjectFolder & HeightMapFileName) ' Load the heightmap file

'Convert the image to a bitmap
Bitmap = New Bitmap(Image1)

'Save the bitmap to the memory stream as a bitmap
Bitmap.Save(MemStream, System.Drawing.Imaging.ImageFormat.Bmp)

MemStream.Position = 0 'Reset the memstraem position to the start
Dim BR As New BinaryReader(MemStream) 'Create a Binary Reader to read for memstream

'seek to the byte that indicates the offset to the actual pixeldata
'The offset is 4 bytes.

BR.BaseStream.Seek(10, SeekOrigin.Current)
Offset = CInt(BR.ReadUInt32)

'Next we seek another 4 bytes to byte 19, where we find the Width and the Length of the image
BR.BaseStream.Seek(4, SeekOrigin.Current)
XDistance = CInt(BR.ReadUInt32)
ZDistance = CInt(BR.ReadUInt32)

'Now we can initialise our heightData array and seek further to the pixeldata
BR.BaseStream.Seek(Offset - 26, SeekOrigin.Current)
ReDim HeightData(XDistance * ZDistance)

'Now we know the exact width and height, we are going to store the sum of the 3 colors as
'the height for a pixel.

Dim YDistance As Integer = 0
For TempIntX = 0 To UBound(HeightData) - 1
YDistance = CInt(BR.ReadByte) ' Red
YDistance += CInt(BR.ReadByte) ' Green
YDistance += CInt(BR.ReadByte) ' Blue
BR.ReadByte() ' alpha channel data
YDistance /= 8
HeightData(TempIntX) = CInt(YDistance)
Next

BR.Close()
MemStream.Close()
BR = Nothing
Image1 = Nothing
YDistance = Nothing
MemStream = Nothing
Bitmap = Nothing

'Configure the VertexPositionColours array, setting the position and colour
'of each vertex

ReDim VertPosColours(XDistance * ZDistance)
Dim z As Integer = -(ZDistance / 2)
Dim x As Integer = -(XDistance / 2)
For TempIntX = 0 To UBound(VertPosColours) - 1
VertPosColours(TempIntX) = New VertexPositionColor(New Vector3(x, _
HeightData(TempIntX), z), Color.WhiteSmoke)
x += 1
If x >= (XDistance / 2) Then
x = -(XDistance / 2)
z += 1
End If
Next

'Multiply heightvalues by skybox.dimensionmultiplir
For TempIntX = 0 To UBound(HeightData) - 1
HeightData(TempIntX) *= DimensionMultiplier
Next

'Redo Vertpos Color dependant on height value
For TempIntX = 0 To UBound(VertPosColours) - 1
Select Case VertPosColours(TempIntX).Position.Y
Case 0 To 2
VertPosColours(TempIntX).Color = Color.Blue
Case 3 To 7
VertPosColours(TempIntX).Color = Color.Green
Case 8 To 48
VertPosColours(TempIntX).Color = Color.Gray
Case 49 To 64
VertPosColours(TempIntX).Color = Color.WhiteSmoke
Case Else
VertPosColours(TempIntX).Color = Color.WhiteSmoke
End Select
Next

' Load the Vertex Buffer

vb = New VertexBuffer(XNAEngine.XNAGraphics.GraphicsDevice, _
UBound(VertPosColours) * 17, BufferUsage.WriteOnly)
vb.SetData(VertPosColours)

'work out indices, 3 indices for each triangle
Dim n As Integer = 0
For w As Integer = 0 To ZDistance - 2
For u As Integer = 0 To XDistance - 2
ReDim Preserve Indices(UBound(Indices) + 6)
Indices(n) = (w * XDistance) + u
Indices(n + 1) = (w * XDistance) + u + 1
Indices(n + 2) = ((w + 1) * XDistance) + u
Indices(n + 3) = (w * XDistance) + u + 1
Indices(n + 4) = ((w + 1) * XDistance) + u + 1
Indices(n + 5) = ((w + 1) * XDistance) + u
n += 6
Next
Next

z = Nothing
x = Nothing
n = Nothing

ib = New IndexBuffer(XNAEngine.XNAGraphics.GraphicsDevice, GetType(Integer), _
UBound(Indices) + 1, BufferUsage.WriteOnly)
ib.SetData(Indices)

Select Case WhichEffectType
Case Effectype.Basic 'Using a basic effect
ABasicEffect = New BasicEffect(XNAEngine.XNAGraphics.GraphicsDevice, Nothing)
ABasicEffect.VertexColorEnabled = True
Case Effectype.Custom 'Using a custom effect
'Load the texture and custom effect from the content pipeline resource
CustomEffect = XNAEngine.XNAContentManager.Load(Of Effect)(XNAGameProjectFolder & "Content\Effects\" & CustomEffectFileName)

'configure the effect parameters
CustomEffect.Parameters("xView").SetValue(Camera.ViewMatrix)
CustomEffect.Parameters("xProjection").SetValue(Camera.ProjectionMatrix)
CustomEffect.Parameters("xWorld").SetValue(Matrix.Identity)
CustomEffect.CurrentTechnique = CustomEffect.Techniques(CustomEffectTechnique)

End Select

Catch ex As Exception
MsgBox(ex.Message)
End Try

End Sub

 

First this function loads the heightmap, it can be a bmp, jpg or png file, I'll write the code later to allow it to be a compiled xnb file, but not yet. The heightmap is then loaded into memory and converted to a bitmap in memory, using a binaryreader we find the width and length of the bitmap. The same biinaryreader then reads the grayscale data and stores this in an array called heightdata. The vertexpositioncolors are created using the HeightData and are then stored in an array called vertposcolours, we then cycle through this array and reassign the colours where the value of the heightdata determines what colour will be assigned to the vertex.

After the vertexbuffer is created then the indices are calculated. Each triangle has 3 indices and each index corresponds to a vertex, so The indices array tells the game what vertex to draw and when. The indexbuffer is then created and loaded.

Lastly the basic or custom effect is configured.

 

The next function is:

' Get the highest value in the heightmap
Public Function GetHighestPointinHeightMap()
TempInt = 0
For TempIntX = 0 To UBound(HeightData) - 1
If HeightData(TempIntX) > TempInt Then
TempInt = HeightData(TempIntX)
End If
Next
Return TempInt
End Function

This sub simply returns the highest point in the heightmap, next is the update sub:

Public Sub Update()

End Sub

The update sub doesn't do anything yet but it might in the future and lastly the draw sub:

 

Public Sub Draw(ByVal WhichEffectType As Effectype)

Select Case WhichEffectType
Case Effectype.Basic
ABasicEffect.View = Camera.ViewMatrix
ABasicEffect.Projection = Camera.ProjectionMatrix

ABasicEffect.Begin()
'For each pass in the total number of passes made in the Textured technique
For Each TempPass In ABasicEffect.CurrentTechnique.Passes
'Begin this pass
TempPass.Begin()

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.Vertices(0).SetSource(vb, 0, _
VertexPositionColor.SizeInBytes)
XNAEngine.XNAGraphics.GraphicsDevice.Indices = ib
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration( _
XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionColor.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, _
0, 0, UBound(VertPosColours), 0, (XDistance - 1) * (ZDistance - 1) * 2)

'End the pass
TempPass.End()
Next
'End the effect
ABasicEffect.End()
Case Effectype.Custom

CustomEffect.Parameters("xView").SetValue(Camera.ViewMatrix)
CustomEffect.Parameters("xProjection").SetValue(Camera.ProjectionMatrix)

CustomEffect.Begin()
'For each pass in the total number of passes made in the Textured technique
For Each TempPass In CustomEffect.CurrentTechnique.Passes
'Begin this pass
TempPass.Begin()

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.Vertices(0).SetSource(vb, 0, _
VertexPositionColor.SizeInBytes)
XNAEngine.XNAGraphics.GraphicsDevice.Indices = ib
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration( _
XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionColor.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, _
0, 0, UBound(VertPosColours), 0, (XDistance - 1) * (ZDistance - 1) * 2)

'End the pass
TempPass.End()
Next
'End the effect
CustomEffect.End()
End Select

End Sub

The draw sub basically decides what type of effect is used and then draws the terrain. There are a few properties that I have written too but you can see them in the source code. Right, if you run the program you should see something like this:

 

XNA in VB.NET - Basic Terrain

As you can see I have used the wireframe view in the XNAEngine.Draw sub by using the following line of code:

XNAGraphics.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame

I have also removed the dice and have not drawn the skybox. The dice is gone for good but the skybox will return once the terrain is complete.

Now, once I have completed the second part of the terrain tutorial I will upload it to the tutorial's page

 

Basic Terrain Source Code - 209Kb
Next Tutorial - BasicTerrain With Lighting

 

     
 
 
     

 

Web site contents © Copyright Alan Phipps 2006, All rights reserved.
Website templates
 
   
 
 

 

__PayPal

PayPal - Any Amount is Welcome
 
Please Donate to the Nvidia Geforce Go 7950 GTX Fund, All donations welcome. Thanks.

 

XNA in C#

 
 

 

Games at Amazon