Export (0) Print
Expand All

How to: Create a SkySphere

This example demonstrates how to create an effect that will apply a skybox-style TextureCube ("cube map") to a sphere.

This example presumes that you have a Camera object for handling the position and view matrix of the 3D camera, along with a simple Model to display. The SkySphere effect also requires a sphere model and a skybox cube map.

To create the SkySphere effect

  1. The vertex shader for the SkySphere effect takes the position of each vertex and extracts the rotation of the camera from the view matrix, then applies the projection matrix to that for the final vertex position. For calculating the pixel values between the vertices, the vertex shader passes the unmodified position.

    uniform extern float4x4 ViewMatrix;
    uniform extern float4x4 ProjectionMatrix;
    
    void SkyboxVertexShader( float3 pos : POSITION0,
                             out float4 SkyPos : POSITION0,
                             out float3 SkyCoord : TEXCOORD0 )
    {
        // Calculate rotation. Using a float3 result, so translation is ignored
        float3 rotatedPosition = mul(pos, ViewMatrix);           
        // Calculate projection, moving all vertices to the far clip plane 
        // (w and z both 1.0)
        SkyPos = mul(float4(rotatedPosition, 1), ProjectionMatrix).xyww;    
    
        SkyCoord = pos;
    };
  2. The pixel shader for the SkySphere effect uses the texCUBE function to choose a pixel on the cube map. Imagine the sphere model surrounding the camera viewpoint, and the cube map surrounding the sphere model (as a cube). Then draw a line from the camera position to a point on the sphere model, and continue that line until it hits the cube map. This is the pixel returned by texCUBE. This has the effect of transforming the cube into a different shape, which makes a more realistic sky (the cube often has obvious corners). You do not have to use a sphere for this effect; a flattened or oblong sphere may look better for some applications.

    uniform extern texture SkyboxTexture;
    sampler SkyboxS = sampler_state
    {
        Texture = <SkyboxTexture>;
        MinFilter = LINEAR;
        MagFilter = LINEAR;
        MipFilter = LINEAR;
        AddressU = WRAP;
        AddressV = WRAP;
    };
    float4 SkyboxPixelShader( float3 SkyCoord : TEXCOORD0 ) : COLOR
    {
        // grab the pixel color value from the skybox cube map
        return texCUBE(SkyboxS, SkyCoord);
    };
  3. The technique for the SkySphere effect has one pass. The technique has to disable culling, because you want all the triangles on the sphere to render. Since the camera is inside the sphere, normal culling would result in no vertices to shade. Depth writes are also disabled, because we want the environment to render behind everything else, without appearing to clip any objects.

    technique SkyboxTechnique
    {
        pass P0
        {
            vertexShader = compile vs_2_0 SkyboxVertexShader();
            pixelShader = compile ps_2_0 SkyboxPixelShader();
    
            // We're drawing the inside of a model
            CullMode = None;  
            // We don't want it to obscure objects with a Z < 1
            ZWriteEnable = false; 
        }
    }

To apply the SkySphere effect

  1. In your Game.LoadGraphicsContent, load the content that you normally draw in your scene.

    Vector3 ModelPosition;
    float ModelRotation = 0.0f;
    Model Model;
    Model SkySphere;
    Effect SkySphereEffect;
    Matrix projectionMatrix;
    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        if (loadAllContent)
        {
            // Load the model to draw, and it's position
            Model = content.Load<Model>( "model" );
            ModelPosition = Vector3.Zero;
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1.0f, 10000f );
  2. Load the SkySphere effect from the Content Manager.

  3. Next, load a cube map into a TextureCube object. A cube map is a texture with six sides that form a cube; for a skybox (or sphere), the cube map is a picture of the far distance in the scene.

  4. Then set the parameters the SkySphere requires, including the TextureCube to draw.

    // Load the effect, the texture it uses, and 
    // the model used for drawing it
    SkySphereEffect = content.Load<Effect>( "skybox" );
    TextureCube SkyboxTexture = content.Load<TextureCube>( "largestarfield3D" );
    SkySphere = content.Load<Model>( "sphere" );
    
    // Set the parameters of the effect
    SkySphereEffect.Parameters["ViewMatrix"].SetValue( myCamera.ViewMatrix );
    SkySphereEffect.Parameters["ProjectionMatrix"].SetValue( projectionMatrix );
    SkySphereEffect.Parameters["SkyboxTexture"].SetValue( SkyboxTexture );
  5. Since the Effect and the Model are loaded separately, you need to apply the SkySphere effect to each Effect property on the ModelMeshPart of the SkySphere model.

            // Set the Skysphere Effect to each part of the Skysphere model
            foreach (ModelMesh mesh in SkySphere.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = SkySphereEffect;
                }
            }
        }
    }
  6. In your Game.Draw, draw your scene as normal.

    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );
    
        //Draw the model, a model can have multiple meshes, so loop
        foreach (ModelMesh mesh in Model.Meshes)
        {
            //This is where the mesh orientation is set, as well as our camera and projection
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
                effect.World = Matrix.Identity * Matrix.CreateRotationY( ModelRotation )
                    * Matrix.CreateTranslation( ModelPosition );
                effect.View = myCamera.ViewMatrix;
                effect.Projection = projectionMatrix;
            }
            //Draw the mesh, will use the effects set above.
            mesh.Draw();
        }
  7. Then draw the SkySphere by setting parameters to the SkySphere Effect, and then drawing the SkySphere Model. In this case we set the View and Projection matrices.

    // Draw the effect first, otherwise it will cover objects
    
    // Set the View and Projection matrix for the effect
    SkySphereEffect.Parameters["ViewMatrix"].SetValue( myCamera.ViewMatrix );
    SkySphereEffect.Parameters["ProjectionMatrix"].SetValue( projectionMatrix );
    // Draw the sphere model that the effect projects onto
    foreach (ModelMesh mesh in SkySphere.Meshes)
    {
        mesh.Draw();
    }
  8. Because this shader sets some render states before it runs, you must reset the affected render states to the values we want to use for further rendering.

        // Undo the renderstate settings from the shader
        graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
        base.Draw( gameTime );
    }

Complete Example

uniform extern float4x4 ViewMatrix;
uniform extern float4x4 ProjectionMatrix;

void SkyboxVertexShader( float3 pos : POSITION0,
                         out float4 SkyPos : POSITION0,
                         out float3 SkyCoord : TEXCOORD0 )
{
    // Calculate rotation. Using a float3 result, so translation is ignored
    float3 rotatedPosition = mul(pos, ViewMatrix);           
    // Calculate projection, moving all vertices to the far clip plane 
    // (w and z both 1.0)
    SkyPos = mul(float4(rotatedPosition, 1), ProjectionMatrix).xyww;    

    SkyCoord = pos;
};
uniform extern texture SkyboxTexture;
sampler SkyboxS = sampler_state
{
    Texture = <SkyboxTexture>;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = LINEAR;
    AddressU = WRAP;
    AddressV = WRAP;
};
float4 SkyboxPixelShader( float3 SkyCoord : TEXCOORD0 ) : COLOR
{
    // grab the pixel color value from the skybox cube map
    return texCUBE(SkyboxS, SkyCoord);
};

technique SkyboxTechnique
{
    pass P0
    {
        vertexShader = compile vs_2_0 SkyboxVertexShader();
        pixelShader = compile ps_2_0 SkyboxPixelShader();

        // We're drawing the inside of a model
        CullMode = None;  
        // We don't want it to obscure objects with a Z < 1
        ZWriteEnable = false; 
    }
}
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Storage;

public class Game1 : Microsoft.Xna.Framework.Game
{
    GraphicsDeviceManager graphics;
    ContentManager content;

    SampleArcBallCamera myCamera;

    public Game1()
    {
        graphics = new GraphicsDeviceManager( this );
        content = new ContentManager( Services );

        myCamera = new SampleArcBallCamera( SampleArcBallCameraMode.Free );
    }


    protected override void Initialize()
    {
        myCamera.Target = new Vector3( 0, 0, 0 );
        myCamera.Distance = -50f;

        base.Initialize();
    }
    Vector3 ModelPosition;
    float ModelRotation = 0.0f;
    Model Model;
    Model SkySphere;
    Effect SkySphereEffect;
    Matrix projectionMatrix;
    protected override void LoadGraphicsContent( bool loadAllContent )
    {
        if (loadAllContent)
        {
            // Load the model to draw, and it's position
            Model = content.Load<Model>( "model" );
            ModelPosition = Vector3.Zero;
            projectionMatrix = Matrix.CreatePerspectiveFieldOfView( MathHelper.PiOver4, 4.0f / 3.0f, 1.0f, 10000f );

            // Load the effect, the texture it uses, and 
            // the model used for drawing it
            SkySphereEffect = content.Load<Effect>( "skybox" );
            TextureCube SkyboxTexture = content.Load<TextureCube>( "largestarfield3D" );
            SkySphere = content.Load<Model>( "sphere" );

            // Set the parameters of the effect
            SkySphereEffect.Parameters["ViewMatrix"].SetValue( myCamera.ViewMatrix );
            SkySphereEffect.Parameters["ProjectionMatrix"].SetValue( projectionMatrix );
            SkySphereEffect.Parameters["SkyboxTexture"].SetValue( SkyboxTexture );

            // Set the Skysphere Effect to each part of the Skysphere model
            foreach (ModelMesh mesh in SkySphere.Meshes)
            {
                foreach (ModelMeshPart part in mesh.MeshParts)
                {
                    part.Effect = SkySphereEffect;
                }
            }
        }
    }

    protected override void UnloadGraphicsContent( bool unloadAllContent )
    {
        if (unloadAllContent == true)
        {
            content.Unload();
        }
    }


    protected override void Update( GameTime gameTime )
    {
        // Allows the default game to exit on Xbox 360 and Windows
        if (GamePad.GetState( PlayerIndex.One ).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        GamePadState PlayerOne = GamePad.GetState( PlayerIndex.One );
        if (PlayerOne.Buttons.A == ButtonState.Pressed)
        {
            myCamera.Target = new Vector3( 0, 0, 0 );
            myCamera.Distance = -50f;
        }

        myCamera.OrbitUp( PlayerOne.ThumbSticks.Right.Y / 4 );
        myCamera.OrbitRight( PlayerOne.ThumbSticks.Right.X / 4 );

        base.Update( gameTime );
    }
    protected override void Draw( GameTime gameTime )
    {
        graphics.GraphicsDevice.Clear( Color.CornflowerBlue );

        //Draw the model, a model can have multiple meshes, so loop
        foreach (ModelMesh mesh in Model.Meshes)
        {
            //This is where the mesh orientation is set, as well as our camera and projection
            foreach (BasicEffect effect in mesh.Effects)
            {
                effect.EnableDefaultLighting();
                effect.World = Matrix.Identity * Matrix.CreateRotationY( ModelRotation )
                    * Matrix.CreateTranslation( ModelPosition );
                effect.View = myCamera.ViewMatrix;
                effect.Projection = projectionMatrix;
            }
            //Draw the mesh, will use the effects set above.
            mesh.Draw();
        }      
        // Draw the effect first, otherwise it will cover objects

        // Set the View and Projection matrix for the effect
        SkySphereEffect.Parameters["ViewMatrix"].SetValue( myCamera.ViewMatrix );
        SkySphereEffect.Parameters["ProjectionMatrix"].SetValue( projectionMatrix );
        // Draw the sphere model that the effect projects onto
        foreach (ModelMesh mesh in SkySphere.Meshes)
        {
            mesh.Draw();
        }

        // Undo the renderstate settings from the shader
        graphics.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
        graphics.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
        base.Draw( gameTime );
    }
}
Show:
© 2014 Microsoft