Stage3D Pipeline In A Nutshell
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.
Why Triangles?
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.
Model Space
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
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
Camera/View Space
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
Clip Space
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
Viewport Space
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.
Conclusion
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!
#1 by AlexG on May 29th, 2012 ·
Great post for beginners. Thanks!
#2 by orion elenzil on May 29th, 2012 ·
> The Program3D fragment shader is called for every pixel of every triangle..
i think should should be “..every un-clipped pixel..” ?
#3 by jackson on May 29th, 2012 ·
Yes, that is true. However, the triangles that make it through the clipping step to viewport space have already been clipped so every one of their pixels will be drawn with the fragment shader’s output color.
#4 by EpicMan on May 30th, 2012 ·
Hello, Jackson! Sorry to ask a question off topic. I know that you are well versed in Flash and I want to ask you something.
I’m doing a game similar to the Terraria (2D MineCraft). I want to make beautiful lighting. But because of poor performance, I had to make a simple light: http://savepic.su/2086129.png
I just use mask is:
But I would like to use a mix of colors, like this:
Here I darkened the block by 50%. So, tell me how to do the same thing, only faster?
Thank you very much!
#5 by jackson on May 30th, 2012 ·
Hello, EpicMan! There are two architectural changes you can make that will greatly increase performance. First, you could switch to using a single, full-screen
Bitmap
and draw your game tiles/rects viaBitmapData
operations, particularly the lightning-fastcopyPixels
. For more on this approach, see Composing BitmapData Scenes. Second, you could switch toStage3D
, which will scale to higher resolutions (e.g. iPad 3) even better due to the use of hardware acceleration (i.e. the GPU). This approach is somewhat more complex if you implement it from scratch, but there are several high-quality 2D engines built onStage3D
(e.g. Starling, ND2D) that take care of most of the complexity for you.