Sharing my personal advancement with Xen ;)

Aug 1, 2009 at 2:04 AM

I've been around for quite a long time reading stuff and asking a couple of questions and since I started really digging into Xen to develop my hobby game, I thought I could give a hand and share a few code to help some other people around.

It's nothing really big but for my busy brain, it still took me a while to get it done right :p

So here it goes. My goal here was to create a starfield composed of a randomized set of stars and a nebula stored in a Cubemap.

All the required code is contained in two files: a Starfield class and a Nebula shader.

We'll start by the Nebula shader that only takes care of rendering a Cubemap in a Sphere.

uniform extern float4x4 ViewMatrix : VIEW;
uniform extern float4x4 ProjectionMatrix : PROJECTION;

void NebulaeVertexShader( float3 pos : POSITION0,
                         out float4 NebulaePos : POSITION0,
                         out float3 NebulaeCoord : 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)
    NebulaePos = mul(float4(rotatedPosition, 1), ProjectionMatrix).xyww;    

    NebulaeCoord = pos;
};
uniform extern texture NebulaeTexture;
sampler NebulaeS = sampler_state
{
    Texture = ;
    MinFilter = LINEAR;
    MagFilter = LINEAR;
    MipFilter = LINEAR;
    AddressU = CLAMP;
    AddressV = CLAMP;
};

float4 NebulaePixelShader( float3 NebulaeCoord : TEXCOORD0 ) : COLOR
{
    // grab the pixel color value from the skybox cube map
    return texCUBE(NebulaeS, NebulaeCoord);
};

technique NebulaeTechnique
{
    pass P0
    {
        VertexShader = compile vs_2_0 NebulaeVertexShader();
        PixelShader = compile ps_2_0 NebulaePixelShader();
    }
}

 

The great thing with Xen is that you don't have to do anything in the code to make that work except creating a Sphere and loading the right resource as Xen takes care of putting the right values in the Vertex and Pixel methods ;)

And now the Starfield class.

First, I setup some using statements that will become useful:

using System;
using Xen;
using Xen.Camera;
using Xen.Graphics;
using Xen.Graphics.State;
using Xen.Ex.Geometry;
using Xen.Ex.Material;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
// This last one is where the Xen Shader tool created and compiled the above shader.
using iScream.CC.Scene.Shaders.Nebulae;

 

The Starfield class implements 2 interfaces provided by Xen: IDraw for rendering calls support and IContenOwner for dynamic content loading support.

public class Starfield : IDraw, IContentOwner

 

First, I add a constant to define the number of stars to create and render.

        /// <summary>
        /// Defines the maximum number of stars used in the Starfield generation.
        /// </summary>
        private const int NUMBER_OF_STARS = 10000;

 

I then add a set of private members to store the Geometry used by the stars a generic collection of Vertices kindly provided by Xen, a Sphere for the Cubemap rendering and the shaders: MaterialShader from the Xen.Ex library for stars and our newly created Nebula shader.

        private IVertices p_SmallStarVertices;
        private MaterialShader p_SmallStarsMaterial;
        private Sphere p_NebulaSphere;
        private NebulaeTechnique p_NebulaShader;

 

Now, all the initialization code.

        public Starfield(ContentRegister contentRegister)
        {
            // The ContentRegister class is the class in Xen that allows you to simply get your class LoadContent and UnloadContent called whenever it needs to be done.
            if (contentRegister == null)
                throw new ArgumentNullException();

            // The starfield material shader.
            // We simply create a Xen MaterialShader instance...
            p_SmallStarsMaterial = new MaterialShader();
            // ... then create a new MaterialLightCollection...
            p_SmallStarsMaterial.Lights = new MaterialLightCollection();
            // ... which we setup the way we want.
            p_SmallStarsMaterial.Lights.AmbientLightColour = Color.White.ToVector3();
            p_SmallStarsMaterial.TextureMapSampler = TextureSamplerState.AnisotropicHighFiltering;
            // This one is important as it will allow Xen to pass the Color you passed in the MaterialShader to the Vertex method.
            p_SmallStarsMaterial.UseVertexColour = true;
            
            // We generate stars (I'll come to this method later)
            this.GenerateSmallStars();

            // The Nebula cubemap shader.
            // We create a new instance of our Nebula class generated automatically by the Xen shader tool in Visual Studio
            p_NebulaShader = new NebulaeTechnique();
            
            // The Nebula sphere we will use to render the cubemap. The Sphere is provided by Xen.Ex library too.
            p_NebulaSphere = new Sphere(new Vector3(5f), 32);

            // And finally we tell Xen to add this class instance to the list of instances to get their LoadContent method call.
            contentRegister.Add(this);
        }

 

Until here, we haven't done anything really tedious. But now, we'll get into some maths.

Remember, I wanted to create a set of stars directly using Vertexes which means that I need to get all of them positioned in a sphere and that implies some coordinates that I need to calculate from the center of a sphere and uniformously distribute.

In order to achieve this, all you really need to do is randomize the two angles that compose any point distributed on a sphere (you could see these angles as the Latitude and Longitude coordinates on our Earth somehow).

        private void GenerateSmallStars()
        {
            // We'll need some randomisation so I create a simple Random class instance from the .Net framework.
            Random rand = new Random();
           // I also create an array of the Xna VertexPositionColor class that will give me the ability to define a Vertex based on its position in 3d space and the associated color.
            VertexPositionColor[] verts = new VertexPositionColor[NUMBER_OF_STARS];
            
           // Now I loop through the number of stars I need to generate.
            for (int i = 0; i < NUMBER_OF_STARS; i++)
            {
                // x, y, z for the final coordinates of the vertex.
                // w and t are used for the angles.
                double x, y, z, w, t;

                // And now the Maths. I won't get into these for now but believe me: it makes the job ;)
                z = 2.0 * rand.NextDouble() - 1.0;
                t = 2.0 * MathHelper.Pi * rand.NextDouble();
                w = Math.Sqrt(1 - z * z);
                x = w * Math.Cos(t);
                y = w * Math.Sin(t);

                // I also define a random float to create the Color I'll use for the Vertex. I only need one as I want to get grayscaled stars.
                float color = (float)rand.NextDouble();

                // And finally, I add to my array of Vertexes a new one with the position and given color.
                // You probably noticed that I multiply all x, y and z by 100000; that's because I didn't get another way (yet) to manage my scene sorting (I get all Vertexes rendered in front of all other meshes otherwise).
                verts[i] = new VertexPositionColor(new Vector3((float)x * 100000, (float)y * 100000, (float)z * 100000), new Color(color, color, color, 1.0f));
            }

            // And the magic of Xen in action: by using the simple Vertices<T> generic class, I just create my own set of Vertices from my array.
            p_SmallStarVertices = new Vertices<VertexPositionColor>(verts);
        }

 

I'll go now to the easiest part of the code which needs no explanations if you know about Xna:

        public void LoadContent(ContentRegister content, DrawState state, Microsoft.Xna.Framework.Content.ContentManager manager)
        {
            p_NebulaShader.NebulaeTexture = manager.Load<TextureCube>("nebula");
        }

        public void UnloadContent(ContentRegister content, DrawState state)
        {
        }

 

And finally, the rendering code:

        public void Draw(DrawState state)
        {
            // Retrieves the camera position and forces the rendering to occur whatever the real game position is.
            Vector3 cameraPosition;
            state.Camera.GetCameraPosition(out cameraPosition);
            state.PushWorldTranslate(ref cameraPosition);

            // Using Xen DrawState instance, I store the current RenderState so that I can reset back to it after my nebula rendering.
            state.PushRenderState();
            // I set the Cullmode to None and DepthWriteEnabled to false so that it doesn't occlude anything on my scene and I don't care about the Sphere normals.
            state.RenderState.DepthColourCull.CullMode = CullMode.None;
            state.RenderState.DepthColourCull.DepthWriteEnabled = false;

            // We then simply bind the Cubemap shader to the current state and call the Sphere Draw method.
            p_NebulaShader.Bind(state);
            p_NebulaSphere.Draw(state);

            // I reset the RenderState.
            state.PopRenderState();

            // I also have to do some RenderState management with my Starfield rendering so that it blends correctly with the nebula.
            state.PushRenderState();
            state.RenderState.AlphaBlend.SetToAdditiveBlending();

            // And once again, the magic of Xen in action: simply bind the MaterialShader and call the Vertices Draw method. I don't need any indices and I simple set it to use PointList.
            p_SmallStarsMaterial.Bind(state);
            p_SmallStarVertices.Draw(state, null, PrimitiveType.PointList);

            state.PopRenderState();
            
            state.PopWorldMatrix();
        }

        // I don't need to cull anything here so I always return true
        public bool CullTest(ICuller culler)
        {
            return true;
        }

And you get the end result something similar to this:

 

Coordinator
Aug 1, 2009 at 2:50 AM

That's fantastic :-)

You've made my day. It's always great to know when someone finds my code is helping them out :-)

Aug 1, 2009 at 9:47 PM

Hey StatusUnknown,

You're welcome. It's my pleasure to give back a little since you are giving so much to the Xna community ;)

Aug 24, 2014 at 3:38 AM
Any chance you could provide the texture you used so i can see what i should replicate to get this effect?