Thursday, August 9, 2012

XNA4.0 – 04 – Finally some substance

Today we have tons of content to cover so let's get right to it.

I will give you another link to a wonderful series of XNA tutorials that guide you nicely along a path of mastering XNA: Riemer's Tutorials.

First I moved out the bulk of the Game class from the second tutorial into a separate class called BaseGame. It is still a little bit hacky, but it does abstract away a few of the elements that are glue code.

God, I need to learn C#...

I would love to go over this class but there is not time.

We have a few new variables. first we will store the graphics device, not just the manager, because we refer to it quite often. Then we have an Effect instance, a.k.a. a shader. In XNA everything is rendered using effects. If I am not mistaken it is truly impossible to render without one. But don't worry, this doesn't mean that you automatically need to write a shader to render anything. There is the built-in BasicEffect, which allows for reasonable rendering when you do not wish to write a custom shader. So while a shader is always used, you don't need to write one.

Then we have the vertices vector. Here we'll create a simple "mesh": three triangles. The vertices have a position and a color, as implied by the class name. HFACTOR and TRISIDE are some constants used to fill the vertices vector with equilateral triangles.

Since we are actually rendering something, we also need to set up our view port so we can actually see what is rendered. I talked a little about the world-view-projection matrix in the past. Here we don't need a world matrix because we render all triangles in their final destination, but we need to set up the view and projection matrices, so we create variables for them.

Finally we have a new option to control if the triangles are rendered filled or just wireframe and a variable to control the camera position. Since this tutorial introduces a lot of features, I opted for very simple camera movement, only adjusting the X position of the camera along the axis.

And speaking of positions: I will no longer be using the "correct" coordinate system, like in real life and mathematics. Instead I will using the "wrong" yet pretty much industry standard coordinate system that you find in pretty much all the games and code snippets. This should make incorporating third party resources a lot easier. The old engine also had and extremely annoying bug where switching camera modes made the camera point in the wrong direction until the first mouse movement. I was unable to fix this bug and hopefully such bugs will be avoided using full featured tried & true camera implementations.

But there are two downsides to this. The first one is that I won't be the Messiah who shows the industry that they have a stupid coordinate system. Somebody else needs to rise up to the call, but I'm willing to become an apostle under this new religion. Second, it will take me weeks, probably months to get used to this, so expect bugs where I mix up the Z and Y axis. Even today's simple example was done by checking the coordinate system schema every 5 seconds. I have very deep and intuitive grasp of what I consider a proper coordinate system and am very comfortable with it. Adapting to the new one will be very unpleasant and time consuming. But unlearn away!

Here we see the vertex fill method. Nothing out of the ordinary, just using the constants to add 3 equilateral triangles pointing up and sitting on the "floor" plane. The triangles are sent in a single batch to the GPU each frame.

The keyboard processing method is very similar, but you can see a few functions that are called and inherited from the BaseGame class. UpdateKeyboard should be always called first, and KeyJustPressed/KeyPressed are used for detecting the keyboard state. One addition is the gameTime parameter. Since we are handling things when a key is held down, we need to scale all animations and movement based on the delta time between two frames. We added 'W' as a shortcut to control wireframe and 'A'/'D' to control the camera position on the X axis.

Ah yes, the update method. It maintains the same structure: reading input and updating stuff incrementally. The SetupCamera method is new here, creating the view and projection matrices. I need to dedicate a post once only to the subject of matrices, but here is the short version: we set up the view matrix based on the camera postions, where it is looking at and the camera's up vector. We then set up the projection matrix based on the camera's FOV, the ration between the width and the height of the window and the near and far planes.

And finally, the meat of the tutorial: the draw method! This one has become more complicated. First, for proper rendering we need a depth buffer and depth test. So when we clear the frame, we also clear the depth buffer. Then we set up the device to use a depth buffer. Without it, triangles would be rendered first to last, covering all triangles before and disregarding the intention of using covering surfaces to create the illusion of 3D space. I'm not 100% sure I set up the depth stencil use correctly.

The using the RasterizerState class we fine tune our triangle rendering. We turn wireframe on/off and deactivate back face culling. This is not necessary here, but since we only have one faced triangles, moving the camera behind them would not render anything and might be confusing for a tutorial.

Then we set up the effect constants by sending it the world, view and projection matrices. Since we are not using a world matrix, we send it the identity matrix. The custom shader is written to be flexible and handle all three matrices, but otherwise is very simple, just projecting points and doing color interpolation. For each pass of the effect (which in our case is only one pass anyway), we send the triangle list to the GPU. Then we draw the text overlay, update the frame count using a method from BaseGame and we are done!

I won't be explaining the shader today, but here is the listing if you are curious (no syntax highlighting):

Now let's see the whole thing in action, after a little keyboard rotation:

Pretty good! It even has that promised atialising.

There was one bug though. Yey, my first bug. The engine will compensate for variable frame rate using the elapsed time between frames. With framerates from 1 to a few hundred this worked fine. With really large framerates like the one in the screenshot the animation speed was only correct with v-sync on. Luckily, this was due to my error, an easy fix (20 minutes of googling to understand what I did wrong) and I am not worried about this issue for now.

Here is the full source code: XNATut03.

No comments:

Post a Comment