Advertisement
  1. Code
  2. Coding Fundamentals
  3. Game Development

Let's Build a 3D Graphics Engine: Colors

Scroll to top
Read Time: 8 min
This post is part of a series called Let’s Build a 3D Graphics Software Engine.
Let's Build a 3D Graphics Engine: Rasterizing Triangles and Quads
Let's Build a 3D Graphics Engine: Dynamic Lighting

Welcome! This is the sixth part in our Let's Build a 3D Graphics Engine series covering the basics of 3D graphics systems.  This time we are going to be talking about color and how to add it into our existing classes. We are also going to create a few useful functions to make handling lighting easier, which is what our next and final part will be about.


Recap

Adding color to our objects is not going to be too huge of an undertaking, so the only two class that we are going to focusing on heavily are our point class and our camera class.  As a refresher, here is what they look like:

1
Point Class
2
{
3
	Variables:
4
		num tuple[3]; //(x,y,z)

5
	Operators:
6
		Point AddVectorToPoint(Vector);
7
		Point SubtractVectorFromPoint(Vector);
8
		Vector SubtractPointFromPoint(Point);
9
		Null SetPointToPoint(Point);
10
	Functions:
11
		drawPoint; //draw a point at its position tuple

12
}
13
14
Camera Class
15
{
16
    Vars:
17
        int minX, maxX;
18
        int minY, maxY;
19
        int minZ, maxZ;
20
        array objectsInWorld; //an array of all existent objects

21
    Functions:
22
        null drawScene(); //draws all needed objects to the screen

23
}

So far, our theoretical engine has almost all of the basics in place, including:

  • Point and Vector classes (the building blocks of our engine).
  • Transformation functions for our points.
  • A Camera class (sets our viewport, and culls points outside of the screen).
  • Three classes for rasterizing (line segments, circles, and polygons).

Now let's add some color!


Color for Everyone!

Our engine is going to handle colors by storing their values within its Point class. This allows each point to have its own individual color, making lighting and shading calculations much simpler (for people, at least - sometimes it is less efficient to code an engine this way).  When figuring out a scene's lighting or shading, we can easily provide the function with a list of points, and then churn through each of them, using their distance from the light to alter their color accordingly.

One of the most common ways to store color in programming is to use red, green, and blue values to create the actual desired color (this is typically called additive color mixing).  By storing a value of 0-255 in each of these color segments, you can easily create a wide variety of colors. (This is how most APIs determine color, so for compatibility reason it makes sense to use this method). 

Then, depending on the graphics API that you are using, you can pass these values in either decimal form (255,0,0), or in hexadecimal form (0xFF0000 or #FF0000).  We're going to use decimal format in our engine since its a bit easier to work with.  Also, if your graphics API does use hexadecimal values, then it likely has a function for converting from decimal to hexadecimal, so this shouldn't be a problem.

AdditiveColorAdditiveColorAdditiveColor

To get our color implementation started we are going to add in three new variables to our Point class: red, blue, and green.  There is nothing too outlandish going on quite yet, but here is what our Point class's new outline could look like:

1
Point Class
2
{
3
	Variables:
4
		num tuple[3]; //(x,y,z)

5
		num red, green, blue; //could be abbreviated to r,g,b if desired

6
	Operators:
7
		Point AddVectorToPoint(Vector);
8
		Point SubtractVectorFromPoint(Vector);
9
		Vector SubtractPointFromPoint(Point);
10
		Null SetPointToPoint(Point);
11
	Functions:
12
		drawPoint; //draw a point at its position tuple

13
}

That is all we need to store our point's color. Now we just need to adjust our camera's draw function so that it uses the specified color.

This is going to change drastically depending on which graphics API you are using, but they should all have a similar function to this:

1
object.setColor(red, green, blue)

If your graphics API happens to use hexadecimal values for color instead of decimal, then your function would look similar to this:

1
object.setColor(toHex(red,green,blue))

That last bit uses a toHex() function (again, the function names will differ from API to API) to convert an RGB value into a hexadecimal value so that you don't have to.  

After having made these changes, you should now be able to have colored points within your scene.  To take it a step further, we are going to adjust each of our rasterization classes so that our entire shape can be colored.

To add this to our classes, we simply have to add in color handling to their constructor functions. This might look like:

1
lineSegment::constructor(startX, startY, endX, endY, red, green, blue)
2
{
3
    this.startX = startX;
4
    this.startY = startY;
5
    this.endX = endX;
6
    this.endY = endY;
7
    this.red = red;
8
    this.green = green;
9
    this.blue = blue;
10
}

Then, we just need to modify its return points function so that it sets each point in its array to have the specified color. The new function would look like this:

1
function returnPointsInSegment()
2
{
3
    //create a list to store all of the line segment's points

4
    var pointArray = new Array();
5
    //set this function's variables based on the class's starting and ending points

6
    var x0 = this.startX;
7
    var y0 = this.startY;
8
    var x1 = this.endX;
9
    var y1 = this.endY;
10
    //define vector differences and other variables required for Bresenham's Algorithm

11
    var dx = Math.abs(x1-x0);
12
    var dy = Math.abs(y1-y0);
13
    var sx = (x0 & x1) ? 1 : -1; //step x

14
    var sy = (y0 & y1) ? 1 : -1; //step y

15
    var err = dx-dy; //get the initial error value

16
    //set the first point in the array

17
    pointArray.push(new Point(x0,y0,this.red,this.green,this.blue));
18
19
    //Main processing loop

20
    while(!((x0 == x1) && (y0 == y1)))
21
    {
22
        var e2 = err * 2; //hold the error value

23
        //use the error value to determine if the point should be rounded up or down

24
        if(e2 => -dy)
25
        {
26
            err -= dy;
27
            x0 += sx;
28
        }
29
        if(e2 < dx)
30
        {
31
            err += dx;
32
            y0 += sy;
33
        }
34
        //add the new point to the array

35
        pointArray.push(new Point(x0, y0,this.red,this.green,this.blue));
36
    }
37
    return pointArray;
38
}

Now, every point within the line segment should be the same color that was passed into the line segment.  You can use this method to set colors up in your other rasterizing classes as well, and soon your scene will come alive with color!  

Let's put our new features into action by making a program to show them off.


Playing With 16.7 Million Colors

Using additive color mixing, we can easily create over 16.7 million different colors using just the simple (r,g,b) notation.  We are going to create a program that takes advantage of this vast number of colors.

Using key presses, we are going to allow the user to control an object's red, green, and blue values individually, allowing them to make it into any color that they would like.

The specifications for our program are as follows:

  • Draw an object to the screen.
  • If the user presses A then lower the object's red value; if they press Q then raise it.
  • If the user presses S then lower the object's green value; if they press W then raise it.
  • If the user presses D then lower the object's blue value; if they press E then raise it.
  • Redraw the object after its color has been updated.
  • Make sure to cap the color values, preventing them from dropping below 0 or rising above 255.

With all of that in mind, let's take a look at what a basic outline of our program might look like:

1
main{
2
3
    //setup for your favorite Graphics API here

4
    //setup for keyboard input (may not be required) here

5
6
    var camera = new Camera(); //create an instance of the camera class

7
8
    //set the camera's view space

9
    camera.minX = 0;
10
    camera.maxX = screenWidth;
11
    camera.minY = 0;
12
    camera.maxY = screenHeight;
13
    camera.minZ = 0;
14
    camera.maxZ = 100;
15
16
    //store our colors so that they can be manipulated

17
    var red, green, blue;
18
19
    //draw initial object and set it to a variable

20
21
    while(key != esc) {
22
        if(key press = 'a')
23
        {
24
            if(red > 0)
25
            {
26
                red --;
27
                object.red = red;
28
                //redraw object

29
            }
30
        }
31
        if(key press = 'q')
32
        {
33
            if(red < 255)
34
            {
35
                red ++;
36
                object.red = red;
37
                //redraw object

38
            }
39
        }
40
        if(key press = 's')
41
        {
42
            if(green > 0)
43
            {
44
                green --;
45
                object.green = green;
46
                //redraw object

47
            }
48
        }
49
        if(key press = 'w')
50
        {
51
            if(green < 255)
52
            {
53
                green ++;
54
                object.green = green;
55
                //redraw object

56
            }
57
        }
58
        if(key press = 'd')
59
        {
60
            if(blue > 0)
61
            {
62
                blue --;
63
                object.blue = blue;
64
                //redraw object

65
            }
66
        }
67
        if(key press = 'e')
68
        {
69
            if(blue < 255)
70
            {
71
                blue ++;
72
                object.blue = blue;
73
                //redraw object

74
            }
75
        }
76
    }
77
}

Now we can play around with our object and make it into any color that you desire!

Check out the demo here - repeatedly press the Q, W, E, A, S, and D keys to change the color of the square.


Conclusion

With color added into our engine, we've got everything that we need in place to finally handle some lighting. In the next article, we will be looking at creating lighting sources, and creating some functions to allow those sources to affect our points' colors. The depth that lighting adds to an engine is extremely satisfying, so make sure that you check it out!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.