Bagikan melalui


Creating a SkySphere

Demonstrates how to create an effect that will apply a skybox-style 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.

Complete Sample

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

Download SkySphere_Sample.zip.

Skysphere Effect

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

  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);
    };
    
    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 also are 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; 
        }
    }
    
    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(
        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["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
    //            GraphicsDevice.RenderState.CullMode =
    //                CullMode.CullCounterClockwiseFace;
    //            GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
    
    base.Draw(gameTime);