Building a Simple 3D Engine with Silverlight

Even if you intend only to use existing 3D engines in your applications, it always helps to understand how they work. Vlad hopes that you 'take the red pill', and learn how to render three-dimensional objects with C# without the use of any pre-existing 3D engine.

This article will demonstrate how to build a simple 3D engine from scratch, using C# and Silverlight, which will allow you to:

  • Draw a group of lines in three dimensions
  • Rotate those lines around a central point
  • Add or remove lines

You can see the finished application here

In order to help demonstrate how these techniques work, I have developed a Silverlight demonstration application, called the Vector Visualizer, on top of the 3D engine discussed in this article. You can see it in action here and the complete source code is available at Codeplex. My goal is to show you how matrix maths is used to produce some simple 3D projections and rotations and to convince you that this is actually not as difficult a task as one may think.

However, you may be asking yourself why you would need such a tool in the first place. Isn’t this a little bit like reinventing the wheel? After all, aren’t there many fully developed 3D engines already out there? On a philosophical note, I don’t really subscribe to the idea that “reinventing the wheel” is always a bad thing. Of course, there are times when deadline pressures make reuse of existing code a much more attractive option but, on the other hand, can you even fully understand the wheel if you never try to reinvent it? And if no one ever tried to reinvent it, wouldn’t we be using the same old wooden wheels people used thousands of years ago?

On a more practical note, there may be times when a full-blown 3D engine might be overkill for your requirements. If you want to implement a limited 3D feature that may not have all the aspects of a fully functional 3D engine but is more efficient in terms of performance, or if you need to perform a single transformation of a 2D object to simulate 3D effect, then this article will lay the foundation knowledge you need to do that. Even if you chose not to implement them in your own code, and instead use external libraries, the basic understanding of how it all works will give you more confidence and clarity when using those libraries.

Vectors and Matrixes

The basic goal of any 3D engine is to be able to show the projection of a three dimensional object in the plane of the observer. In order to achieve this goal in building our engine, we need first to understand a few linear algebra concepts.

NOTE:
For those interested to delving deeper into linear algebra concepts, I would recommend the lectures of Prof. Gilbert Strang from the Massachusetts Institute of Technology. They are freely available on YouTube here

In this article, I want focus on implementing only basic 3D functionality, so more complex issues such as z-order and drawing surfaces will not be covered. As such, we will only need to answer the following core questions:

  1. What is a matrix?
  2. What is a vector?
  3. How do I multiply vector by matrix?
  4. How do I multiply matrix by matrix?

A matrix is a rectangular table of numbers or functions. In our case, a vector will represent a point in three-dimensional space. Our basic goal is to be able to plot a three-dimensional point and then, using a matrix, track its projection on a given plane as we rotate the point in 3D space.

I am sure that the vast majority of people reading this article will be familiar with the movie, The Matrix, with Neo flying around and shooting evil agents. I’m also sure that many of you have heard analogies made to this film before, when the topic of matrixes has been raised. However, I ask you to bear with me one more time, as it really can shed some light on this subject.

The movie plot hinges on the idea that humans live in a virtual reality, with their entire world being controlled by a program called Matrix. People go about their daily life, completely unaware of the controlling influence that the matrix has over their lives. Likewise, on the plane of a normal observer, changes in the projection of a point rotating in 3-D space can appear random and unpredictable. However, in fact, these patterns are precisely and accurately predicted by matrix operations. For one who can ‘see’ the Matrix, like our super hero Neo, the world is governed by an abstract principle invisible for the others but obvious to him.

And now for the important question: do you want the blue or the red pill? If you choose the blue pill, then stop reading this article, go download any 3D engine and stop trying to understand how it all actually works. If you choose the red pill then stay in wonderland and read on.

Operations with Matrixes

Now that we’ve established that the matrix determines the changes in projection of our three-dimensional point, as it rotates, we can get deeper into the mechanisms of how it does so.

Say we want to rotate a point by 30 degrees, around the axis-Z. First, we need to create the matrix that can perform such an operation. In other words, we need to find the matrix that will transform a three-dimensional point such that it is rotated 30 degrees around the z-axis.

Figure:  1

643-image002.jpg

We then multiply the 3D point (vector) by this matrix to produce another point that has been rotated appropriately.

We can also multiply one matrix by another matrix. Let’s say we know the matrix that rotates a point by 30 degrees around Z and we know the matrix that rotates a point by 10 degrees around Y. If we simply multiply those two matrixes together, we arrive at a single matrix that does both: rotates a point by 30 degrees around Z and by 10 degrees around Y.

Figure: 2

643-image004.jpg

These examples illustrate that the two operations we need to understand in order to build our 3D engine are:

  1. how to multiply a vector (in our case a 3D point) by a matrix,

  2. how to multiply one matrix by another matrix.

Vector-Matrix operations

We will use a three row by three column matrix, reflecting the fact that our point is located in three dimensions, and our vector will be a three-dimensional point (X, Y, Z). So, let’s first see how to multiply a point by a matrix. Surprisingly, it is quite easy and straightforward. Here is an example of such a multiplication:

Figure:  3

643-image006.jpg

Here we have an arbitrary matrix acting on a point with coordinates X = 5, Y = 1, Z = 2. We multiply each element of each row of the matrix by each element of the vector in order to obtain new values of (X, Y, Z) for the transformed point. So, for example, we multiply column 1 row 1 of the matrix by X, column 2 row 1 by Y, and column 3 row 1 by Z. We then sum these values to get the value of X for the transformed point. We repeat the process for rows 2 and 3 to arrive at the transformed values of Y and Z.

Matrix-Matrix operations

The multiplication of two matrices looks very similar. Here is one example:

Figure:  4

643-image008.jpg

As you can see we multiply each element of the row of the first matrix by each element of the column of the second matrix. We repeat that for all the rows and columns.

The Identity Matrix

The final concept that needs to be introduced, before we move on to creating matrixes for rotating a point around X,Y or Z, is that of the Identity Matrix.  The matrixes that I will use will be built on top of the identity matrix. The following figure depicts a 3×3 identity matrix:

643-image010.jpg

All of the numbers in the matrix are zero, except the numbers in the top-left to bottom-right diagonal, which are set to 1. If you try to multiply any matrix by the identity matrix you will get the same matrix, just as any number multiplied by one gives the same number.

Writing Matrix Operations in C#

It is now time to translate these matrix concepts into a real C# code.  In the source code that I referenced at the start of the article, you can find the following objects:

  • Point3D object – this stores (X, Y, Z) values of a 3D point. We will use it to store both the initial and current values for X, Y and Z. Later I will explain why we need to store both.
  • Matrix3D object – the Matrix3D object that has private property, _matrix, which is two-dimensional array of type double. In one of the dimensions we will store the rows, and in the other the columns, of the matrix.

We can see here the elegance of C#, in that it allows us to use operator overloads for the multiplication of matrix and vector. In our case, we will simply multiply the Matrix3D by the Point3D objects. Following is the implementation of this operation:

The reason that I store both the initial and current values of (X, Y, Z) is that I always transform a current point from the initial coordinates rather than the current coordinates. This is because during each transformation there will be a certain loss of precision. This means that if I rotate a point by a certain angle and then rotate it back by the same angle but with opposite sign, I will not return to the initial point because of that loss of precision. In order to avoid these complications, I always perform transformations from the initial point position.

Now let’s see how we would handle the multiplication of two matrices in C#:

The following C# code is used to set the current matrix as identity matrix:

I call this method in the constructor of the Matrix3D object because all the matrixes I use are built on top of the identity matrix.

Now, we need to write the code for the matrix that will rotate our 3D point around the axes X, Y or Z. In fact, there are three matrices, one for each plane, and each one is built on top of the identity matrix:

Figure: 6

643-image012.jpg

Translating this into C#, we get the following code;

In order to create a single matrix that rotates a point in all the three axes, simultaneously, we simply multiply these three matrices together, as follows:

Radians or Degrees?

You will have noticed that I am passing angles in radians. Radians describe the angles as a relationship between radius and the length of an arc lying on an imaginary circle around the angle within this angle. However, because we live on a planet that revolves approximately 360 times around it’s axis for each rotation around our lovely star, the sun, our ancestors decided to split the circle into 360 parts and call each part a degree. So, out of respect for the knowledge and wisdom of our ancestors, we’ll use degrees for the angles we pass to the matrix. In order to do that, I have created a utility function that transforms degrees into radians as follows:

And in this case a rotation matrix can be created from angle in degrees as follow:

Perspective Effect

Once we have multiplied the 3D point coordinates by the desired matrix, we can simply assign the new X and Y values to a point drawn on the screen, as follows:

However, because the central point around which the points rotate is not necessarily the (0,0) point on the screen we have to adjust the position of this point by adding to it the distance from the (0,0) point on the screen. We do that as follows:

This is all we need to do in order to get the basic 3D transformation. However, if we want to have more realistic 3D Figure:  we also have to consider the perspective effects. The closer an object, or part of the object is the bigger it will appear. Likewise, more distant objects will look smaller.

The following formula will transform the object in such a way so to make a closer one (one with higher Z value) to look bigger and more distant one (with smaller Z) to look smaller:

In this way, I can increase or decrease the coefficient to alter the effect of that change. If the coefficient is smaller it will look as if the rotating object is located at a certain distance from the eyes of the observer. If the coefficient is higher it will look as if it is very close to the eyes of the observer, or that it is very big in size. The bigger objects will experience that change more than the smaller ones.

In the source code, this functionality is implemented in the method, MapToPoints, of the class Line2D. This method performs the mapping from the three dimensional point of our object to a two dimensional point on the computer screen.

Conclusion

Once you wrap your head around a few basic operations with matrixes, implementing three-dimensional transformations is quite easy, 3-step process:

  1. Create the desired transforming matrix by multiplying different matrices
  2. Multiply that matrix by a point in 3D
  3. Assign that point to a 2D screen point by applying perspective formula

I hope I’ve provided some helpful insights into 3D transformations, which will be useful to you in writing your own 3D engine, or in understanding how a third party engine works. Hopefully, you’ll now look at the full source code here and try out the application here.

Tags: , , , ,

  • 43959 views

  • Rate
    [Total: 98    Average: 4.3/5]
  • Anonymous

    Good article
    It’s been a while since I’ve had linear algebra in college so it was a nice refresher to read your article. I may start to implement more 3D in my programs since I can use WPF & Silverlight.

  • Jack

    refreshing
    I also thought this was a very good refresher on 3-D rendering, but it wasn’t until I saw the demo that I forgot how exciting it could be as a software technology. I came up with a bunch of uses just by playing around with it. Looking forward to checking this out.

  • vladibo

    Silverlight 3
    If Microsoft come with a Beta version of the Silverlight 3 player next month on MIX conference it will be interesting to see what will be the 3D capabilities of Silverlight. They announced that one of the features would be the hardware acceleration for 3D.

  • Jack

    source code
    It might just be me but I’m having trouble seeing any of the source code after I download it.

  • vladibo

    Re:source code
    Possibly you download the binaries instead of code. Go to “Source Code” tab at http://www.codeplex.com/FlajaxianFileUpload/SourceControl/ListDownloadableCommits.aspx and click on the Download link on the last Change Set.

  • Rahul Singla

    Cool
    Did not had the time to read your article, but played with the sample application. It was cool.

    The code could be extended (albeit with some hair pulling) for custom tasks.

  • re-flex

    Silverlight & 3D
    Возможно что уже знакомо. Тоже довольно не плохой двиг на сильверлайте.
    http://www.denebspace.com/blog/archives/26

  • vladibo

    Re: Silverlight & 3D
    For those who don’t speak Russian, re-flex points to another Silverlight 3D project that can be seen in action here: http://www.denebspace.com/blog/wp-content/uploads/2008/12/silverlight3d1.html

    It looks quite impressive and with quite good performance but unfortunately I couldn’t find a reference to the source code.

  • Stag

    Nice article
    Nice article. I will try this

  • Anonymous

    Very helpful
    Thanks!

  • vladibo

    Nearest Stars
    Here is one evolution of this project. A tool for seeing the nearest stars within 16 light years radius in 3D

    http://www.bodurov.com/NearestStars/