October 6, 2009

Module : Introduction to 3D Graphics – Part 3

Posted in Development Report - 3D Graphics Renderer at 19:11 by markcampbellprogrammer

Post Christmas

The first thing I decided to do was to rename the project and re – factor the code into a more logic order. Cleaning up the code over the Christmas was a good place to restart as I started to notice small bugs and inconsistencies which lead to big problems first time around. The biggest problem I had originally was with my matrix class and was due to human error  and found that copying and pasting code can indeed be a very dangerous technique. This I learned the hard way.

The fact that my original hand in wasn’t much of a demo was also a concern and I decided that I wanted my renderer to be more of a demonstration of what I had done and changes made through the rendering pipeline. I also wanted to include a frame per second counter to test the efficiency of my code and spot any bottle-necks that I could optimise.

For the time being though I decided that fixing up the code and naming conventions such as local member variables starting with a _ would help in keeping code tidy. I renamed the namespaces and started to plan on what else I could improve on with the time I had left.

The main things to get into my demo was Ambient and directional lighting, an improved working camera, back-face culling working properly and to make my code into a working demo.

From Christmas onwards

Due to time needing to spend on other modules, I couldn’t spend all my time working on my renderer and implement every single feature. Below is a list of changes made and how long they took –

  • Tidy Code – 1 hour
  • Change Matrix class – 2 hours (around 12 debugging before initial hand in)
  • Change Camera class – 2 hours
  • Fix back face culling – 4 hours
  • Add a working polygon sort method (3 hours)
  • Improve pipeline – 3 hours
  • Implement Ambient lighting – 6 hours
  • Implement Directional lighting – 3 hours (Longer in correcting Polygon class and back – face culling)
  • Set up an FPS counter via tutorial on pdf (1 hour)
  • Display text and numbers on screen via MSDN example (2 hours)
  • Implement case based drawing based on time (2 hours)

 Notable Code Changes / Addition

In the camera class I added a GetCameraPos() that returned a vector. This was then used in the MyApp render call in the calculation of back faces. The object pointer calls the back faces method and passes the vector in. This was an important addition as I create 2 separate cameras in my renderer and rotate them around the object models. This meant that originally the back facing was being calculated wrong and only half of the model was being drawn. This took some time to get my head around as I didn’t understand why the back face culling wasn’t working. I added the std::sort call to my Object sort() method after working through the online tutorial at MSDN as recommended by the pdf. Just above that method in the Object.cpp I added the bool UDGreater method that checks if one Polygons._zIndex is greater than the other and sorts them. The parameter _numPolys – 1 is there to go to the last element -1 which I think would be the original one. This required me to add an overloading operator to the Polygon class and I also had to use my namespace in the call as I think the compiler thought polygon was of a type used in windows. I also added a Vector to represent the normal’s for lighting computations to this class.

A huge difference making was in my calculation of blackfaces. When working out the _polys[i].normal vector by getting the cross vector of 2 vectors created by subtracting the indices in the model data, I realised I was using the original verts before they were transformed (which later on gave me weird lighting effects). This was just a small change but it contributed in a huge way as it gave me correct back face culling.

The biggest challenge I next faced was getting lighting into my application. Although the tutorials given are relatively clear now, I didn’t understand how to call and manipulate the Gdiplus color fields. It took me a long time to get my head around the coding of implementing lighting as the maths behind it I thought was relatively straight forward. Once I understood how to use the colour fields I managed to implement ambient lighting (after remembering to store the color back in the polygon). Directional lighting took a little longer to implement as the problem described earlier with calculating blackfaces was giving me some strange lighting results. The only difference with the directional lighting calculation was adding the calculation of the dot product value taken from each poly’s normal with the specified vector for representing where the light was placed.  I also added a TurnOffLighting() method that simply looped through each polygon in an object and set the colour to black.

I added an FPS counter and was hugely surprised with the good solid frame rate that my renderer produced. The only bottleneck seemed to come from calling the drawWireFrame() in the rasterizer.cpp. The frame rate takes a massive hit compared to all other calculations even when drawing lighting as well. I also included a clamping method to clamp my ARG lighting values that I can call instead of having to keep doing it in each lighting method.

One of the last things I have done was to add a series of “if” statements in the render method that turns on and off each individual feature giving my demo a more “demo” like feel. The last “if – else” simply paints the screen black to give the impression the rendering has finished.

I am pleased my renderer now resembles a demo and would like to continue to improve it. I attempted to add my own rasterizer and implement Gouraud shading. I had previously tried to implement both of these but will need more time to get them working effectively.

June 17, 2009

Module : Introduction to 3D Graphics – Part 2

Posted in Development Report - 3D Graphics Renderer at 16:49 by markcampbellprogrammer

The Camera

The next stage required me to implement a virtual camera into my project so that I could view objects in my renderd scenes. For the time being however, I was just concerned with getting my camera’s methods and data fields implemented. Just to point out, these tasks were being assigned on a weekly basis meaning that there was little time to really take in what was being learned making this project took up a lot of time outside of class time. The camera implementation was one of the most difficult concepts for me to grasp and implement correctly. I fell behind during this stage as I tried various ways of trying to get a working camera but learned some important lessons on how to approach writing code and methodologies used to make coding easier.

Usually I would just come up with a bit of pseudo code on paper and start whacking in all sorts of variables and parameters without really thinking about how code communicates with each other. What I started to do at this point was to use a system that would draw out how classes and object instances would communicate to each other (what would later be taught as case statements and UML diagrams). Although people may look at it as a way of wasting time by writing out code on paper, I find it helpful to be able to see how objects and methods should link together on paper. I really like being able to carry on thinking about how to solve a problem whilst being able to take a break away from starring at a computer screen and now use this approach whenever I’m writing code even if its only a small amount. Burn out is a killer when trying to meet deadlines and bang code out in the shortest amount of time and most of the time I end up re – writting naff code and not using the hundreds of floats I declared at the start of the night.  

So for writing the camera class I had to think about what sort of functionality and data I would be working with and ways of implementing them. I had the tools already implemented as I had full Matrix and Vector functionality from the classes I had written and tested in the previous 2 weeks and so it was just a case now of getting the camera to make use of them. I started with the header file and set up a default constructor and destructor and then thought about the matrices I would need. I needed matrices to represent the screen, perspective and camera transforms which would be used in the local methods for creating each one.  I would also need rotation methods for the X, Y, Z axis methods and later added a GetCamPos method which returned a Vector representing the position of the camera.

Camera transforms, rotations and references

When setting up a virtual camera there are a various important transformations that are required to correctly render images on screen. These transformations are based around moving from different co – ordinate spaces each being a different representation of my vertices data. The main four spaces I made use of were Modelling, World, View and Screen space and considered these when writing my renderer and constructing its pipeline. Modelling space was simply the co – ordinate system used local to the model and relative to the centre of the model i.e. the X, Y and Z at the centre of the model is 0, 0, 0. World space is responsible for translations scaling  and rotations and allows different models with the same Model space co – ordinates to be set up in a larger space. View space is what I was really interested in at this point as it represents where my camera will be and how models in world space will be relative to it. Finally Screen space is simply pixel space on the screen and the transformation from 3D to 2D also known as projection. These four spaces are controlled by 3 matrices the World, View and Projection matrix. These are the rights of passage from space to space and are responsible for controlling the vertices data for the models.  

So the first method I set up was to build my perspective transform which contained a matrix that took a float value and measured a field of view based on what was passed into it. This value was then passed along each axis to correctly set up the camera’s view in relation to the objects on screen. I then created a build screen transform method that simply created a screen based on an X and Y value passed in. The last transform method I needed was to build a camera transformwhich created me a view matrix. This was one of the hardest things for me to understand an get working correctly as I kept messing up the translation of the camera position and the order in which rotations were multiplied. It was also at this stage I realised I had a mistake in my matrix class that was responsible for screwing up my entire renders. In my Matrix multiplication method, I had copy and pasted lines of code and accidently changed a “x” to a “+” giving me some weird results and so learned another important lesson to avoid copy and paste when coding late at night! 

The parameters for the rotation methods were simple a float amount and a matrix reference. The float amount was simply the value passed into the matrix column to rotate by. The matrix reference simply stores the results that are then used later. Another new technique I learned at this point to was how to make use of references when referring to values or data types.

A reference provides another name for a variable. Whatever is done to the reference is done to the variable it refers to. The best description I was given was from a fellow class mate who suggested thinking about references as a nickname for a variable i.e. another name that a variable goes by. So in my parameters to my methods, I pass a reference to a matrix which I then define and save the working calculations in. Make sense? check out the code below -

void Camera ::RotateX(float rotX, Matrix & resultMat)

{

          // Create Matrix for X rotation a.k.a -roll

          resultMat = Matrix(  1,        0,          0,         0

                                      0, cos(rotX), -sin(rotX),  0

                                      0, sin(rotX), cos(rotX),   0

                                      0,        0,          0,        1 );

}

 

The variable reference resultMat then has the values saved into it and is recognised as a matrix. This makes things very easy and convenient when saving data and variables as well as not needing to copy large data types. References can be used in a variety of ways and have many advantages when deciding how to construct arguments. I had used References before but not in a way in which they were passed by parameter or as arguments.  This gave me a nice introduction to using them and taught me an important lesson for passing data chunks.

June 5, 2009

Module : Introduction to 3D Graphics – Part 1

Posted in Development Report - 3D Graphics Renderer at 22:31 by markcampbellprogrammer

This module was introduced in the first semester of the second year during my university course. The purpose of it was to learn about the fundamentals of 3D graphics at a foundational level and gain confidence using 3D APIs. The main focus of study was aimed at the practical and theoretical understanding of real time rendering and the role of the graphics pipeline.

Although I had been introduced to C++ in the first semester, this was the first time I was expected to use full object orientation using inheritance and understand how important its power is in complex programming. As well as the programming element, this module gave me an understanding of how to represent 3D rendering using mathematical techniques and equations. A lot of this module focused on understanding the maths first, then implementing what was learned week by week into the final project. 

The Project  

Each weekly task required me to learn the mathematical theory then implement that into a small program. As the semester progressed, I was then required to combine each weeks work into one big project being a 3D graphics renderer. The focus was on implementing a range of techniques and presenting them as a demo showing technical ability and personal style similar to that of the demo scene for rendering in the early 1990′s i.e. the pre –  hardware accelerated graphics era.

This was without a doubt going to be the most difficult project of my semester due to the volume of work and understanding of the theory behind it. I was excited and nervous at the same time about how the renderer would turn out as I felt that my understanding of programming at the time was not of a good enough standard and would require a lot of additional private study gain a better understanding. Although the programming and mathematical side of this module was important, understanding and getting a full grasp of how the rendering pipeline works and considering what order function calls and transformations are done in was the main objective. The rendering pipeline is ultimately the factory part of the program. It is feed all this data and then is forced to deal with it in constructive and logical way to produce the desired results. There are various considerations which I will discuss later that can affect the pipeline and make rendering slow and inefficient.

During the first few weeks the main focus was on getting a vector and matrix class written to express mathematical calculations used to describe transformations. Vectors and matrices have a wide range of uses in 3D rendering and although in graphics based programs such as OpenGL and DirectXyou can simply create a variable using the functionality within the libraries, we instead had to write our own including simpler mathematical methods such as addition, subtraction, multiplication and divide. However to make full use of our own written classes I also wrote methods such as dot and cross product for the Vector class, and Matrix based transforms such as transpose, transform and axis based rotations.

At this point everything seemed a little murky as the only way of testing what I had wrote was to write a console out method to test what I had done. From calling this method in main I was able to check manual if my methods was working correctly. For the time being I simply hard-coded 2 vectors and 2 matrices and worked out on paper if the answers i had matched that of my program. To be honest I didn’t do enough testing at this point as I should have as there was a problem with my matrix multiplication function which delayed progress later on which I should really have spotted at this point. Below are a code snippets from the Vector and Matrix classes -

Cross product from my Vector class

Vector Vector::CrossProduct(Vector v)

{

         float crossP_x = ((_y * v._z) – (_z * v._y));

         float crossP_y = ((_z * v._x) – (_x * v._z));

         float crossP_z = ((_x * v._y) – (_y * v._x));

         float crossP_w = ((_w * v._w) – (_w * v._w));

        // Returns new vector

        return Vector(crossP_x, crossP_y, crossP_z, crossP_y);

}

 Rotate X method from my matrix class

void Martrix :: RotateX(Matrix &resultMat, const float rotation)

{

          resultMat.m[1] [1] = cos(rotation);

          resultMat.m[2] [1] = -sin(rotation);

          resultMat.m[1] [2] = sin(rotation);

          resultMat.m[2] [2] = cos(rotation);

}

Constructors, Destructors, Static and Const

Using classes and header files in C++ was new to me and I had to quickly understand the difference between using them in C++ compared to C# which I had used in the first year. Using constructors and destructors was something I understood to a degree but didn’t understand the full usability of. Making use of a constructor is useful because multiple versions for the same class can be defined with different parameters to distinguish them. This was something I found useful by using assignment and copy constructors when creating instances of objects. Using parameterised constructors means that I can pass specific parameters to an objects base constructor when creating specific objects. Getting used to how these differ and each work individually  gave me more power and freedom when creating objects and gave me a nice introduction to object orientation.

Destructors are simply the matching partner to the constructor and just de-allocated memory when a class object passes out of scope or is explicitly deleted. It is always important to include these whenever a constructor is used and usually is where pointer variables are freed. Using the key word static simply means when declaring varaibles as being static, that varaible’s value is shared between each instance of each class i.e. it is passed down through different blocks of code. This was useful mainly due to the fact that it reduced the chance of values being changed without me wanting them to be. Const simply means that a variable is constant and therefore can’t be modified however it was a key word which I had had not encountered before and was unsure of how to use.

At this point my programming skills had been tested but I didn’t really understand the full picture of what it was I was doing. It wasn’t until I could see the effects of what I had written that I could really see what was going on and be able to look at ways to improve or comprehend what was actually being done by my code and why it was so important to make sure these calculations were correct.

Follow

Get every new post delivered to your Inbox.