Google
 
Showing posts with label SkyBox. Show all posts
Showing posts with label SkyBox. Show all posts

Thursday, August 16, 2007

Generic XNA - How to get images from a cube map

OK, may be a bit of an odd post, but I thought I would put it up any way.

I have 2 methods for creating a skybox now, either by using 6 textured quads and positioning them around the camera at set distance and scale or using an inverted cube mesh and then applying a cube map texture to it. So for both these methods I need textures to generate these skybox's, and the majority of images I have found were in cube maps but this then means that I can only use them in one of my skybox methods.

So I had a look at the DirectX Texture tool to see if there was a way to dissect a cube map there (as this is where I create my maps at the moment) but could not find how to do this here. So I decided to write my own.

In short, what I am posting here is a method to take a given cube map and break it up into it's 6 constituent parts and save them as jpg files so you can use them in the textured quad skybox method.

Here is the code:
   cube = game.Content.Load<TextureCube>("Content/textures/skybox/CubeMap");
   Color[][] sideData = new Color[6][];

   for (int s = 0; s < 6; s++)
   {
       sideData[s] = new Color[cube.Size * cube.Size];
       cube.GetData<Color>((CubeMapFace)s, sideData[s]);
       Texture2D side = new Texture2D(cube.GraphicsDevice, cube.Size, cube.Size, 0, ResourceUsage.AutoGenerateMipMap, cube.Format);
       side.SetData<Color>(sideData[s]);
       side.Save("Content/textures/skybox/" + ((CubeMapFace)s).ToString() + ".jpg", ImageFileFormat.Jpg);
   }



I guess you could reverse this to take 6 images and generate a cube map possibly to build a cube map to be used as an environment cube map used in a reflection shader or just to create a cube map for a skybox.

I intend to post an example of creating cube maps on the fly so that reflective shades will give true reflections.

Hope you find it useful.

Thursday, July 26, 2007

Generic XNA - SkyBox



So a sky box, having done the Hazy Mind tutorial and had a play with Riemers tutorials I decided to create my own skybox. I like the way both tutorials do there sky box's but I kind of like cube maps, it means I keep 6 textures in one place, have only one texture variable and so makes my life a little easier when passing textures to my skybox. So my version of a skybox uses a box mesh (the same one from Riemers tutorial) but instead of passing a texture to each side of the box or having to pass multiple textures to the mesh I just pass a single cube map. I then wrote a shader to render the cube map on the skybox mesh.

In this example I also have a basic camera that is a static class, this does not use the GameComponents at all as I want it to be used by all drawable elements in my code, so this class is driven by the game loop directly from in my game class.

So, the code...

SkyBox Class

   public class SkyBox : DrawableGameComponent
   {
       private Model skyboxMesh;
       public Vector3 myPosition;
       public Quaternion myRotation;
       public Vector3 myScale;

       public TextureCube environ;

       Effect shader;

       string modelAsset;
       string shaderAsset;
       string textureAsset;

       ContentManager content;

       public SkyBox(Game game,string modelAsset,string shaderAsset,string textureAsset) : base(game)
       {
           content = new ContentManager(game.Services);

           this.modelAsset = modelAsset;
           this.shaderAsset = shaderAsset;
           this.textureAsset = textureAsset;

           myPosition = new Vector3(0, 0, 0);
           myRotation = new Quaternion(0, 0, 0, 1);
           myScale = new Vector3(55, 55, 55);
       }

       protected override void LoadGraphicsContent(bool loadAllContent)
       {
           if (loadAllContent)
           {
               skyboxMesh = content.Load<Model>(modelAsset);
               shader = content.Load<Effect>(shaderAsset);
               environ = content.Load<TextureCube>(textureAsset);
           }
           base.LoadGraphicsContent(loadAllContent);
       }

       public override void Draw(GameTime gameTime)
       {
           Matrix World = Matrix.CreateScale(myScale) *
                           Matrix.CreateFromQuaternion(myRotation) *
                           Matrix.CreateTranslation(Camera.myPosition);

           shader.Parameters["World"].SetValue(World);
           shader.Parameters["View"].SetValue(Camera.myView);
           shader.Parameters["Projection"].SetValue(Camera.myProjection);
           shader.Parameters["surfaceTexture"].SetValue(environ);

           shader.Parameters["EyePosition"].SetValue(Camera.myPosition);

           for (int pass = 0; pass < shader.CurrentTechnique.Passes.Count; pass++)
           {
               for (int msh = 0; msh < skyboxMesh.Meshes.Count; msh++)
               {
                   ModelMesh mesh = skyboxMesh.Meshes[msh];
                   for (int prt = 0; prt < mesh.MeshParts.Count; prt++)
                       mesh.MeshParts[prt].Effect = shader;
                   mesh.Draw();
               }
           }

           base.Draw(gameTime);
       }        
   }


Camera Class

   public sealed class Camera
   {
       public static Vector3 myPosition;
       public static Vector3 myTarget;
       public static Quaternion myRotation;

       private static Matrix myWorld;
       public static Matrix myView;
       public static Matrix myProjection;
       public static Viewport myViewport;

       private Camera()
       { }

       public static void Initialize()
       {
           myTarget = new Vector3();
           myPosition = new Vector3(0, 0, 0);
           myRotation = new Quaternion(0, 0, 0, 1);
       }

       public  static void Update()
       {
           myWorld = Matrix.Identity;

           myView = Matrix.Invert(Matrix.CreateFromQuaternion(myRotation) *
                                   Matrix.CreateTranslation(myPosition));

           float aspectRatio = myViewport.Width / myViewport.Height;
           myProjection = Matrix.CreatePerspectiveFieldOfView(1, aspectRatio, myViewport.MinDepth, myViewport.MaxDepth);

           myProjection = Matrix.CreatePerspectiveFieldOfView(MathHelper.Pi / 3.0f, (float)myViewport.Width / (float)myViewport.Height, myViewport.MinDepth, myViewport.MaxDepth);
       }
       public static void Rotate(Vector3 axis, float angle)
       {
           axis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
           myRotation = Quaternion.Normalize(Quaternion.CreateFromAxisAngle(axis, angle) * myRotation);

           Update();
       }
       public static void Translate(Vector3 distance)
       {
           myPosition += Vector3.Transform(distance, Matrix.CreateFromQuaternion(myRotation));            
           Update();
       }

       public static void Revolve(Vector3 target, Vector3 axis, float angle)
       {
           Rotate(axis, angle);
           Vector3 revolveAxis = Vector3.Transform(axis, Matrix.CreateFromQuaternion(myRotation));
           Quaternion rotate = Quaternion.CreateFromAxisAngle(revolveAxis, angle);
           myPosition = Vector3.Transform(target - myPosition, Matrix.CreateFromQuaternion(rotate));

           Update();
       }      
   }



SkyBox Shader

//////////////////////////////////////////////////////////////
//                                                            //
//    Writen by C.Humphrey                                    //
//    26/07/2007                                                //
//                                                            //
//                                                            //
//    Shader used to render a cube map to an inverted box        //
//    mesh.                                                    //
//                                                            //
//////////////////////////////////////////////////////////////

Texture surfaceTexture;
samplerCUBE TextureSampler = sampler_state
{
   texture = <surfaceTexture> ;
   magfilter = LINEAR;
   minfilter = LINEAR;
   mipfilter = LINEAR;
   AddressU = Mirror;
   AddressV = Mirror;
};

float4x4 World : World;
float4x4 View : View;
float4x4 Projection : Projection;

float3 EyePosition : CameraPosition;

struct VS_INPUT
{
   float4 Position    : POSITION0;
   float3 Normal : NORMAL0;    
};

struct VS_OUTPUT
{
   float4 Position    : POSITION0;
   float3 ViewDirection : TEXCOORD2;
       
};

float4 CubeMapLookup(float3 CubeTexcoord)
{    
   return texCUBE(TextureSampler, CubeTexcoord);
}

VS_OUTPUT Transform(VS_INPUT Input)
{
   float4x4 WorldViewProjection = mul(mul(World, View), Projection);
   float3 ObjectPosition = mul(Input.Position, World);
   
   VS_OUTPUT Output;
   Output.Position    = mul(Input.Position, WorldViewProjection);    
   
   Output.ViewDirection = EyePosition - ObjectPosition;    
   
   return Output;
}

struct PS_INPUT
{    
   float3 ViewDirection : TEXCOORD2;
};

float4 BasicShader(PS_INPUT Input) : COLOR0
{    
   float3 ViewDirection = normalize(Input.ViewDirection);    
      return CubeMapLookup(-ViewDirection);
}

technique BasicShader
{
   pass P0
   {
       VertexShader = compile vs_2_0 Transform();
       PixelShader  = compile ps_2_0 BasicShader();
   }
}



So the sky box is instantiated like this in the Game1 constructor:

           SkyBox skyBox = new SkyBox(this,"Content/Models/skybox", "Content/Shaders/skybox", "Content/Textures/cubeMap");
           this.Components.Add(skyBox);



The configuration of the camera is a little more detaild. First of all, I want the user to resize the viewport, so I need to wire an event to manage this and update the cameras viewport properties, also, if the user has multiple screens and they drag the game from one window to the next, the camera viewport has to be reset again so this even also needs to be wired. So first off I write my methods to be called by the events.


       /// <summary>
       /// Should the user move the game screen over to another monitor.
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       void DeviceChanged(object sender, EventArgs e)
       {
           Camera.myViewport = graphics.GraphicsDevice.Viewport;
           Camera.myViewport.MinDepth = 1f;
           Camera.myViewport.MaxDepth = 1000f;
       }

       /// <summary>
       /// Should the game window screen size change.
       /// </summary>
       /// <param name="sender"></param>
       /// <param name="e"></param>
       public void Resize(object sender, EventArgs e)
       {
           Camera.myViewport.Width = Window.ClientBounds.Width;
           Camera.myViewport.Height = Window.ClientBounds.Height;
       }



I now wire these events up in the Game1 constructor.

           Window.ClientSizeChanged += new EventHandler(Resize);
           Window.ScreenDeviceNameChanged += new EventHandler(DeviceChanged);



Now I have those bases covered I need to initialize my camera, this is done in the Initialize method of the Game1 class.


           Camera.Initialize();
           Camera.myViewport = graphics.GraphicsDevice.Viewport;
           Camera.myViewport.MinDepth = 1f;
           Camera.myViewport.MaxDepth = 1000f;



Now all I have to do is update my camera, and yes, you guessed it, this is done in the Update method of the Game2 class.


           Camera.Update();


Controls
Mouse rotates camera.
Esc to exit.

Again, any comments, issues, suggestions or fixes, please comment here.

OOPS! I forgot to add the following method to the SkyBox class, basicly without it if you move the game window to another screen your app will crash as you will get memory violations. Sorry. I have not updated the zip so you will have to do this manualy, source from now on will include this method if needed.


       protected override void UnloadGraphicsContent(bool unloadAllContent)
       {
           content.Unload();
           base.UnloadGraphicsContent(unloadAllContent);
       }



GenericExampleSkyBox.zip