Contact

 
Google
Web www.alanphipps.com

 
   
 
   
www.alanphipps.com

.: The 3D SkyBox

 
 

Before we start, I'd like to mention that from now on I will assume that any models, textures, effect files have been converted to xnb files as described in 2D Tutorial 21 - The Content Pipeline, I will also be removing the original files from the source code, because it is getting too big..

In this tutorial we will build a 3D skybox that will enclose our game, this skybox will encapsulate all our game's components, and will effectively be the outer limits of our game world. Now I know that in the last tutorial I said that I would be working on a quaternion camera next, and in all fairness I did give it a shot, however, trying to work out quaternion maths is difficult when the only object and hence the only reference point in the game is a red cube that moves out of view and makes it impossible to see what the camera is doing and hence I decided that I would do the skybox first and then work on the camera. So, moving on, source code, last tutorial and here we go. As is often the case in game development, this tutorial will involve a lot of new code and a few changes to the existing stuff, as always I will try to explain it as I go, but take your time, go over it again if necessary and as usual the game source code is at the bottom of the page if you would prefer to look at it in VB Express. Rename a copy of the source code folder from the last tutorial to 3DSkyBox and once the project is open we will start by rewriting the TextureClass.

The Objects and Variables section becomes:

#Region "Objects and Variables"

Private CustomEffect As Effect 'In XNA, all primitives must have an effect applied to them
Private ABasicEffect As BasicEffect 'If your not using an effects file then you can use a built-in basiceffect
Private EffectType As String = "Basic" ' The type of effect applied to the texture
Private vertices As VertexPositionTexture() ' There are a number of different vertex formats, we will use
'VertexPositionTexture array because we want to apply a texture and not a solid colour.
Private vertexBuffer1 As VertexBuffer ' The vertices will be stored in the vertex buffer, before being
'streamed to the graphics device
Private TextureFile As Texture2D 'Used to hold the texture
Private TextureName As String = "" ' The name of the texture

'The following arrays can be used by any texture, they are not specific to any one texture instance
Public Shared TempVertexArray() As Vector3 = New Vector3() {} ' The vertex array that can be used as a parameter to the Initialize Sub
Public Shared TempUCoords() As Integer = New Integer() {0, 0, 1, 1} ' The U Coordinates array that can be used as a parameter to the Initialize Sub
Public Shared TempVCoords() As Integer = New Integer() {1, 0, 0, 1} ' The V Coordinates array that can be used as a parameter to the Initialize Sub

#End Region

In XNA a texture is drawn using an effect, XNA has two types of effects, the built-in BasicEffect and the manually created CustomEffect. Our texture class will allow texture creation using both effect types, so, we have created an effect object for both types. A texture is drawn using a series of vertices, 3D points in space, these vertices are stored in a buffer and are of VertextPositionTexture format. The TextureFile object will hold the actual .xnb texture file. The TempVertexArray holds all the vertices before they are applied to the texture, this array is not necessary but it does allow nicer code. The TempUCoords and TempVCoords Arrays store the UV coords of the texture. Notice that the last three arrays are all Shared, this means that when they are changed, the change occurs for all textureclass instances, they do not belong to any one instance. This means that they must be re-calculated every time they are used, or when they are applied to texture, they will still contain the settings from the previous use.

I will give a quick explanation of texture UV Coords. Imagine four vertices positioned so that they create a square, now imagine that you have a square texture that you want to place within the four vertices. You can change the size of the square texture so that it is either equal to or smaller than the size of the four vertices. So, instead of working out new xyz coors for the texture you can use UV coords where

 

0,0 = the square's top-left vertex

1,0 = the square's top-right vertex

0,1 = the square's bottom-left vertex

1,1 = the square's bottom-right vertex

UV Coordinates

Moving onto the "Subs and Functions" region we have two Initialize subs, of which the first looks like this:

''' <summary>
''' Defines and initializes a texture. Uses a preloaded texture2D object.
''' </summary>
''' <param name="VertexArray">An array of vector3s that hold the coords each vertex. Vertices will
''' be used sequentially. Use TextureClass.TempVertexArray()</param>
''' <param name="VertexUCoordArray">The U coords of the texture, assigned sequentially. Use TextureClass.TempUCoords()</param>
''' <param name="VertexVCoordArray">The V coords of the texture, assigned sequentially. Use TextureClass.TempVCoords()</param>
''' <param name="ThisTexturesName">The name given to the texture. A String Value.</param>
''' <param name="xView">The view matrix for the texture, use Camera.ViewMatrix.</param>
''' <param name="CustomEffectTechnique ">The custom effect technique to be used to draw the texture. If you are
''' using a basic effect then set this value to nothing.</param>
''' <param name="ThisEffectType">The type of effect used to draw the texture. Either "Basic" or "Custom".</param>
''' <param name="xProjection">The projection matrix for the texture, use Camera.ProjectionMatrix.</param>
''' <param name="ThisTextureFile">The pre-initialized texture2D object.</param>
''' <param name="CustomEffectFileName">The name of the effect .fx file. If your are using a basic effect, set this value to Nothing.</param>
''' <param name="xWorld">The world matrix for the texture, use Matrix.Identity.</param>

Public Sub Initialize(ByVal ThisTextureFile As Texture2D, ByVal VertexArray() As Vector3, ByVal CustomEffectFileName As String, _
ByVal CustomEffectTechnique As String, ByVal VertexUCoordArray() As Integer, ByVal VertexVCoordArray() As Integer, ByVal ThisTexturesName As String, _
ByVal xView As Matrix, ByVal xProjection As Matrix, ByVal xWorld As Matrix, ByVal ThisEffectType As String)

'Check to see if the Customfilename has the .xnb file extension included
If CustomEffectFileName IsNot Nothing Then
If Microsoft.VisualBasic.Right(CustomEffectFileName, 4) = ".xnb" Then
CustomEffectFileName = Mid(CustomEffectFileName, 1, Len(CustomEffectFileName) - 4)
End If
End If

'Uses a custom effect
EffectType = ThisEffectType

'define the name of the texture
TextureName = ThisTexturesName

'initialize the vertex array
vertices = New VertexPositionTexture(UBound(VertexArray)) {}

'Every Vertex has an x and y coordinate, however, the part of the texture that you want to display
'on top of that vertex are sometimes called the u and v coordinates. In XNA though, they are referred
'to as TextureCoordinate.X and TextureCoordinate.Y as shown below.

For x As Integer = 0 To UBound(VertexArray)
vertices(x).Position = VertexArray(x)
vertices(x).TextureCoordinate.X = VertexUCoordArray(x)
vertices(x).TextureCoordinate.Y = VertexVCoordArray(x)
Next

Select Case ThisEffectType
Case Is = "Basic"
'Initialize the basiceffect object
ABasicEffect = New BasicEffect(XNAEngine.XNAGraphics.GraphicsDevice, Nothing)
'configure the effect parameters
ABasicEffect.View = xView
ABasicEffect.Projection = xProjection
ABasicEffect.TextureEnabled = True
ABasicEffect.Texture = TextureFile
Case Is = "Custom"
'Load the texture and custom effect from the content pipeline resource
CustomEffect = XNAEngine.XNAContentManager.Load(Of Effect)(XNAGameContentFolder & "Effects\" & CustomEffectFileName)

'configure the effect parameters
CustomEffect.Parameters("xView").SetValue(xView)
CustomEffect.Parameters("xProjection").SetValue(xProjection)
CustomEffect.Parameters("xWorld").SetValue(xWorld)
CustomEffect.Parameters("xTexture").SetValue(ThisTextureFile)
CustomEffect.CurrentTechnique = CustomEffect.Techniques(CustomEffectTechnique)
End Select

End Sub

The two initialize subs differ mainly because the first one above takes a preloaded texture2D object as a parameter, whereas the second sub takes the texture name as a string and then loads the texture file from within the sub. The next parameter in this sub is vertexarray, here you should pass in the TexpVertexArray we talked about earlier, remember to configure it first. Next is the name of the customeffect file without the .xnb file extension, if you want to use a basic effect then set this to Nothing. The next item is CustomEffectTechnique, every customeffect file can have multiple techniques and hence this is where you tell the sub which one to use, again if you are using a basiceffect then set this to Nothing. Next we have VertexVCoordArray and VertexUCoordArray, which we spoke about earlier and then ThisTexturesName which is the name of the texture, you should use the filename, then xView, xProjection and xMatrix are the three matrices used to draw the content in the game, you should use camer.viewmatrix, camera.projectionmatrix and matrix.identity respectively. Lastly we have ThisEffectType which tells the sub which effect you want to use, choices are "Basic" or "Custom".

As we go through the sub, the various variables and objects are checked and assigned, we then get to a select case which is dependant on the type of effect you want here, the required effect is then configured, as you can see the customeffect is an external file and is loaded here. The texture is now initialized and the sub exits. The second Initialize sub is below:

''' <summary>
''' Defines and initializes a texture. Uses the texture name to load the texture.
''' </summary>
''' <param name="VertexArray">An array of vector3s that hold the coords each vertex. Vertices will
''' be used sequentially. Use TextureClass.TempVertexArray()</param>
''' <param name="VertexUCoordArray">The U coords of the texture, assigned sequentially. Use TextureClass.TempUCoords()</param>
''' <param name="VertexVCoordArray">The V coords of the texture, assigned sequentially. Use TextureClass.TempVCoords()</param>
''' <param name="ThisTexturesName">The name given to the texture. A String Value.</param>
''' <param name="xView">The view matrix for the texture, use Camera.ViewMatrix.</param>
''' <param name="CustomEffectTechnique ">The custom effect technique to be used to draw the texture. If you are
''' using a basic effect then set this value to nothing.</param>
''' <param name="ThisEffectType">The type of effect used to draw the texture. Either "Basic" or "Custom".</param>
''' <param name="xProjection">The projection matrix for the texture, use Camera.ProjectionMatrix.</param>
''' <param name="ThisTextureFile">The name of the texture file. Should not include the .xnb file extension.
''' Is not the full file path. The Texture will be loaded/initialized as part of the Sub.
''' Example: "SkyUp" refers to a texture called SkyUp.xnb in the content\textures folder
''' Example 2: "Skies\SkyUp" refers to a texture called SkyUp.xnb in the content\textures\skies folder.</param>
''' <param name="CustomEffectFileName">The name of the effect .fx file. If your are using a basic effect, set this value to Nothing.</param>
''' <param name="xWorld">The world matrix for the texture, use Matrix.Identity.</param>

Public Sub Initialize(ByVal ThisTextureFile As String, ByVal VertexArray() As Vector3, ByVal CustomEffectFileName As String, _
ByVal VertexUCoordArray() As Integer, ByVal VertexVCoordArray() As Integer, ByVal ThisTexturesName As String, _
ByVal CustomEffectTechnique As String, ByVal xView As Matrix, ByVal xProjection As Matrix, ByVal xWorld As Matrix, _
ByVal ThisEffectType As String)

'Check to see if any of the texture names have the .xnb file extension included.
If Microsoft.VisualBasic.Right(ThisTextureFile, 4) = ".xnb" Then
ThisTextureFile = Mid(ThisTextureFile, 1, Len(ThisTextureFile) - 4)
End If

'Check to see if the Customfilename has the .xnb file extension included
If CustomEffectFileName IsNot Nothing Then
If Microsoft.VisualBasic.Right(CustomEffectFileName, 4) = ".xnb" Then
CustomEffectFileName = Mid(CustomEffectFileName, 1, Len(CustomEffectFileName) - 4)
End If
End If

'Load the texture file
TextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameContentFolder & "Textures\" & ThisTextureFile)

'Uses a custom effect
EffectType = ThisEffectType

'define the name of the texture
TextureName = ThisTexturesName

'initialize the vertex array
vertices = New VertexPositionTexture(UBound(VertexArray)) {}

'Every Vertex has an x and y coordinate, however, the part of the texture that you want to display
'on top of that vertex are sometimes called the u and v coordinates. In XNA though, they are referred
'to as TextureCoordinate.X and TextureCoordinate.Y as shown below.

For x As Integer = 0 To UBound(VertexArray)
vertices(x).Position = VertexArray(x)
vertices(x).TextureCoordinate.X = VertexUCoordArray(x)
vertices(x).TextureCoordinate.Y = VertexVCoordArray(x)
Next

Select Case ThisEffectType
Case Is = "Basic"
'Initialize the basiceffect object
ABasicEffect = New BasicEffect(XNAEngine.XNAGraphics.GraphicsDevice, Nothing)

'configure the effect parameters
ABasicEffect.View = xView
ABasicEffect.Projection = xProjection
ABasicEffect.TextureEnabled = True
ABasicEffect.Texture = TextureFile
Case Is = "Custom"
'Load the texture and custom effect from the content pipeline resource
CustomEffect = XNAEngine.XNAContentManager.Load(Of Effect)(XNAGameContentFolder & "Effects\" & CustomEffectFileName)

'configure the effect parameters
CustomEffect.Parameters("xView").SetValue(xView)
CustomEffect.Parameters("xProjection").SetValue(xProjection)
CustomEffect.Parameters("xWorld").SetValue(xWorld)
CustomEffect.Parameters("xTexture").SetValue(ThisTextureFile)
CustomEffect.CurrentTechnique = CustomEffect.Techniques(CustomEffectTechnique)
End Select

End Sub

As you can see the only difference is the parameter ThisTextureFile is a string value and during the sub, this string is used to load the texture2D object.

This class has two draw subs, the first of which is shown below:

''' <summary>
''' Draws the texture using default values.
''' </summary>

Public Sub Draw()

Select Case EffectType
Case Is = "Custom"
'Once all parameters have been set, we will begin the actual drawing process. In XNA all primitives
'must have an effect applied to them.

'Make sure the correct texture is applied to the customeffect
CustomEffect.Parameters("xTexture").SetValue(TextureFile)

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

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration(XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionTexture.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleFan, vertices, 0, 2)

'End the pass
temppass.End()
Next
'End the effect
CustomEffect.End()
Exit Select
Case Is = "Basic"
'Once all parameters have been set, we will begin the actual drawing process. In XNA all primitives
'must have an effect applied to them.

'Reset the texture object in ABasicEffect
ABasicEffect.Texture = TextureFile

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

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration(XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionTexture.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleFan, vertices, 0, 2)

'End the pass
temppass.End()
Next
'End the effect
ABasicEffect.End()
Exit Select
End Select

End Sub

This sub takes no parameters and is basically a select case of effecttype, the sub then draws the texture in the normal way with the required effect. The second draw sub is slightly different:

''' <summary>
''' Draws the texture using manually configured values.
''' </summary>
''' <param name="ThePrimitiveType">The way the vertices are drawn. The default is TriangleFan.</param>
''' <param name="VertexOffset">The offset used to display the vertices. The default is 0</param>
''' <param name="PrimitiveCount">The number of primitives used to display the texture. Usually 1 per triangle.</param>

Public Sub Draw(ByVal ThePrimitiveType As PrimitiveType, ByVal VertexOffset As Integer, _
ByVal PrimitiveCount As Integer)

Select Case EffectType
Case Is = "Custom"
'Once all parameters have been set, we will begin the actual drawing process. In XNA all primitives
'must have an effect applied to them.

'Make sure the correct texture is applied to the customeffect
CustomEffect.Parameters("xTexture").SetValue(TextureFile)

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

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration(XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionTexture.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawUserPrimitives(ThePrimitiveType, vertices, VertexOffset, PrimitiveCount)

'End the pass
temppass.End()
Next
'End the effect
CustomEffect.End()
Exit Select
Case Is = "Basic"
'Once all parameters have been set, we will begin the actual drawing process. In XNA all primitives
'must have an effect applied to them.

ABasicEffect.Begin()

'Reset the texture object in ABasicEffect
ABasicEffect.Texture = TextureFile

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

'associate the vertexdeclaration with our graphics device
XNAEngine.XNAGraphics.GraphicsDevice.VertexDeclaration = New VertexDeclaration(XNAEngine.XNAGraphics.GraphicsDevice, _
VertexPositionTexture.VertexElements)
'and then draw the primitives in the TriangleList style.
XNAEngine.XNAGraphics.GraphicsDevice.DrawUserPrimitives(ThePrimitiveType, vertices, VertexOffset, PrimitiveCount)

'End the pass
temppass.End()
Next
'End the effect
ABasicEffect.End()
Exit Select
End Select
End Sub

The only difference is that it takes in a few parameters that can then be used in the DrawUserPrimitives sub.

After reading about properties in vb.net being secure and very useful I decided to start using them:

#Region "Properties"

''' <summary>
''' The CustomEffect used by this texture.
''' </summary>

Public Property TheCustomEffect() As Effect
Get
Return CustomEffect
End Get
Set(ByVal Value As Effect)
CustomEffect = Value
End Set
End Property

''' <summary>
''' The basicEffect used by this texture.
''' </summary>

Public Property TheBasicEffect() As BasicEffect
Get
Return ABasicEffect
End Get
Set(ByVal Value As BasicEffect)
ABasicEffect = Value
End Set
End Property

''' <summary>
''' The type of effect used to draw this texture, either "Basic" or "Custom"
''' </summary>

Public Property TheEffectType() As String
Get
Return EffectType
End Get
Set(ByVal Value As String)
EffectType = Value
End Set
End Property

''' <summary>
''' The texture file that is associated with this textureclass instance.
''' </summary>

Public Property TheTextureFile() As Texture2D
Get
Return TextureFile
End Get
Set(ByVal Value As Texture2D)
TextureFile = Value
End Set
End Property

#End Region

These properties are used by other parts of the game to access the private variables and objects that were declared at the beginning of the code. Now one last point before we move onto the next class. The entire TextureClass that I have written here simplifies the process of drawing a texture. this process is as follows:

 

create a new instance of the class:

then initialize the texture:

then draw the texture:

Dim texture1 as new textureclass

texture1.Initizialize

texture1.draw

 

I will attempt to do this with all objects so that you can concentrate on you game logic. Next we will look at the SkyBox class

Create a new class and call it SkyBox class, when it opens rewrite the code so it looks like this.

Imports Microsoft.Xna.Framework
Imports Microsoft.Xna.Framework.Graphics

Namespace XNA
Public Class SkyBoxClass

 

End Class
End Namespace

As normal we will start with an "Objects and Variables" Region:

'The 6 textureclass objects that form the skybox
Private SkyUp As New TextureClass
Private SkyDown As New TextureClass
Private SkyRight As New TextureClass
Private SkyLeft As New TextureClass
Private SkyFront As New TextureClass
Private SkyBack As New TextureClass

'An Array to hold the textureclasses
Priv