.: Basic Terrain With Lighting
|
|
| |
This tutorial will take our terrain and add a lighting effect using both XNA's built in basic effect and a custom effect using HLSL. There are only really a couple of code changes from the last tutorial, so as usual, source code, last tutorial and now we will begin.
If we look at the terrain from the last tutorial:
|
|

We can see that although it looks like a set of mountains, each pixel blends into the next which makes it look unrealistic. What we need to to is to apply different levels of lighting to each vertex, and we will do this using normals. Every vertex is assigned a normal which is perpendicular to the vertex and its neighbour.
A more detailed explanation can be found here.
Now, in the last tutorial we used VertexPositionColors to draw each triangle. We would use VertexPositionNormalColours to draw our terrain this time but unfortunately they don't exist in XNA, however, we can make them. So, open the terrain class and add the following declaration:
Private VertPosNormColours() As VertexPositionNormalColor = New VertexPositionNormalColor() {}
Public Structure VertexPositionNormalColor
Public Position As Vector3
Public Colour As Color
Public Normal As Vector3
Public Shared SizeInBytes As Integer = 7 * 4
Public Shared VertexElements As VertexElement() = New VertexElement() _
{New VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0), _
New VertexElement(0, 4 * 3, VertexElementFormat.Color, VertexElementMethod.Default, VertexElementUsage.Color, 0), _
New VertexElement(0, 4 * 4, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0)}
End Structure
The structure contains the 5 components required to create a VertexPositionNormalColour, this structure can now be used to draw a vertex. The first line declares an instance of the VertexPositionNormalColour structure.
The Initialize Sub also has new code:
''' <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="DimensionMultiplier">Multplies the width an length vslues
''' of the heightmap to make it bigger</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. Required if using a custom effect.
''' Try "Content\Effects\standardeffects" for a custom effect and "Nothing" for a basc effect.</param>
''' <param name="CustomEffectTechnique">The Technique with which to draw the terrain. Colored, Textured, etc. Required if using a custom effect. Type
''' Nothing if you are using a basic effect</param>
''' <param name="WhichVertexType">The type of vertex used.</param>
''' <param name="ThisTextureFile">The name of the texture file to use. If using a colored effect
''' set this to Nothing</param>
Public Sub Initialize(ByVal HeightMapFileName As String, ByVal WhichEffectType As Effecttype, _
ByVal DimensionMultiplier As Single, ByVal WhichVertexType As VertexType, _
ByVal CustomEffectFileName As String, ByVal CustomEffectTechnique As String, _
ByVal ThisTextureFile As String)
If DimensionMultiplier < 1.0 Then DimensionMultiplier = 1.0
Select Case WhichVertexType
Case VertexType.Colored 'Using Colours
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)
'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)
YDistance += CInt(BR.ReadByte)
YDistance += CInt(BR.ReadByte)
BR.ReadByte() ' alpha channel data
YDistance /= 8
HeightData(TempIntX) = CInt(YDistance)
Next
BR.Close()
MemStream.Close()
BR = Nothing
Image1.Dispose()
YDistance = Nothing
MemStream.Dispose()
Bitmap.Dispose()
'Configure the VertexPositionColours array, setting the position and colour
'of each vertex
ReDim VertPosNormColours(XDistance * ZDistance)
Dim z As Integer = -(ZDistance / 2)
Dim x As Integer = -(XDistance / 2)
For TempIntX = 0 To UBound(VertPosNormColours) - 1
VertPosNormColours(TempIntX).Position = New Vector3(x, HeightData(TempIntX), z)
'Mutiply each vertex position by the dimension multiplier
VertPosNormColours(TempIntX).Position *= DimensionMultiplier
'and determine Colours
Select Case VertPosNormColours(TempIntX).Position.Y
Case 0 To 3
VertPosNormColours(TempIntX).Colour = Color.Blue
Case 4 To 15
VertPosNormColours(TempIntX).Colour = Color.Green
Case 16 To 128
VertPosNormColours(TempIntX).Colour = Color.Gray
Case Is > 128
VertPosNormColours(TempIntX).Colour = Color.WhiteSmoke
End Select
x += 1
If x >= (XDistance / 2) Then
x = -(XDistance / 2)
z += 1
End If
Next
z = Nothing
x = Nothing
'Define Normal data.
Dim a As Integer = 0
Dim b As Integer = 0
Dim c As Integer = 0
For b = 0 To ZDistance - 2
For c = 0 To XDistance - 2
'Vector1 = first corner of the triangle minus the second corner of the triangle
'Vector2 = second corner of the triangle minus the third corner of the triangle
Dim Vector2 As Vector3 = Vector3.Subtract(VertPosNormColours((b _
* XDistance) + c).Position, VertPosNormColours((b * _
XDistance) + c + 1).Position)
Dim Vector1 As Vector3 = Vector3.Subtract(VertPosNormColours(((b _
+ 1) * XDistance) + c).Position, VertPosNormColours((b * _
XDistance) + c).Position)
'The normal for that triangle is the cross product of the 2 vectirs
Dim Normal As Vector3 = Vector3.Cross(Vector1, Vector2)
'Normalizing the vector keeps it small.
Normal.Normalize()
'assign the Normals to the vertices
VertPosNormColours((b * XDistance) + c).Normal += Normal
VertPosNormColours((b * XDistance) + c + 1).Normal += Normal
VertPosNormColours(((b + 1) * XDistance) + c).Normal += Normal
Next
Next
a = Nothing
b = Nothing
c = Nothing
'Load the vertex buffer
vb = New VertexBuffer(XNAEngine.XNAGraphics.GraphicsDevice, _
UBound(VertPosNormColours) * 29, BufferUsage.WriteOnly)
vb.SetData(VertPosNormColours)
'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
n = Nothing
ib = New IndexBuffer(XNAEngine.XNAGraphics.GraphicsDevice, GetType(Integer), _
UBound(Indices) + 1, BufferUsage.WriteOnly)
ib.SetData(Indices)
Select Case WhichEffectType
Case Effecttype.Basic
'Configure basic effect
ABasicEffect = New BasicEffect(XNAEngine.XNAGraphics.GraphicsDevice, Nothing)
ABasicEffect.VertexColorEnabled = True 'The vertices can be colored
ABasicEffect.EnableDefaultLighting() 'initiallly set the defualt lighting
ABasicEffect.DirectionalLight0.Direction = Vector3.Up 'Set the lights position to (0,1,0)
ThisEffectType = Effecttype.Basic
Case Effecttype.Custom
'Load the texture and custom effect from the content pipeline resource
CustomEffect = XNAEngine.XNAContentManager.Load(Of Effect)(XNAGameProjectFolder & CustomEffectFileName)
ThisEffectType = Effecttype.Custom
'configure the effect parameters
CustomEffect.Parameters("xView").SetValue(Camera.ViewMatrix)
CustomEffect.Parameters("xProjection").SetValue(Camera.ProjectionMatrix)
CustomEffect.Parameters("xWorld").SetValue(Matrix.Identity)
CustomEffect.Parameters("xEnableLighting").SetValue(True)
CustomEffect.Parameters("xLightDirection").SetValue(Vector3.Up)
CustomEffect.CurrentTechnique = CustomEffect.Techniques(CustomEffectTechnique)
End Select
Catch ex As Exception
' MsgBox(ex.Message)
'Application.Exit()
Exit Sub
End Try
Case VertexType.Textured 'Using Textures
End Select
End Sub
The new code is in Red and the first part calculates the normals for each triangle. The second part enables lighting in each effect. The only other new code is in the Draw sub:
Public Sub Draw()
Select Case ThisEffectType
Case Effecttype.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, _
VertexPositionNormalColor.SizeInBytes)
XNAEngine.XNAGraphics.GraphicsDevice.Indices = ib
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration( _
XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionNormalColor.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, _
0, 0, UBound(VertPosNormColours), 0, (XDistance - 1) * (ZDistance - 1) * 2)
'End the pass
TempPass.End()
Next
'End the effect
ABasicEffect.End()
Case Effecttype.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, _
VertexPositionNormalColor.SizeInBytes)
XNAEngine.XNAGraphics.GraphicsDevice.Indices = ib
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration( _
XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionNormalColor.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, _
0, 0, UBound(VertPosNormColours), 0, (XDistance - 1) * (ZDistance - 1) * 2)
'End the pass
TempPass.End()
Next
'End the effect
CustomEffect.End()
End Select
End Sub
The Red code here simply tells the graphics device that we will be using our newly created VertexPositionNormalColours and then it draws each triangle.
In the XNAEngine Initialize Sub you can run the game using a BasicEffect as follows:
Terrain.Initialize("Textures\HeightMaps\HeightMap128.jpg", _
TerrainClass.Effecttype.Basic, 6.0, TerrainClass.VertexType.Colored, _
Nothing, Nothing, Nothing)
Which will give the following:

and if you want to use a custom effect using:
Terrain.Initialize("Textures\HeightMaps\HeightMap128.jpg", _
TerrainClass.Effecttype.Custom, 6.0, TerrainClass.VertexType.Colored, _
"Content\Effects\standardeffects", "Colored", Nothing)
You will get:

As you can see there is a difference and I personally prefer the basic effect and hence I will continue to use it until I go over HLSL properly in a future tutorial.
Right, in the next tutorial we will try drawing our terrain using textures, and of course when it is finished I will upload it to the tutorials page, So until then, Enjoy.
Web site contents © Copyright Alan Phipps 2006, All rights reserved.
Website templates |