Getting started with SunBurn – part 2
In my last SunBurn post we set up a skeleton game for working with SunBurn. Unfortunately that left us with an ugly grey box, which is incredibly sad given what SunBurn can do. This time we’re going to actually get some things on the screen and start seeing just why SunBurn is such a useful tool for any game developer thinking of making a 3D game with XNA Game Studio.
First we need to get a few assets. I’m borrowing the tank model from the XNA Creators Club Simple Animation sample as well as a texture from the Studio Evil texture pack. If you’re feeling lazy (and we all know programmers are), I’ve bundled up the files for you here: BasicSunBurn_02_Assets.zip.
Now we need to add a reference to the Content project to support loading in models for SunBurn. Right click on the Content project and select Add Reference. Scroll down and add SynapseGaming-SunBurn-{Version}-Processors where, again, {Version} is whatever version of SunBurn you own.
Next let’s add these to our content project. Add all four files to your game’s content project. Then right click on engine_diff_tex.tga and turret_alt_diff_tex.tga and choose Exclude from Project. Why’d we do this? By adding them to the project, it ensures the files are copied to our project directory, but since tank.fbx has the information to find these files, we don’t need to build them in our project. So at this point your folder should have all four files, but your Content project should just have se_free_road_texture.jpg and tank.fbx.
Last on the content, we want to tweak a couple of properties of the content processors for the content. Right click on se_free_road_texture.jpg and choose Properties. Expand the Content Processor box and change Generate Mipmaps to true and set the Texture Format to DxtCompressed. The mipmaps can help make the texture look good at different sizes and the DxtCompressed will keep the texture size down a good amount at a slight cost of quality.
Then view the properties for tank.fbx. We need to first change the Content Processor entirely from Model – XNA Framework to Model – SunBurn Lighting System. Make sure you do not select Model – SunBurn Deferred Lighting System as we are not using deferred rendering. Once you’ve changed that, expand the Content Processor and change Scale from 1 to .02; the tank model is quite large, so we want to scale that down at build time in order to see it on screen more easily.
That’s it for setting up the content. Now let’s get that tank on screen. Open up your Game1’s LoadContent method and let’s get that tank in the game. All it takes to load a static model is one line of code:
renderManager.SubmitRenderableObject(
Content.Load<Model>("tank"),
Matrix.Identity,
true,
ObjectLifeSpan.Scene);
Pretty basic, right? We load the model in, set a world matrix for the model, tell the render manager whether or not we want it to cast shadows, and then how long the object will exist. Our two choices for ObjectLifeSpan are Scene (meaning it’s always there) or Frame (meaning it’s there for just one frame). If you use Frame, you have to make sure to call SubmitRenderableObject each frame to have the model draw, but that’s how you have to use SunBurn if you want to update the world matrix each frame. For now we’ll stick with a static tank.
We also need to update our view matrix to put the camera somewhere that can actually see the tank. Change your view initialization in LoadContent to this:
view = Matrix.CreateLookAt( new Vector3(10f), Vector3.Zero, Vector3.Up);
Now hit F5 and see your glorious ta… wait a minute.
Why’s the tank black? I thought SunBurn was this amazing lighting engine and yet I see no lights. Well the problem is simple: we didn’t add any lights. We’re going to solve this by adding an ambient light and a directional light. In LoadContent, add this code:
renderManager.LightManager.SubmitDirectionalLight(
new Vector3(.8f),
new Vector3(-1f),
1f,
ShadowType.AllObjects,
1f,
1f,
.1f,
ObjectLifeSpan.Scene);
AmbientLight ambientLight = new AmbientLight
{
DiffuseColor = new Vector3(.3f)
};
renderManager.LightManager.SubmitLight(ambientLight, ObjectLifeSpan.Scene);
You can use the docs or Intellisense to see exactly what all of the parameters are for the directional light, but basically we added a directional light with a .8, .8, .8 diffuse color and a direction of (-1, -1, -1) that applies shadow to all objects and exists as part of the Scene (as opposed to just the Frame).
Then we added an ambient light so that we have some light even in shadows. I like .3 but you can raise or lower that as you want. In fact, you probably should play with some values just to see how they all work in the scene.
Now hit F5 and you should see your tank all nice and lit up. You can even see there is some shadowing going on, but it’s a bit hard to see.
Let’s take a second and make our camera rotate around the tank so we can get a good view. First remove the initialization of your view matrix from LoadContent and then head down to the Draw method and add these lines:
view = Matrix.CreateLookAt(
new Vector3(
(float)Math.Cos(gameTime.TotalGameTime.TotalSeconds) * 10f,
10f,
(float)Math.Sin(gameTime.TotalGameTime.TotalSeconds) * 10f),
Vector3.Zero,
Vector3.Up);
Now run the game again and you’ll get a little bit closer and rotating view of the tank. Now you can see the shadowing a bit better.
That’s basically it for loading in a static model from a file, but what about dynamically generated content? I’m a big fan of creating stuff at runtime, so how can we do that with SunBurn? It’s actually quite easy: there’s an overload of SubmitRenderableObject that takes it a VertexBuffer and IndexBuffer along with a bunch of supporting parameters and uses that to draw the object. Let’s go ahead and add in some ground for the tank.
Start by adding a new method called CreateGroundPlane. I’m going to go ahead and paste in the code here and assume that you know enough about VertexBuffers and IndexBuffers to know what’s going on there.
private void CreateGroundPlane()
{
// sets the width/depth of the ground plane
const float size = 20f;
// sets the amount of texture tiling for the ground plane
const float tiling = 2f;
// create a vertex buffer for our four points
VertexBuffer vertBuffer = new VertexBuffer(
GraphicsDevice,
VertexPositionNormalTexture.SizeInBytes * 4,
BufferUsage.WriteOnly);
vertBuffer.SetData(new[]
{
new VertexPositionNormalTexture(
new Vector3(-size, 0f, -size), Vector3.Up, new Vector2(0f, 0f)),
new VertexPositionNormalTexture(
new Vector3(size, 0f, -size), Vector3.Up, new Vector2(tiling, 0f)),
new VertexPositionNormalTexture(
new Vector3(-size, 0f, size), Vector3.Up, new Vector2(0f, tiling)),
new VertexPositionNormalTexture(
new Vector3(size, 0f, size), Vector3.Up, new Vector2(tiling, tiling)),
});
// create an index buffer
IndexBuffer indexBuffer = new IndexBuffer(
GraphicsDevice,
sizeof(short) * 6,
BufferUsage.WriteOnly,
IndexElementSize.SixteenBits);
indexBuffer.SetData(new short[] { 0, 1, 2, 2, 1, 3 });
// create a LightingEffect and load a texture in for the diffuse map
LightingEffect effect = new LightingEffect(GraphicsDevice)
{
DiffuseMapTexture = Content.Load<Texture2D>("se_free_road_texture")
};
// submit the object to be rendered as a scene object
renderManager.SubmitRenderableObject(
new BoundingSphere(
Vector3.Zero,
new Vector3(size, 0f, size).Length()),
effect,
indexBuffer,
vertBuffer,
new VertexDeclaration(
GraphicsDevice,
VertexPositionNormalTexture.VertexElements),
0,
PrimitiveType.TriangleList,
2,
0,
4,
0,
VertexPositionNormalTexture.SizeInBytes,
Matrix.Identity,
null,
false,
ObjectLifeSpan.Scene);
}
There we go. Looks like a lot, but it’s not so bad if you go line by line. Doesn’t help that the last method has a ton of parameters and, for the sake of formatting this post, I broke each one into its own line (or two). But basically we create and fill our buffers, create a LightingEffect (which is part of SunBurn), and then submit the whole mess to the render manager along with all the information it needs to draw it such as a BoundingSphere that encapsulates the object, index count, vertex count, and all the other goodies one needs when drawing primitives.
Now make sure your LoadContent method is calling CreateGroundPlane and hit F5 to see your tank sitting on some ground.
Notice the shadow is cast from the tank onto the ground as well as itself. This is where SunBurn really starts being awesome. We haven’t had to even think about shadows beyond some boolean values and they’re just there (and look pretty nice considering we’re using largely default values for quality). The engine supports shadowing not only of other objects, but self shadowing. It’s very nice quality considering how little code had to be written to get these results.
That’s it for this post. This is quite a bit for a single post, but I figured it was better to cover too much than not enough. Next time we’ll get into using the ObjectLifeSpan.Frame value for rotating the whole tank and then move on to rotating individual pieces separately. Good fun ahead.
Download: BasicSunBurn_02.zip
Possibly Related Posts
(Automatically Generated)Getting started with SunBurn – part 3
Split screen in SunBurn
Extending SunBurn with physics
Getting started with SunBurn – part 1
Getting started with SunBurn – part 1





FYI: This part works with the free Sunburn Framework after making the content submit changes as in Step 3. There is heavy aliasing in windowed view. Can this be smoothed out?
FYI: This part works with the free Sunburn Framework after making the content submit changes as in Step 3. There is heavy aliasing in windowed view. Can this be smoothed out?