Author: Jeff Hill

On the Design and Implementation of a 3D Rendering System in C++, Part 1: Vectors and Matrices

Introduction


This article is the first in a series discussing a number of oft-overlooked topics in computer graphics. The standard textbooks on 3D rendering tend towards the more theoretical aspects of the subject, and even the most practical references often implement either partial systems, or trivial systems. These articles do not attempt to detail the mechanics of a rendering system; there are many good articles on the subject by those much more qualified than your humble author. Specifically I will assume the existance of a low-level rendering toolkit, such as OpenGL, Direct3D, Quickdraw 3D or RAVE. Any system capable of rendering a scene composed of textured and shaded triangles will do. I use OpenGL on MacOS as my development platform of choice, however the topics discussed should work with little modification on any rendering system.

Vectors and Matrices


The first question any rendering system needs to address is that of vectors and matrices. 4 element or 3, float, double or fixed point, row-major or column major, the number of variants is nearly limitless. 3D mathematics libraries rival string classes as the hobby-horse of choice among graphics coders. When I sat down to write my latest revision of the ever-present vector/matrix library, I made a number of decisions based on the current philosophy in the graphics programming community, and on my experience with C++ language features. Here is a list of the decisions I made, and why.

No Templates


Often vector/matrix classes are templates, either on scalar type, vector/matrix size, or both. I feel that this is the wrong choice for two reasons; the template versions can be non-trivial to write when compared to the non-template versions, and using templates doesn't really accomplish anything, it simply defers the decisions until later. By choosing a scalar type, some math functions can be significantly optimized using lookup tables (Graphics Gems I covers this, as do a number of docs). Parameterizing on the scalar type forces either many versions of these optimizations, or the potential that perfectly reasonable code won't compile... neither is a desirable situation. Parameterizing on the size can be worse. Dot product may not be defined on 2 element vectors, or it may be, depending on your preference. I don't want to have to remember what decision I made on this 2 months from now when a piece of code refuses to compile. Better to resolve the issue ahead of time, and force the size to a constant.

The Scalar Type Is A typedef


This allows templates to be avoided, but it also allows for the scalar type to be changed easily.

4 Element Vectors


Yes, the vast majority of vectors only use 3 elements. The fourth element makes the structure pack nicely, and allows the use of one class for both "regular" vectors and homogenous coordinates.

Vectors Are Interpreted As Columns


The first book on 3D rendering I read was the Watt/Watt book, "Advanced Animation and Rendering Techniques", in which the row vector standard is used. Since then I have had to mentally transpose the matrices printed in every other graphics reference other than docs written by similarly handicapped authors. It seems the mathematical convention has won out over the hacker convention in the references, so rather than fight the standard, I will adopt it.

Operator * Is Not Overloaded For Two Vectors


What does operator * mean on a vector anyhow? matrix * vector is obvious, as is vector * scalar or scalar * vector, but is vector * vector dot or cross product? Ask two programmers, I'll lay odds they say different things. Again, resolve the confusion by avoiding it in the first place.

Operator [ ] Is Index, operator() Is Index And Modify


Const-correctness is a holy grail in C++. If your program is const-correct, then some of the nastiest sorts of bugs can be avoided completely. To this end, operator[] returns a const scalar_t& while operator() returns a scalar_t&. On matrices, it's scalar_t*'s, so to access an element is either matrix[x][y] or matrix(x)[y]. Yes, you can cast away const, but you need to work to do it, so unintentional errors can be caught by the compiler with no run-time overhead.

Almost All Operations Are Inline


Only matrix inverse, adjoint and determinant are out-of-line. The argument that this makes the code hard to read is moot, as you can defer the definition of inline functions using the inline keyword, then define all the implementations in a block at the end of the header. On modern processors, inline can make worlds of difference. Optimizing compilers can make a small inline function essentially free, when the non-inline function has function call overhead. Almost all vector/matrix ops are small enough that code stream bloat due to inline expansion is simply not an issue.

Transformation Matrices Are Defined In Math3D


While transformation and projection matrices are arguably not mathematical entities, but uses of those entities, the utility of having these functions in this part of the library outweighs any philosophical objections that may arise from their place in this library.

Place All Math Functions In A Namespace


Namespaces are a gift from the Gods. They allow the freedom to choose short, descriptive names with a guarantee that there will be no name collision. Even when working on a small project, namespaces allow the creation of seperate "modules", distinct from header/implementation file restrictions.

Conclusion


So, with the design considerations out of the way, the implementation is trivial busywork. I chose a float for the scalar type, as floats can be twice as fast as doubles, and fixed-point is essentially dead. Modern processors have floating point units that pipeline computation, so the difference between bit-shifting and floating point multiplies has all but disappeared. The code included with this article has been completely tested, and it is in use in a number of projects on MacOS.
On the Design and Implementation of a 3D Rendering System in C++
Jeff Hill may be contacted at hill@ican.net.

Included Source:


Math3d.cp
Math3d.h

Discuss this article in the forums


Date this article was posted to GameDev.net: 8/3/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Matrices
Vectors

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!