Running on Empty

The few things I know, I like to share.

XNA Framework GameEngine Development. (Part 16, Skydome)

Introduction

Welcome to Part16 of the XNA Framework GameEngine Development series.  In this article I will get back on track toward my original plan.  I am sorry if any readers felt misled by my deviation from the series plan.  From now on I will treat Physics updates and WorldBuilder updates as their own sub-series.

Now that we have created a reasonably decent rendering wrapper, we should start creating some standard objects that will be used most often in games.  I normally start my engine development off with a good skydome, mostly because the math is simple and well known (more on this silly statement soon).  Essentially, we are going to draw half a sphere on the screen, but with criteria.

  • Would prefer to use TriangleStrips over TriangleLists.
  • Would like to have control over the Skydome vertex/index complexity.
  • Would like to have the skydome centered on the camera.

part16.jpg

Release

Here is the source code, as a side note thanks to feedback from readers I implemented the JigLibX physics library into this latest release.  This library has a cleaner feel than BulletX and will be the physics engine for this game engine… well until next month when I change my mind again.

Where to begin?

At this point, I grabbed my old geometry book and found… well nothing helpful to me.  I wish I was able to say, “yup I used my highschool geometry books and wrote a skydome tutorial”, but I did not.  At this point, I did what any developer would do, I googled until I found something helpful. 

After reading a few dozen articles I realized, there are literally thousands of people making skydomes, skyplanes, skyboxes, and skyspheres.  As a bi-product, there are thousands of ways to create a skydome and by no means am I saying the way I chose is best.  In fact, while writing this article, I looked up at least 3 other ways to create a skydome and will probably write up tutorials on those subjects as well.

Skydome

A skydome is a simple half sphere that will allow us to surround the camera with a beautifully shaded sky.

First, we create a scene object that is loadable.  This skydome is constructed based on four parameters, the radius of the circle, the height (this implies we can make the sphere more elliptical in the Y direction), the number of rings (latitude), and the number of segments (longitude).

using System;
using System.Collections.Generic;
using System.Text;
using RoeEngine2.Interfaces;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using RoeEngine2.Managers;

namespace RoeEngine2.SceneObject.StandardObjects
{
    public class Skydome : RoeSceneObject, IRoeLoadable
    {
        int _numberOfVertices;
        int _numberOfIndices;
        float _radius;
        float _height;
        int _rings;
        int _segments;
        VertexBuffer _vb;
        IndexBuffer _ib;

        private bool _attachToCamera = true;
        public bool AttachToCamera
        {
            get { return _attachToCamera; }
            set { _attachToCamera = value; }
        }

        public Skydome(float radius, float height, float rings, float segments)
        {
            _radius = (float)Math.Max(radius, 1.0e-05);
            _height = (float)Math.Max(height, 1.0e-05);

            _rings = (int)Math.Max(rings, 1) + 1;
            _segments = (int)Math.Max(segments, 4) + 1;

            _numberOfVertices = _rings * (_segments + 1);
            _numberOfIndices = _segments * _rings * 2;
        }  

        public virtual void LoadContent()
        {
            CreateVB();
            CreateIB();
            ReadyToRender = true;
        }

        public virtual void UnloadContent()
        {
           
        }

        BasicEffect effect = new BasicEffect(EngineManager.Device, null);

        public override void Draw(GameTime gameTime)
        {
            if (_attachToCamera)
            {
                Position = new Vector3(CameraManager.ActiveCamera.Position.X, Position.Y, CameraManager.ActiveCamera.Position.Z);
            }

            effect.EnableDefaultLighting();
            effect.PreferPerPixelLighting = true;

            effect.World = World;
            effect.View = CameraManager.ActiveCamera.View;
            effect.Projection = CameraManager.ActiveCamera.Projection;

            effect.DiffuseColor = new Vector3(255, 0, 0);
            effect.DirectionalLight0.Enabled = true;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                using (VertexDeclaration declaration = new VertexDeclaration(EngineManager.Device, VertexPositionTexture.VertexElements))
                {
                    EngineManager.Device.VertexDeclaration = declaration;
                    EngineManager.Device.Vertices[0].SetSource(_vb, 0, VertexPositionTexture.SizeInBytes);
                    EngineManager.Device.Indices = _ib;
                    EngineManager.Device.DrawIndexedPrimitives(PrimitiveType.TriangleStrip, 0, 0, _numberOfVertices, 0, _numberOfIndices - 2);
                }
                pass.End();
            }
            effect.End();
        }

        public override string ToString()
        {
            return "Skydome";
        }

        private void CreateIB()
        {
            _ib = new IndexBuffer(
                EngineManager.Device,
                typeof(int),
                _numberOfIndices,
                BufferUsage.WriteOnly);

            int[] indices = new int[_numberOfIndices];

            bool leftToRight = false;
            int n = 0;
            for (int y = -0; y < _segments; y++)
            {
                if (leftToRight == true)
                {
                    for (int x = 0; x = 0; x--)
                    {
                        indices&#91;n++&#93; = y * _rings + x;
                        indices&#91;n++&#93; = (y + 1) * _rings + x;
                    }
                }
            }
            _ib.SetData(indices);
        }

        private void CreateVB()
        {

            _vb = new VertexBuffer(
                EngineManager.Device,
                typeof(VertexPositionTexture),
                _numberOfVertices,
                BufferUsage.WriteOnly);

            VertexPositionTexture&#91;&#93; vertices = new VertexPositionTexture&#91;_numberOfVertices&#93;;
            int n = 0;
            for (int y = 0; y = 0; x--)
                {
                    float phi = (float)x / _rings * MathHelper.ToRadians(90.0f);

                    VertexPositionTexture vert = new VertexPositionTexture();
                    vert.Position.X = (float)(Math.Sin(phi) * Math.Cos(theta) * _radius);
                    vert.Position.Z = (float)(Math.Sin(phi) * Math.Sin(theta) * _radius);
                    vert.Position.Y = (float)(Math.Cos(phi) * _height);
                    vertices&#91;n++&#93; = vert;
                }
            }
            _vb.SetData(vertices);
        }
    }
}&#91;/sourcecode&#93;
I would like to point out a few items that might interest readers.  I am using a BasicEffect at this point, this will be replaced by a more sophisticated shader in the next article.  Also, I am using a user defined vertex and index buffer rather than a model.  Hopefully, readers will realize the flexibility of not always using a model from the content pipeline for rendering.<strong>Adding a Skydome to the SceneGraph</strong>

Lines of code like the following are the reason why we write game engines.  We want to make game developers lives easier so they can focus on gameplay.  Personally, this is the reason why I write this series, to make it possible for game developers to worry less about graphics and more about gameplay.

            Skydome dome = new Skydome(20, 20, 32, 32);
            SceneGraphManager.AddObject(dome);

Conclusion

In this article, I introduced the first user defined SceneObject in the engine.  A simple and useful Skydome will be the backdrop of any outdoor game.  In the next article, I will show how to paint this dome with a true to life lightscattering shader.

Please feel free to leave comments or suggestions, I thoroughly enjoy the feedback.

March 5, 2008 - Posted by | C#, XBOX360, XNA

15 Comments »

  1. The skydome seems to include a cone inside. Care to elaborate on why that’s there and what it’s use will be?

    Comment by JeBuS | March 6, 2008 | Reply

  2. Glad you asked, that is part of using a trianglestrip instead of a triangle list. The skydome is made up of segments and inside those rings.

    That means in order for us to get from the top to the bottom of the sphere to make the next segment we have to draw a NULL polygon, essentially a line.

    Comment by roecode | March 6, 2008 | Reply

  3. Good job on this very nice series, hope to see it going 🙂

    I’m having problems unzipping the current source (Part16.zip), the earlier ones went smoothly. Tried a lot of different mirrors too but unable to unzip it anyway. Is it corrupt or do I need another unpacker (using winrar 3.60 and total commander 7.02a)? If not I would appreciate it alot if you could send the file to my email.

    Regards and thanks again.

    Comment by Zenox | March 6, 2008 | Reply

  4. I’m not following what you mean. Will you be explaining in a future part how the dome gets used?

    Comment by JeBuS | March 6, 2008 | Reply

  5. Yes, I am currently writing an example that will use the dome to do daylight scattering using the Hoffman algorithm.

    Perhaps your question is about the difference between TriangleLists and TriangleStrips?

    Comment by roecode | March 7, 2008 | Reply

  6. Not able to unzip Part16.zip at all, WinZip complains about corruption. Tried downloading using Firefox, Internet Explorer and wget using different mirrors and computers without success. Ran Diskinternals Zip Repair which fixed most of the archive, but alot of the files seemed missing, like the solution file for instance. Is there a possibility that you could download this part from SourceForge too verify if it’s complete and working?

    Comment by Zenox | March 7, 2008 | Reply

  7. Zenox,
    I will send you the file via email. Very sorry about this issue. I will check out the sourceforge file today.

    Comment by roecode | March 7, 2008 | Reply

  8. This is a really good tutorial serie. I bought the Nitschke book and your tuts seems to implement one of the missing tools of this book.

    But, I think that we have some performance issues here. On my laptop (pretty old, only a 9600 pro, but he can run CoD 4 at 60 fps…), I cannot reach 20 fps when all the balls are rendered in the game (SVN version). You should try to implement a LineHelper class to draw all the lines in one pass, I think that you could easily multiply your fps doing this. I’ll try tomorrow to test this statement, if I find enough time…

    There’s an explanation on this kind of class in the Nitschke book.

    Thank you very much for these impressive tutorials !

    Comment by Dracul86 | March 8, 2008 | Reply

  9. Without a doubt there are improvements to be made to the engine. I plan to do some optimization tutorials, I would like to see your implementation.

    Comment by roecode | March 8, 2008 | Reply

  10. So, I’ve implemented a (really) simplified version of the Nitschke Mesh manager. In this case, it is only made for rendering meshes through a basicEffect. I modified the RoeSceneObject.cs that it add itself during the initialise method, and I deleted his render method.

    I added a “meshRenderer” in the base RoeEngine2, wich is created during the engine initialisation , and drawn every frame.

    I also implemented a “MeshToRender” class to help me during the development process. Basically, a MeshToRender is just a wrapper of the index buffer, vertex buffer, index of the first vertice etc.

    This is a really easy implementation. You should modify it to be a little more flexible, and more elegant.

    I tried to make a SVN patch, but I cannot manage to make a patch with new files and folders in it. So I rared it, and posted it here : http://dracul86.free.fr/XNA/Changes.rar .

    Feel free to ask questions on my mail adress, or on my msn : dracul86@hotmail.fr (As you probably already stated, I’m French…)

    Ah, the last but not the least, with these modifications, I went from 12 fps to 30+ (Centrino 1.86 Ghz, Radeon 9600 Pro).

    Comment by Dracul86 | March 9, 2008 | Reply

  11. Very nice Dracul86, I can see where batching like effects together in a major rendering loop will improve speed. I had planned on doing something similar to this in a future article. If you do not mind, I will take some of your ideas and put my own tweeks to fit my style better.

    I admit I am more focused on ease of use and reader understanding in this series. I do plan on going back in a future article series and optimizing a lot.

    Keep up the suggestions though, I very much appreciate knowledgable readers like yourself.

    Comment by roecode | March 9, 2008 | Reply

  12. Cannot wait for that daylight scattering example!

    Comment by Pete | March 10, 2008 | Reply

  13. Still working on the article, have the code written though.

    Comment by roecode | March 10, 2008 | Reply

  14. Hi, first off great series! Keep it up!

    Unfortunately there seems to be a few problems here. First off there is an obvious error in the source listing on this page – the for loop on line 130. Second I do not see how you are creating the indexbuffer properly…

    Dropping your skydome code (from d/load) into some code of mine results in a broken mesh. The indexbuffer seems to be incorrectly defined… The “cone” section is not made up of degenerate triangles and the dome seems to be defined in the wrong order and is thus culled with the default culling.

    A simple fix is to add some more degenerate triangles to the list and swap the order the triangles are defined in. Something like:

    for (int x = rings – 1; x >= 0; x–)
    {

    indices[n++] = (y + 1) * rings + x;
    indices[n++] = y * rings + x;

    // add some degenerate triangles
    if (x == 0)
    {
    indices[n++] = (y) * rings;
    indices[n++] = (y) * rings;
    }

    }

    Though when running your example code with your framework the mesh seems to be displaying correctly. I have not had the time yet to look into why…

    Comment by Leigh | March 19, 2008 | Reply

  15. Leigh, thank you for your comments.

    I will obviously spend some time looking into your suggestions.

    Comment by roecode | March 20, 2008 | Reply


Leave a reply to roecode Cancel reply