Export (0) Print
Expand All

How To: Create a SkySphere

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

This example assumes 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.

The Complete Sample

The code in this topic shows you the technique. You can download a complete code sample for this topic, including full source code and any additional supporting files required by the sample.

The Skysphere Effect

The vertex shader for the SkySphere effect takes the position of each vertex and extracts the rotation of the camera from the view matrix. It 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;
};

The pixel shader for the SkySphere effect uses the texCUBE function to choose a pixel on the cube map.

To Create the SkySphere Effect

  1. Imagine the sphere model surrounding the camera viewpoint, and the cube map surrounding the sphere model (as a cube).

  2. 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 the sky look more realistic (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 = CLAMP;
        AddressV = CLAMP;
    };
    float4 SkyboxPixelShader( float3 SkyCoord : TEXCOORD0 ) : COLOR
    {
        // grab the pixel color value from the skybox cube map
        return texCUBE(SkyboxS, SkyCoord);
    };
    

    The technique for the SkySphere effect has one pass. Because you want all the triangles on the sphere to render, the technique has to disable culling. 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.LoadContent, 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 LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);
        Model = Content.Load<Model>("redtorus");
        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. 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. 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>("SkySphere");
    TextureCube SkyboxTexture = Content.Load<TextureCube>("uffizi_cross");
    SkySphere = Content.Load<Model>("SphereHighPoly");
    
    // Set the parameters of the effect
    //SkySphereEffect.Parameters["ViewMatrix"].SetValue(myCamera2.ViewMatrix);
    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, 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.View;
                effect.View = myCamera.ViewMatrix;
                effect.Projection = projectionMatrix;
            }
            //Draw the mesh, will use the effects set above.
            mesh.Draw();
        }
    
  7. Draw the SkySphere by setting parameters to the SkySphere Effect.

  8. Draw the SkySphere Model.

    In this case we set the View and Projection matrices.

    // Set the View and Projection matrix for the effect
    SkySphereEffect.Parameters["ViewMatrix"].SetValue(myCamera.ViewMatrix);
    //SkySphereEffect.Parameters["ViewMatrix"].SetValue(myCamera.View);
    SkySphereEffect.Parameters["ProjectionMatrix"].SetValue(projectionMatrix);
    // Draw the sphere model that the effect projects onto
    foreach (ModelMesh mesh in SkySphere.Meshes)
    {
        mesh.Draw();
    }
    
  9. Because this shader sets some render states before it runs, 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);
            }
        }
    }
    #region File Description
    //-----------------------------------------------------------------------------
    // Program.cs
    //
    // Microsoft XNA Community Game Platform
    // Copyright (C) Microsoft Corporation. All rights reserved.
    //-----------------------------------------------------------------------------
    #endregion
    
    using System;
    
    namespace SkySphere
    {
        static class Program
        {
            /// <summary>
            /// The main entry point for the application.
            /// </summary>
            static void Main(string[] args)
            {
                using (Game1 game = new Game1())
                {
                    game.Run();
                }
            }
        }
    }
    
    #region File Description
    //-----------------------------------------------------------------------------
    // SampleCamera.cs
    //
    // Microsoft XNA Community Game Platform
    // Copyright (C) Microsoft Corporation. All rights reserved.
    //-----------------------------------------------------------------------------
    #endregion
    
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Audio;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using Microsoft.Xna.Framework.Content;
    using Microsoft.Xna.Framework.Storage;
    #endregion
    
    namespace SkySphere
    {
        public enum SampleArcBallCameraMode
        {
            /// <summary>
            /// A totally free-look arcball that orbits relative
            /// to its orientation
            /// </summary>
            Free = 0,
    
            /// <summary>
            /// A camera constrained by roll so that orbits only
            /// occur on latitude and longitude
            /// </summary>
            RollConstrained = 1
    
        }
    
        /// <summary>
        /// An example arc ball camera
        /// </summary>
        public class SampleArcBallCamera
        {
            #region Helper Functions
            /// <summary>
            /// Uses a pair of keys to simulate a positive or negative axis input.
            /// </summary>
            public static float ReadKeyboardAxis(KeyboardState keyState, Keys downKey,
                Keys upKey)
            {
                float value = 0;
    
                if (keyState.IsKeyDown(downKey))
                    value -= 1.0f;
    
                if (keyState.IsKeyDown(upKey))
                    value += 1.0f;
    
                return value;
            }
            #endregion
    
            #region Fields
            /// <summary>
            /// The location of the look-at target
            /// </summary>
            private Vector3 targetValue;
    
            /// <summary>
            /// The distance between the camera and the target
            /// </summary>
            private float distanceValue;
    
            /// <summary>
            /// The orientation of the camera relative to the target
            /// </summary>
            private Quaternion orientation;
    
            private float inputDistanceRateValue;
            private const float InputTurnRate = 3.0f;
            private SampleArcBallCameraMode mode;
            private float yaw, pitch;
            #endregion
    
            #region Constructors
            /// <summary>
            /// Create an arcball camera that allows free orbit around a target point.
            /// </summary>
            public SampleArcBallCamera(SampleArcBallCameraMode controlMode)
            {
                //orientation quaternion assumes a Pi rotation so you're facing the "front"
                //of the model (looking down the +Z axis)
                orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.Pi);
    
                mode = controlMode;
                inputDistanceRateValue = 4.0f;
                yaw = MathHelper.Pi;
                pitch = 0;
            }
            #endregion
    
            #region Calculated Camera Properties
            /// <summary>
            /// Get the forward direction vector of the camera.
            /// </summary>
            public Vector3 Direction
            {
                get
                {
                    //R v R' where v = (0,0,-1,0)
                    //The equation can be reduced because we know the following things:
                    //  1.  We're using unit quaternions
                    //  2.  The initial aspect does not change
                    //The reduced form of the same equation follows
                    Vector3 dir = Vector3.Zero;
                    dir.X = -2.0f *
                        ((orientation.X * orientation.Z) + (orientation.W * orientation.Y));
                    dir.Y = 2.0f *
                        ((orientation.W * orientation.X) - (orientation.Y * orientation.Z));
                    dir.Z =
                        ((orientation.X * orientation.X) + (orientation.Y * orientation.Y)) -
                        ((orientation.Z * orientation.Z) + (orientation.W * orientation.W));
                    Vector3.Normalize(ref dir, out dir);
                    return dir;
    
                }
            }
    
            /// <summary>
            /// Get the right direction vector of the camera.
            /// </summary>
            public Vector3 Right
            {
                get
                {
                    //R v R' where v = (1,0,0,0)
                    //The equation can be reduced because we know the following things:
                    //  1.  We're using unit quaternions
                    //  2.  The initial aspect does not change
                    //The reduced form of the same equation follows
                    Vector3 right = Vector3.Zero;
                    right.X =
                        ((orientation.X * orientation.X) + (orientation.W * orientation.W)) -
                        ((orientation.Z * orientation.Z) + (orientation.Y * orientation.Y));
                    right.Y = 2.0f *
                        ((orientation.X * orientation.Y) + (orientation.Z * orientation.W));
                    right.Z = 2.0f *
                        ((orientation.X * orientation.Z) - (orientation.Y * orientation.W));
    
                    return right;
    
                }
            }
    
            /// <summary>
            /// Get the up direction vector of the camera.
            /// </summary>
            public Vector3 Up
            {
                get
                {
                    //R v R' where v = (0,1,0,0)
                    //The equation can be reduced because we know the following things:
                    //  1.  We're using unit quaternions
                    //  2.  The initial aspect does not change
                    //The reduced form of the same equation follows
                    Vector3 up = Vector3.Zero;
                    up.X = 2.0f *
                        ((orientation.X * orientation.Y) - (orientation.Z * orientation.W));
                    up.Y =
                        ((orientation.Y * orientation.Y) + (orientation.W * orientation.W)) -
                        ((orientation.Z * orientation.Z) + (orientation.X * orientation.X));
                    up.Z = 2.0f *
                        ((orientation.Y * orientation.Z) + (orientation.X * orientation.W));
                    return up;
                }
            }
    
            
    
            /// <summary>
            /// Get the View (look at) Matrix defined by the camera
            /// </summary>
            public Matrix ViewMatrix
            {
                get
                {
                    return Matrix.CreateLookAt(targetValue -
                        (Direction * distanceValue), targetValue, Up);
                }
            }
    
            public SampleArcBallCameraMode ControlMode
            {
                get { return mode; }
                set
                {
                    if (value != mode)
                    {
                        mode = value;
                        SetCamera(targetValue - (Direction* distanceValue),
                                  targetValue, Vector3.Up);
                    }
                }
            }
    
    
            #endregion
    
            #region Position Controls
            /// <summary>
            /// Get or Set the current target of the camera
            /// </summary>
            public Vector3 Target
            {
                get
                { return targetValue; }
                set
                { targetValue = value; }
            }
    
            /// <summary>
            /// Get or Set the camera's distance to the target.
            /// </summary>
            public float Distance
            {
                get
                { return distanceValue; }
                set
                { distanceValue = value; }
            }
    
            /// <summary>
            /// Sets the rate of distance change 
            /// when automatically handling input
            /// </summary>
            public float InputDistanceRate
            {
                get
                { return inputDistanceRateValue; }
                set
                { inputDistanceRateValue = value; }
            }
    
            /// <summary>
            /// Get/Set the camera's current postion.
            /// </summary>
            public Vector3 Position
            {
                get
                {
                    return targetValue - (Direction * Distance);
                }
                set
                {
                    SetCamera(value, targetValue, Vector3.Up);
                }
            }
            #endregion
    
            #region Orbit Controls
            /// <summary>
            /// Orbit directly upwards in Free camera or on
            /// the longitude line when roll constrained
            /// </summary>
            public void OrbitUp(float angle)
            {
    
                switch (mode)
                {
                    case SampleArcBallCameraMode.Free:
                        //rotate the aspect by the angle 
                        orientation = orientation *
                         Quaternion.CreateFromAxisAngle(Vector3.Right, -angle);
    
                        //normalize to reduce errors
                        Quaternion.Normalize(ref orientation, out orientation);
                        break;
                    case SampleArcBallCameraMode.RollConstrained:
                        //update the yaw
                        pitch -= angle;
    
                        //constrain pitch to vertical to avoid confusion
                        pitch = MathHelper.Clamp(pitch, -(MathHelper.PiOver2) + .0001f,
                            (MathHelper.PiOver2) - .0001f);
    
                        //create a new aspect based on pitch and yaw
                        orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, -yaw) *
                            Quaternion.CreateFromAxisAngle(Vector3.Right, pitch);
                        break;
                }
            }
    
            /// <summary>
            /// Orbit towards the Right vector in Free camera
            /// or on the latitude line when roll constrained
            /// </summary>
            public void OrbitRight(float angle)
            {
                switch (mode)
                {
                    case SampleArcBallCameraMode.Free:
                        //rotate the aspect by the angle 
                        orientation = orientation *
                            Quaternion.CreateFromAxisAngle(Vector3.Up, angle);
    
                        //normalize to reduce errors
                        Quaternion.Normalize(ref orientation, out orientation);
                        break;
                    case SampleArcBallCameraMode.RollConstrained:
                        //update the yaw
                        yaw -= angle;
    
                        //float mod yaw to avoid eventual precision errors
                        //as we move away from 0
                        yaw = yaw % MathHelper.TwoPi;
    
                        //create a new aspect based on pitch and yaw
                        orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, -yaw) *
                            Quaternion.CreateFromAxisAngle(Vector3.Right, pitch);
                        
                        //normalize to reduce errors
                        Quaternion.Normalize(ref orientation, out orientation);
                        break;
                }
            }
    
            /// <summary>
            /// Rotate around the Forward vector 
            /// in free-look camera only
            /// </summary>
            public void RotateClockwise(float angle)
            {
               
    
                switch (mode)
                {
                    case SampleArcBallCameraMode.Free:
                        //rotate the orientation around the direction vector
                        orientation = orientation *
                            Quaternion.CreateFromAxisAngle(Vector3.Forward, angle);
                        Quaternion.Normalize(ref orientation, out orientation);
                        break;
                    case SampleArcBallCameraMode.RollConstrained:
                        //Do nothing, we don't want to roll at all to stay consistent
                        break;
                }
            }
    
            /// <summary>
            /// Sets up a quaternion & position from vector camera components
            /// and oriented the camera up
            /// </summary>
            /// <param name="eye">The camera position</param>
            /// <param name="lookAt">The camera's look-at point</param>
            /// <param name="up"></param>
            public void SetCamera(Vector3 position, Vector3 target, Vector3 up)
            {
                //Create a look at matrix, to simplify matters a bit
                Matrix temp = Matrix.CreateLookAt(position, target, up);
    
                //invert the matrix, since we're determining the
                //orientation from the rotation matrix in RH coords
                temp = Matrix.Invert(temp);
    
                //set the postion
                targetValue = target;
    
                //create the new aspect from the look-at matrix
                orientation = Quaternion.CreateFromRotationMatrix(temp);
    
                //When setting a new eye-view direction 
                //in one of the gimble-locked modes, the yaw and
                //pitch gimble must be calculated.
                if (mode != SampleArcBallCameraMode.Free)
                {
                    //first, get the direction projected on the x/z plne
                    Vector3 dir = Direction;
                    dir.Y = 0;
                    if (dir.Length() == 0f)
                    {
                        dir = Vector3.Forward;
                    }
                    dir.Normalize();
    
                    //find the yaw of the direction on the x/z plane
                    //and use the sign of the x-component since we have 360 degrees
                    //of freedom
                    yaw = (float)(Math.Acos(-dir.Z) * Math.Sign(dir.X));
    
                    //Get the pitch from the angle formed by the Up vector and the 
                    //the forward direction, then subtracting Pi / 2, since 
                    //we pitch is zero at Forward, not Up.
                    pitch = (float)-(Math.Acos(Vector3.Dot(Vector3.Up, Direction))
                        - MathHelper.PiOver2);
                }
            }
            #endregion
    
            #region Input Handlers
            /// <summary>
            /// Handle default keyboard input for a camera
            /// </summary>
            public void HandleDefaultKeyboardControls(KeyboardState kbState,
                GameTime gameTime)
            {
                if (gameTime == null)
                {
                    throw new ArgumentNullException("gameTime", 
                        "GameTime parameter cannot be null.");
                }
    
                float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
    
                float dX = elapsedTime * ReadKeyboardAxis(
                    kbState, Keys.A, Keys.D) * InputTurnRate;
                float dY = elapsedTime * ReadKeyboardAxis(
                    kbState, Keys.S, Keys.W) * InputTurnRate;
    
                if (dY != 0) OrbitUp(dY);
                if (dX != 0) OrbitRight(dX);
    
    
                distanceValue += ReadKeyboardAxis(kbState, Keys.Z, Keys.X)
                    * inputDistanceRateValue * elapsedTime;
                if (distanceValue < .001f) distanceValue = .001f;
    
                if (mode != SampleArcBallCameraMode.Free)
                {
                    float dR = elapsedTime * ReadKeyboardAxis(
                        kbState, Keys.Q, Keys.E) * InputTurnRate;
                    if (dR != 0) RotateClockwise(dR);
                }
            }
    
            /// <summary>
            /// Handle default gamepad input for a camera
            /// </summary>
            public void HandleDefaultGamepadControls(GamePadState gpState, GameTime gameTime)
            {
                if (gameTime == null)
                {
                    throw new ArgumentNullException("gameTime", 
                        "GameTime parameter cannot be null.");
                }
    
                if (gpState.IsConnected)
                {
                    float elapsedTime = (float)gameTime.ElapsedGameTime.TotalSeconds;
    
                    float dX = gpState.ThumbSticks.Right.X * elapsedTime * InputTurnRate;
                    float dY = gpState.ThumbSticks.Right.Y * elapsedTime * InputTurnRate;
                    float dR = gpState.Triggers.Right * elapsedTime * InputTurnRate;
                    dR-= gpState.Triggers.Left * elapsedTime * InputTurnRate;
    
                    //change orientation if necessary
                    if(dY != 0) OrbitUp(dY);
                    if(dX != 0) OrbitRight(dX);
                    if (dR != 0) RotateClockwise(dR);
    
                    //decrease distance to target (move forward)
                    if (gpState.Buttons.A == ButtonState.Pressed)
                    {
                        distanceValue -= elapsedTime * inputDistanceRateValue;
                    }
                    //increase distance to target (move back)
                    if (gpState.Buttons.B == ButtonState.Pressed)
                    {
                        distanceValue += elapsedTime * inputDistanceRateValue;
                    }
                    if (distanceValue < .001f) distanceValue = .001f;
                }  
            }
            #endregion
    
            #region Misc Camera Controls
            /// <summary>
            /// Reset the camera to the defaults set in the constructor
            /// </summary>
            public void Reset()
            {
                //orientation quaternion assumes a Pi rotation so you're facing the "front"
                //of the model (looking down the +Z axis)
                orientation = Quaternion.CreateFromAxisAngle(Vector3.Up, MathHelper.Pi);
                distanceValue = 3f;
                targetValue = Vector3.Zero;
                yaw = MathHelper.Pi;
                pitch = 0;
            }
            #endregion
        }
    }
    

Community Additions

ADD
Show:
© 2015 Microsoft