Amazingly, everything you see in a
Stage3D scene is one of two very basic elements: the solid background color and a triangle. Yes, realistic portrayals of human avatars, fantastical renderings of futuristic spaceships, and every special effect you’ve ever seen are nothing more than cleverly-processed triangles. Just how are they so cleverly processed to get these effects? Today’s article takes you through the journey these triangles take from lowly three-sided shapes to building blocks of immersive experiences.
In the beginning there is a simple triangle. Really, there’s nothing that can be complex about a triangle. It has three 3D (x, y, z) vertices/vertexes/corners and three sides connecting them. This simple nature has numerous helpful implications such as the triangle always being flat and therefore laying on exactly one plane. Even a four-sided polygon (a quad) could be “folded” and therefore not lay on just one plane; this is enough complexity to make it much less useful than simply using two, three, or four triangles instead.
All triangles that make up the scene begin in what’s called “model space”. Imagine a universe where all triangles are defined in relation to the origin (0, 0, 0) with no regard for any other object in the scene, where the camera is, or any other factor. The triangles making up the “model” (e.g. an avatar, a big rock, a game level) may be centered around the origin or not. For example, an avatar’s triangles may be situated so it appears to “stand” on the origin. Essentially, this is equivalent to a
DisplayObject in 2D Flash: the children of the
DisplayObject are like triangles and are positioned relative to the origin (0, 0).
World Space is the second coordinate space in the 3D graphics “pipeline”. While triangles in model space are simply definitions produced by a 3D artist or a programmer’s algorithm, triangles in world space are computed every frame they are drawn. When every model’s model space triangles are converted into world space triangles they are all brought into the same conceptual space. Essentially, this step arranges the various models in the scene into a single space like actors taking their positions on stage. There are actually two sub-steps to this. First a matrix is created that model space vertices can be multiplied by to convert them to world space.
Matrix3D can be very helpful here as it provides an easy, built-in way of representing a 4×4 (4 rows, 4 columns) matrix. This matrix will typically contain a series of transformations such as scaling the model to the appropriate size and translating/moving it to the desired location. One of these matrices will be created per model in the scene.
The second sub-step is to apply the matrix via multiplication to the model space triangles’ vertices in the
Program3D‘s vertex shader. This will be executed once for every vertex of every model in the scene, but it happens blazingly fast because GPUs have specialized hardware for doing tons of these operations per frame. Here’s a one-line example of AGAL shader code that applies a model->world matrix:
// Matrix multiply of model->world matrix (vc0) with model space vertex (va0) into temporary (vt0) // Equivalent to: vt0 = vc0*va0 m44 vt0, va0, vc0
One of the biggest advantages to a 3D scene is the flexibility you gain by being able to easily move a 3D camera through the scene and get all kinds of expensive 2D operations (e.g. scaling, rotation) virtually for free. Adding this feature is surprisingly easy. All we need to do is provide another matrix to convert world space vertices into camera/view space. What is camera/view space? It’s the name of a space where the camera is at the origin and the rest of the scene has been moved and rotated to accommodate. The math here is pretty complicated, but you can see AS3 code for this in my Simple Stage3D Camera article. So once again we have a matrix being computed in AS3 per model and then being applied once per vertex in the scene in the vertex shader:
// Matrix multiply of model->world matrix (vc0) with model space vertex (va0) into temporary (vt0) // Equivalent to: vt0 = vc0*va0 m44 vt0, va0, vc0 // Matrix multiply of world->camera/view matrix (vc4) with world space vertex (vt0) into temporary (vt1) // Equivalent to: vt1 = vc4*vt0 m44 vt1, vt0, vc4
At this point all of the triangles are positioned relative to the camera, but there’s still a ways to go until they’re actually on the screen where we can see them. So the next step is another mathematically-complicated one covered in the Simple Stage3D Camera article. Most people don’t find it very intuitive either: the triangles are transformed so that the ones within the camera’s viewing frustum are instead within a 1x1x1 cube surrounding the camera, which is at the origin (0, 0, 0). This “clip space” is so named because it is specially-designed for really efficient clipping of triangles that are partly in the camera’s view frustum (and partly on-screen) and culling of triangles that are outside the camera’s view frustum (off-screen).
Clip space vertices are the required output of the vertex shader. The GPU will be doing all of the work after this step. So once again we have a matrix generated in AS3 code per model and applied by the vertex shader per vertex in the scene:
// Matrix multiply of model->world matrix (vc0) with model space vertex (va0) into temporary (vt0) // Equivalent to: vt0 = vc0*va0 m44 vt0, va0, vc0 // Matrix multiply of world->camera/view matrix (vc4) with world space vertex (vt0) into temporary (vt1) // Equivalent to: vt1 = vc4*vt0 m44 vt1, vt0, vc4 // Matrix multiply of camera/view->clip matrix (vc8) with world space vertex (vt1) into output (op) // Equivalent to: op = vc8*vt1 m44 op, vt1, vc8
The above shader shows the three individual steps to transform vertices from model space to camera/view space to clip space. In practice, it’s never actually done this way. Instead, the three matrices are multiplied together to form a single matrix that transforms vertices from model space all the way to clip space. So the final shader will end up being much simpler:
// Matrix multiply of model->clip matrix (vc0) with model space vertex (va0) into position (op) // Equivalent to: op = vc0*va0 m44 op, va0, vc0
This step, performed automatically the the GPU, is done to flatten the 1x1x1 cube of triangles into a 2D rectangle that matches the size (
Context3D.configureBackBuffer) and location (
Stage3D.x/y) of the area to render the scene to. At this point the 3D triangles have been flattened into 2D triangles and a 2D triangle drawing algorithm can then determine which pixels to draw. The
Program3D fragment shader is called for every pixel of every triangle to determine the color of the pixel to be drawn.
Here is an overview of the journey of triangles through the 3D pipeline:
- Model Space: triangles in relation to the model
- World Space: in relation to the world
- Camera/View Space: in relation to the camera
- Clip Space: transformed for easy clipping
- Viewport Space: flattened for 2D drawing
If you have any questions about how the 3D pipeline works, please feel free to leave a comment below!