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)(XNAGameProjectFolder & "Content\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)(XNAGameProjectFolder & "\Content\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)(XNAGameProjectFolder & "Content\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
Private TextureClassArray() As TextureClass

'There are 8 vertices that will be used to make the skybox. The Vertices are created as if you were
'standing in the centre of the cube at vector3 = 0,0,0 looking in the positive z direction, which is out
'of the screen towards you.

Private SkyUpVertexBackLeft As Vector3
Private SkyUpVertexFrontLeft As Vector3
Private SkyUpVertexFrontRight As Vector3
Private SkyUpVertexBackRight As Vector3
Private SkyDownVertexBackLeft As Vector3
Private SkyDownVertexFrontLeft As Vector3
Private SkyDownVertexFrontRight As Vector3
Private SkyDownVertexBackRight As Vector3

'Used to determine whether the skybox will use a custom or basic effect
Private EffectType As String = "Basic"

'Does the skybox move synchronously with the camera position
Private LockSkyboxIntoCameraPosition As Boolean = False
Private SkyBoxOrigin As New Vector3(0.0F, 0.0F, 0.0F) ' used only if LockSkyboxIntoCameraposition = True

As you can see the skybox is basically six textures that align to form a cube, the textures are then displayed on the inside surfaces. The skybox has two initialize subs and the first one looks like this:

''' <summary>
''' Initializes an instance of the skybox. Skybox dimensions are automatically created.
''' Each texture name is a string value and points to the location of the texture. 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. The dimensions of the skybox will be determined by the dimension of the
''' SkyUp and SkyFront textures multiplied by the value given for DimensionMultiplier.
'''</summary>
''' <param name="strSkyUp">The name of the SkyUp texture.</param>
''' <param name="strSkyDown">The name of the SkyDown texture.</param>
''' <param name="strSkyRight">The name of the SkyRight texture.</param>
''' <param name="strSkyLeft">The name of the SkyLeft texture.</param>
''' <param name="strSkyFront">The name of the SkyFront texture.</param>
''' <param name="strSkyBack">The name of the SkyBack texture.</param>
''' <param name="DimensionMultiplier">The floating point value that the texture dimensions will be multiplied by to
''' set the skybox size. Example, a value of 2.0 and a texture length of 1024 will make a skybox length of 2048.
''' If the value entered is less than 0 then the value will be set to 1.</param>
''' <param name="thisEffectType">The type of effect used to display the skybox, choices are "Custom" or "Basic".</param>
''' <param name="LockSkyBox">If True the skybox will move in synch with the camera position.</param>

Public Sub Initialize(ByVal strSkyUp As String, ByVal strSkyDown As String, ByVal strSkyLeft As String, _
ByVal strSkyRight As String, ByVal strSkyFront As String, ByVal strSkyBack As String, ByVal DimensionMultiplier As Single, _
ByVal thisEffectType As String, ByVal LockSkyBox As Boolean)

'make sure that DimensionMultiplier is a single value
DimensionMultiplier = CSng(DimensionMultiplier)

'Make sure Dimensionmultiplier is bigger than 0
If DimensionMultiplier <= 0 Then DimensionMultiplier = 1

'Check that thisEffectType has the proper value
If Not thisEffectType = "Basic" AndAlso Not thisEffectType = "Custom" Then
Throw New Exception
Exit Sub
End If

'Set the effecttype
EffectType = thisEffectType

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

Try
'assign the textures
SkyUp.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyUp)
SkyDown.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyDown)
SkyRight.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyRight)
SkyLeft.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyLeft)
SkyFront.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyFront)
SkyBack.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyBack)

'Initialize the array
TextureClassArray = New TextureClass() {SkyUp, SkyDown, SkyRight, SkyLeft, SkyFront, SkyBack}

For x As Integer = 0 To UBound(TextureClassArray)
If TextureClassArray(x).TheTextureFile Is Nothing Then
TextureClassArray(x).TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\texturenotfound")
End If
Next
Catch ex As Exception
'MsgBox(ex.Message)
End Try

'Determine and assign the positions of the vertices.
SkyUpVertexBackLeft = New Vector3((-(SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyUpVertexFrontLeft = New Vector3((-(SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyUpVertexFrontRight = New Vector3(((SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyUpVertexBackRight = New Vector3(((SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyDownVertexBackLeft = New Vector3((-(SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyDownVertexFrontLeft = New Vector3((-(SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyDownVertexFrontRight = New Vector3(((SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), ((SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))
SkyDownVertexBackRight = New Vector3(((SkyUp.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyFront.TheTextureFile.Width / 2) * DimensionMultiplier), (-(SkyUp.TheTextureFile.Height / 2) * DimensionMultiplier))

'Should the skybox move in synch with the camera position
LockSkyboxIntoCameraPosition = LockSkyBox
SkyBoxOrigin = Camera.CameraPosition 'The initial position of the skybox origin

'Set the cameraview to the diameter of the skybox
Camera.FarClip = Vector3.Distance(SkyUpVertexBackLeft, SkyDownVertexFrontRight)

Select Case EffectType
Case Is = "Basic"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
'Redo the vertex array for the next texture, no need to change the U and V coords because they are the
'same for all textures

TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)

'Setup the basic effect for each skybox texture
For Each TempTextureClass As TextureClass In TextureClassArray
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressU = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressV = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.DepthBufferEnable = False
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace
Next

Case Is = "Custom"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
'Redo the vertex array for the next texture, no need to change the U and V corrs because they are the
'same for all textures
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)

End Select

End Sub

The first initialize sub use the height and width of the Front and Up textures and a dimension multiplier to determine the dimensions of the skybox. The first six parameters that this sub takes strSkyUp, strSkyDown, strSkyLeft, strSkyRight, strSkyFront and strSkyBack are the files names of their respective texture, without the .xnb extension. The next parameter, thisEffectType tells the sub whether the skybox textures will be using a "Basic" or "Custom" effect and lastly the DimensionMultiplier is a Single (float) value that is used to determine the dimensions of the skybox, example: if the front texture as a width of 1024 pixels and you use a dimension multiplier of 2.0 then the skybox will have a width of 2048 pixels. The next parameter, thisEffectType determines whether the skybox will be initialized with a "Basic" or "Custom" effect and lastly the parameter LockSkyBox is a boolean that determines whether the skybox will move in synch with the camera position, meaning that wherever you move the skybox will always by the same distance away and hence will appear like a proper sky.

As we go through the sub the first few operations are about checking parameters and assigning values to variables, when we get to the Try statement, we load the necessary textures into the textureclass.texturefile objects. As the skybox is the outer limit of our game would and if a skybox texture cannot be found then, instead of leaving a hold in our cube we will load a texture called "texturenotfound", this will be displayed in our game and will alert us to the fact that a texture cannot be found.

After the Try statement we calculate the eight vertices of our skybox using the dimensions and dimensionmultiplier. Next we assign a value to LockSkyboxIntoCameraPosition and set the skybox origin to the position of the camera. We also set the camera.farclip to the distance between one vertex and its geometrically opposite partner, this will be explained more in the next tutorial.

Lastly we use a Select case on effecttype to determine he effect used to draw the textures, once known we then initialize each texture using the textrueclass's initialize sub, remembering to recalculate the TextureClass.TempVertexArray before it is used again. For a basic effect we set the following fields:


TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressU = TextureAddressMode.Clamp
TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressV = TextureAddressMode.Clamp
TheBasicEffect.GraphicsDevice.RenderState.DepthBufferEnable = False
TheBasicEffect.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace

These settings make sure that our skybox does not appear like the inside of a cube but as a proper sky. These settings would be coded directly into a custom effect .fx file.

The second Initialize Sub:

''' <summary>
''' Initializes an instance of the skybox. Skybox dimensions are manually entered.
''' Each texture name is a string value and points to the location
''' of the texture. 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.
''' The dimensions of the skybox will be determined by manually entering a vector3 for each of the 8 vertices.
'''</summary>
''' <param name="strSkyUp">The name of the SkyUp texture.</param>
''' <param name="strSkyDown">The name of the SkyDown texture.</param>
''' <param name="strSkyRight">The name of the SkyRight texture.</param>
''' <param name="strSkyLeft">The name of the SkyLeft texture.</param>
''' <param name="strSkyFront">The name of the SkyFront texture.</param>
''' <param name="strSkyBack">The name of the SkyBack texture.</param>
''' <param name="SkyDownBackLeft">The Vector3 coord of the SkyBox Vertex at (-X,-Y,-Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyDownBackRight">The Vector3 coord of the SkyBox Vertex at (+X,-Y,-Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyDownFrontLeft">The Vector3 coord of the SkyBox Vertex at (-X,-Y,+Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyDownFrontRight">The Vector3 coord of the SkyBox Vertex at (+X,-Y,+Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyUpBackLeft">The Vector3 coord of the SkyBox Vertex at (-X,+Y,-Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyUpBackRight">The Vector3 coord of the SkyBox Vertex at (+X,+Y,-Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyUpFrontLeft">The Vector3 coord of the SkyBox Vertex at (-X,+Y,+Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="SkyUpFrontRight">The Vector3 coord of the SkyBox Vertex at (+X,+Y,+Z),
''' using the XYZ system where +X = Right, +Y = Up, +Z = towards you. Origin = New Vector3(0,0,0)</param>
''' <param name="thisEffectType">The type of effect used to display the skybox, choices are "Custom" or "Basic".</param>
''' <param name="LockSkyBox">If True the skybox will move in synch with the camera position.</param>

Public Sub Initialize(ByVal strSkyUp As String, ByVal strSkyDown As String, ByVal strSkyLeft As String, _
ByVal strSkyRight As String, ByVal strSkyFront As String, ByVal strSkyBack As String, ByVal SkyUpBackLeft As Vector3, _
ByVal SkyUpFrontLeft As Vector3, ByVal SkyUpFrontRight As Vector3, ByVal SkyUpBackRight As Vector3, _
ByVal SkyDownBackLeft As Vector3, ByVal SkyDownFrontLeft As Vector3, ByVal SkyDownFrontRight As Vector3, _
ByVal SkyDownBackRight As Vector3, ByVal thisEffectType As String, ByVal LockSkyBox As Boolean)

'Check that thisEffectType has the proper value
If Not thisEffectType = "Basic" AndAlso Not thisEffectType = "Custom" Then
Throw New Exception
Exit Sub
End If

'Set the effect type
EffectType = thisEffectType

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

Try
'assign the textures
SkyUp.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyUp)
SkyDown.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyDown)
SkyRight.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyRight)
SkyLeft.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyLeft)
SkyBack.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyBack)
SkyFront.TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\" & strSkyFront)

'Initialize the array
TextureClassArray = New TextureClass() {SkyUp, SkyDown, SkyRight, SkyLeft, SkyFront, SkyBack}

For x As Integer = 0 To UBound(TextureClassArray)
If TextureClassArray(x).TheTextureFile Is Nothing Then
TextureClassArray(x).TheTextureFile = XNAEngine.XNAContentManager.Load(Of Texture2D)(XNAGameProjectFolder & "Content\Textures\texturenotfound")
End If
Next
Catch ex As Exception
' MsgBox(ex.Message)
End Try

'Determine and assign the postions of the vertices.
SkyUpVertexBackLeft = SkyUpBackLeft
SkyUpVertexFrontLeft = SkyUpFrontLeft
SkyUpVertexFrontRight = SkyUpFrontRight
SkyUpVertexBackRight = SkyUpBackRight
SkyDownVertexBackLeft = SkyDownBackLeft
SkyDownVertexFrontLeft = SkyDownFrontLeft
SkyDownVertexFrontRight = SkyDownFrontRight
SkyDownVertexBackRight = SkyDownBackRight

'Should the skybox move in synch with the camera position
LockSkyboxIntoCameraPosition = LockSkyBox
'Because the skybox origin might not be (0,0,0) when the vertices are initialized manually, we must now calculate it.
SkyBoxOrigin = New Vector3(SkyUpVertexFrontRight.X - (Vector3.Distance(SkyUpVertexFrontRight, SkyUpVertexFrontLeft) / 2), _
SkyUpVertexFrontRight.Y - (Vector3.Distance(SkyUpVertexFrontRight, SkyDownVertexFrontRight) / 2), _
SkyUpVertexFrontRight.Z - (Vector3.Distance(SkyUpVertexFrontRight, SkyUpVertexBackRight) / 2))

'Set the cameraview to the diameter of the skybox
Camera.FarClip = Vector3.Distance(SkyUpVertexBackLeft, SkyDownVertexFrontRight)

Select Case EffectType
Case Is = "Basic"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
'Redo the vertex array for the next texture, no need to change the U and V corrs because they are the
'same for all textures

TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)

'Setup the basic effect for each skybox texture
For Each TempTextureClass As TextureClass In TextureClassArray
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressU = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressV = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.DepthBufferEnable = False
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace
Next

Case Is = "Custom"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
'Redo the vertex array for the next texture, no need to change the U and V corrs because they are the
'same for all textures

TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, thisEffectType)
End Select

End Sub

The main difference in this sub is that the skybox vertices are given as parameters to the sub, meaning that they and the skybox dimensions are configured manually. This way you can make it any size and position you like. Inside the sub the skybox origin (centre) is calculated from the vertices.

The next sub is OnDeviceReset:

''' <summary>
'''When the graphics device is lost, usually when another process takes control of it, the skybox must be re-initialized.
'''</summary>

Public Sub OnDeviceReset()
'When the XNAGraphics.GarphicsDevice is lost, the skybox must be reinitialized
Select Case EffectType
Case Is = "Basic"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
'Redo the vertex array for the next texture, no need to change the U and V corrs because they are the
'same for all textures

TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, Nothing, Nothing, TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)

'Setup the basic effect for each skybox texture
For Each TempTextureClass As TextureClass In TextureClassArray
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressU = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.SamplerStates(0).AddressV = TextureAddressMode.Clamp
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.DepthBufferEnable = False
TempTextureClass.TheBasicEffect.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace
Next

Case Is = "Custom"
'Initialize the textures
'Setup the Temporary Vertex array

TextureClass.TempVertexArray = New Vector3() {SkyUpVertexBackLeft, SkyUpVertexFrontLeft, SkyUpVertexFrontRight, SkyUpVertexBackRight}
SkyUp.Initialize(SkyUp.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyUp", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
'Redo the vertex array for the next texture, no need to change the U and V corrs because they are the
'same for all textures

TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyDownVertexBackLeft, SkyDownVertexBackRight, SkyDownVertexFrontRight}
SkyDown.Initialize(SkyDown.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyDown", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackRight, SkyUpVertexBackRight, SkyUpVertexFrontRight, SkyDownVertexFrontRight}
SkyRight.Initialize(SkyRight.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyRight", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontLeft, SkyUpVertexFrontLeft, SkyUpVertexBackLeft, SkyDownVertexBackLeft}
SkyLeft.Initialize(SkyLeft.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyLeft", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexFrontRight, SkyUpVertexFrontRight, SkyUpVertexFrontLeft, SkyDownVertexFrontLeft}
SkyBack.Initialize(SkyBack.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyBack", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)
TextureClass.TempVertexArray = New Vector3() {SkyDownVertexBackLeft, SkyUpVertexBackLeft, SkyUpVertexBackRight, SkyDownVertexBackRight}
SkyFront.Initialize(SkyFront.TheTextureFile, TextureClass.TempVertexArray, "skyboxeffect", "SkyBox", TextureClass.TempUCoords, TextureClass.TempVCoords, _
"SkyFront", Camera.ViewMatrix, Camera.ProjectionMatrix, Matrix.Identity, EffectType)

End Select
End Sub

This sub reinitializes the skybox textures when the graphics device is lost for whatever reason.

The Update Sub is next:

''' <summary>
''' If LockSkyBox is True then this sub will update the position of the skybox, such that it is always the
''' same distance from the camera, giving the impression that the sky is always on the horizon
''' </summary>
''' <param name="LockSkybox">The Boolean that decides if this sub should run - Use SkyBoxInstance.LockSkyBox</param>
''' <remarks></remarks>

Public Sub Update(ByVal LockSkybox As Boolean)

'If LockSkybox = False then the skybox position does not need to updated.
If LockSkybox = False Then Exit Sub

'If the camera position has moved.
If Camera.LastCameraPosition <> Camera.CameraPosition Then
' calculate the new Skybox vertex positions
SkyUpVertexBackLeft = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyUpVertexFrontLeft = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyUpVertexFrontRight = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyUpVertexBackRight = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyDownVertexBackLeft = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyDownVertexFrontLeft = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyDownVertexFrontRight = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))
SkyDownVertexBackRight = Vector3.Add(SkyDownVertexFrontRight, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))

'Update the SkyBoxOrigin too
SkyBoxOrigin = Vector3.Add(SkyBoxOrigin, _
Vector3.Subtract(Camera.CameraPosition, Camera.LastCameraPosition))

'assign the last camera position
Camera.LastCameraPosition = Camera.CameraPosition
End If

End Sub

This sub is used only when the LockSkyBox boolean was set to True in the initialize sub or when the LockSkyBox property is set to True at any point in the code. This sub basically updates the skybox vertices when the camera moves position.

the last Sub is the Draw sub:

''' <summary>
''' Updates the views of the skybox
''' </summary>
''' <param name="ProjectionMatrix">The updated projection matrix - Use Camera.ProjectionMatrix</param>
''' <param name="Viewmatrix">The updated view matrix - Use Camera.ViewMatrix</param>

Public Sub Draw(ByVal ViewMatrix As Matrix, ByVal ProjectionMatrix As Matrix)
For Each TempTextureClass As TextureClass In TextureClassArray
Select Case TempTextureClass.TheEffectType
Case Is = "Custom"
TempTextureClass.TheCustomEffect.Parameters("xView").SetValue(ViewMatrix)
TempTextureClass.TheCustomEffect.Parameters("xProjection").SetValue(ProjectionMatrix)
TempTextureClass.Draw()
Case Is = "Basic"
TempTextureClass.TheBasicEffect.View = ViewMatrix
TempTextureClass.TheBasicEffect.Projection = ProjectionMatrix
TempTextureClass.Draw()
End Select
Next
End Sub

As most of the work was done in the initialize sub, all Draw has to do is decide what effect is used, update the texture views and then draw them

The properties for the skybox are as follows:

#Region "Properties"

''' <summary>
''' The Boolean that determines whether the skybox will move in synch with the camera position.
''' </summary>

Public Property LockSkybox() As Boolean
Get
Return LockSkyboxIntoCameraPosition
End Get
Set(ByVal Value As Boolean)
LockSkyboxIntoCameraPosition = Value
End Set
End Property

''' <summary>
''' The Vector3 that represents the skybox's central point, the skybox origin.
''' </summary>

Public ReadOnly Property Origin() As Vector3
Get
Return SkyBoxOrigin
End Get
End Property

#End Region

Not much to report here, they just get or set the LockSkyBox variable and as the Origin property is readonly, it only gets the skybox Origin value. Right, that all for the skybox class, we will now open the XNAEngine class and make a few changes:

in the "Objects and Variables" region we have created an instance of the skyboxclass

Private SkyBox As New SkyBoxClass ' The Skybox

In Public Sub New we have added a new Handler

'Add an event handler that runs when the device is reset
AddHandler XNAGraphics.DeviceReset, New EventHandler(AddressOf Me.OnDeviceReset)

We have then added a new sub that is linked to the new Handler

'the following code runs when the window is resized
Private Sub OnDeviceReset(ByVal sender As Object, ByVal e As EventArgs)
'Re-initialize the skybox
SkyBox.OnDeviceReset()
End Sub

As you can see this sub runs the skybox.OnDeviceReset sub and because it is linked to the OnDeviceReset Handler it will run when xna loses the graphics device. This was necessary when pressing F1 to toggle full screen.

We have rearranged the initialize sub:

'Initializes the XNAGame class instance, runs once when game starts
Protected Overrides Sub Initialize()

MyBase.Initialize() 'comes first, because this loads LoadGraphicsContent below
'Initialize Camera First
Camera.Initialize()
'Then Initialize Skybox
SkyBox.Initialize("SkyUp", "SkyDown", "SkyLeft", "SkyRight", "SkyBack", "SkyFront", 1.0F, "Basic", True)
'Then the rest

'Sound.InitializeEngine()
Dice.Initialize(XNAGameProjectFolder & "\Content\Models\dice") ' Initialize the model and load content

End Sub

I have moved MyBase.Initialize to the top because it runs LoadAllGraphicsContent and any textures, models, etc will then already be loaded for any of the other initialize subs to use, next we initialize the camera, then the skybox and then everything else.

The Update sub becomes:

'Updates all game components, runs before every Draw Loop
Protected Overrides Sub Update(ByVal gameTime As GameTime)
'Locate mouse position
Camera.CurrentMousePosX = Mouse.GetState.X
Camera.CurrentMousePosY = Mouse.GetState.Y

'Find what keys are pressed and save it in GetKeys
Dim GetKeys As KeyboardState = Keyboard.GetState
Dim GetMouse As MouseState = Mouse.GetState

'Exit the game with Escape
If GetKeys.IsKeyDown(Keys.Escape) Then
MyBase.Exit()
End If

'Change from Windowed to Fullscreen
If GetKeys.IsKeyDown(Keys.F1) Then
XNAGraphics.ToggleFullScreen()
End If

'If the mouse has moved
Camera.Update(GetKeys, GetMouse, gameTime) ' Update the camera position, view, etc
'assign the previous mouse position
Camera.PreviousMousePosX = Camera.CurrentMousePosX
Camera.PreviousMousePosY = Camera.CurrentMousePosY
'Update the skybox
SkyBox.Update(True)

'Add you game code here

'Show the game framerate in the title bar
Me.Window.Title = String.Format("The framerate is {0}", Framerate.CalculateFrameRate())

' Sound.UpdateEngine()
MyBase.Update(gameTime)
Application.DoEvents()
End Sub

As you can see the skybox.update sub runs after the camera and the LockSkyBox parameter is set to True in this case so the skybox will move position in synch with the camera.

Last of all we have the Draw Sub:

'Renders any Backbuffer draw data to the screen
Protected Overrides Sub Draw(ByVal gameTime As GameTime)
XNAGraphics.GraphicsDevice.Clear(Color.Black)

'Draw Camera First
Camera.Draw()
'Then Draw Skybox
SkyBox.Draw(Camera.ViewMatrix, Camera.ProjectionMatrix)
'Then draw the rest

'Draw Dice
Dice.Draw(Camera.ViewMatrix, Camera.ProjectionMatrix, True)

MyBase.Draw(gameTime)
End Sub

The skybox draw sub comes after the camera and before the dice, remember that anything drawn last will appear on top , so the skybox is drawn first. You may notice in the source code that there is a camera class, this is only half done but it is useful to check the skybox. W and S will rotate the camera Up and Down, A and D will rotate the camera left and right, and the arrow keys will moved the camera. Don't rotate the camera in more than one direction at any one time cos' it doesn't like that . If you run the game you will get :

XNA in VB.NET - 3D Skybox

and if you rotate left

XNA in VB.NET - 3D SkyBox Rotated

Right, I will now work on that 3D quaternion camera and get it finished and as usual, when I'm done, I will upload it to the tutorial page, so until then, Enjoy.

Please move onto the next tutorial for the last part of the skybox.

 

3DSkyBox Source Code - 11,246Kb
Next Tutorial - A Quaternion Camera

 

     
 
 
     

 

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