Rendering 3d From Scratch: Chapter 1 - The Point

 Programming 3d stuff can be really satisfying. You write some code, and see an actual 3d world as a result. It can feel downright godly at times. Learning 3d programming is a monumental undertaking, however. Sure, there are amazing tools like Unity where you can get started in a matter of hours, but you don’t necessarily gain any understanding of a lot of the “magic” that’s taking place under the hood. And some of those things are important to understand in order to accomplish certain tasks.

 The problem is there is a huge foundation of knowledge that has to be gained before you can create even the simplest of 3d scenes. You need to be able to use some rendering engine like openGL or directX. You need to understand incredibly complex math like linear algebra, vertices, matrices, and quaternions (what?!). You need to have some semblance of an understanding of 3d models, and how they’re represented. Then, you need to be able to use software to create said models. And I haven’t even mentioned lighting yet, which is literally something you can go to university for four years to study.

 It seems daunting. In fact, it is daunting. A lot of people try to get into 3d and get discouraged by the extent of all this prerequisite knowledge. This happened to me when I first started. Luckily, I had some friends to help me learn a lot of this stuff, and I continue to learn new things every day.

 What I want to try to do here is teach some 3d concepts with as little prerequisite knowledge as possible. At the end of this series of blog posts, you’ll be able to render 3d scenes, you’ll understand why certain things are incredibly useful or important (like matrices), and you’ll have a foundation for how 3d rendering works on a conceptual level. You’ll still have to take the time to learn things like openGL and linear algebra, but you’ll be armed with some up front understanding of why these various tools are important and you’ll know where they fit in in the larger picture.

 So, without further ado, let’s do this. Now, I know I said that there was as little prerequisite knowledge as possible, but there is some prerequisite knowledge. I’m assuming you have a basic understanding of programming, and I’m also assuming you have some knowledge of math (I’m talking relatively basic arithmetic, 2d cartesian plane and line drawing, and some small amount of geometry). If you’re clear on those things, then let’s go!

 Not surprisingly, if we’re doing 3d rendering, we need a way to represent 3d things. The most basic thing is a point in 3d space. Points are the primitives of 3d, everything is composed of them. But before we create a class called Point, let’s think about a few other things we’ll need. Firstly, we need to be able to represent directions and distances. If something is further away from a camera, it looks smaller, right? And if we want to render a bunch of pixels between two points, we need to be able to start at the first point, head in the direction of the second, and drop pixels along the way like some sort of digital Johnny Appleseed.

 How, then, do we represent directions and magnitudes? Well, when thinking about 3d, it’s often useful to think about the problem in 2d, first. It’s easier to draw, and insights learned in 2d can sometimes be translated to 3d by simply adding another dimension.

 For example, let’s figure out the distance between (0, 0) and (3, 2). We can draw a line, and then use Pythagorean’s Theorem to calculate the length of the line:

right_triangle.png

 The answer is about 3.6. Great, we’ve got the distance, now what about the direction? Well, you might recall “slope” from graphing functions. Remember y=mx+b where m is the slope and b is the y intercept? Well, slope and direction are the same thing, and in this particular case, our y intercept is 0. We know slope is rise over run, so we can say our direction is ⅔. Now you might’ve noticed that the only numbers we’ve used so far are 2 and 3, which are simply the x and y coordinates of the point itself. So, basically, all the information we need is encoded into the point. We know where it is (3, 2), we know how far it is from the origin (3.6), and we know what direction we need to go to get there (⅔). And it works the exact same way in 3d. A point, direction or magnitude can all be represented by a combination of 3 values (x, y, z). We often use the mathematical term Vector to describe this combination of x, y, and z. We don’t want to call it point, because it represents more than just points, it represents directions and magnitudes.

 Now, you might validly point out that we’ve only discussed how to get directions and magnitudes from the origin, and I mentioned before that we need to be able to get directions and magnitudes from one arbitrary point to another. Well, luckily, we can simply translate the whole system to the origin:

 To accomplish this, you just have to subtract points. So, in this case, we have two points (-1, -7) and (1, -6), we subtract point 1 from both values, so point 1 = (-1 - -1, -7 - -7) = (0, 0) and point 2 = (1 - -1, -6 - 76) = (2, 1), and boom, one of our points is now the origin and we can use our tricks from above! And again, the same thing works in 3d.

graph_anim.gif

 Okay, so let’s finally define our Vector class. We’ll call it Vec3 for two reasons. One, it’s shorter and in 3d programming you’re going to be typing the name of this class all the time, and two, I want to distinguish it from C++’s vector class that exists in the standard library. Oh, and the 3 is because we’re also going to have Vec2s (2d points with just an x and y), and I want to distinguish it from that as well.

#include <cmath>

class Vec3 {
  float x;
  float y;
  float z;
public:
  Vec3(float x, float y, float z) : x(x), y(y), z(z) {}

  Vec3 add(const Vec3& v) {
    return Vec3(x + v.x, y + v.y, z + v.z);
  }

  Vec3 subtract(const Vec3& v) {
    return Vec3(x - v.x, y - v.y, z - v.z);
  }

  Vec3 multiply(float v) {
    return Vec3(x * v, y * v, z * v);
  }

  float magnitude() {
    return std::sqrtf(x * x + y * y + z * z);
  }

  float distance(const Vec3& v) const {
    float diffX = x - v.x;
    float diffY = y - v.y;
    float diffZ = z - v.z;
    return std::sqrtf(diffX * diffX + diffY * diffY + diffZ * diffZ);
  }
};

 We’ve defined our basic Vector primitive! This will be the underpinning of everything we do going forward, so be sure this makes total sense before moving ahead. In the next chapter, we’ll discuss how to represent 3d objects.

Jon Bedard3d drawing