Stage3D is hugely powerful, but with that power comes a very new and very complicated requirement for many programmers: shaders. Shaders are tiny programs that run on the GPU and are hugely limited compared to fancy CPU-side languages like AS3. For many programmers new to shaders these things are truly mind-bending. They’re even split into two parts: vertex and fragment shaders. Today’s article focuses on the two kinds of inputs to vertex shaders and seeks to explain them and clear up some of the confusion.

Vertex shaders get two inputs from your AS3 code: constants and attributes. Constants are the simpler of the two, so let’s get them out of the way first. Your vertex shader can use up to 128 vertex constants. Each of these is made up of four floating point values. In Adobe’s AGAL assembly language for shaders, your vertex shader can access them with the vcX syntax. For example:

mov vt0, vc53

This simple shader copies the vertex constant at index 53 (vc53) to the shader’s first temporary variable (vt0). Since it’s made up of four floating point values, you can also access them individually:

mov vt0.x, vc53.x

This shader only copies the first (x) component of the vertex constant at index 53 (vc53) to the first (x) component shader’s first temporary variable (vt0).

In summary, there are two important concepts to keep in mind for vertex constants:

  1. You get 128 vertex constants
  2. Each vertex constant consists of four floating-point values

Next there are vertex attributes. Unlike vertex constants, these are attached to a particular vertex. One way to look at it is that your vertex shader is run for each vertex and the vertex attributes are the input to it. Vertex attributes are defined as four floating point values, just like vertex constants. It’s typically a good idea to lay out how you’re going to arrange these values. For example, here’s a common list of values that a vertex shader would need for each vertex:

|-----------------------------------------------------------|
| Index | X           | Y           | Z          | W        |
|-------|-------------|-------------|------------|----------|
| 0     | Position X  | Position Y  | Position Z | {Unused} |
| 1     | Color R     | Color G     | Color B    | Color A  |
| 2     | Tex Coord U | Tex Coord V | {Unused}   | {Unused} |
|-----------------------------------------------------------|

Here’s how the shader would access these values, each stored in vaX: (note that AGAL does not support comments but I’ve added them for clarity)

va0.x // Position X
va0.y // Position Y
va0.z // Position Z
 
va1.x // Color R
va1.y // Color G
va1.z // Color B
va1.w // Color A
 
va2.x // Tex Coord U
va2.y // Tex Coord V

Compared to nice languages like AS3, this arrangement is far from ideal in many ways. Perhaps most painful is that the attributes have no names in the shader aside from their numerical indices and generic component names. However, you can sort-of get around this if you’re writing your AGAL code in AS3:

static const MY_SHADER_SOURCE:String =
    "va0.x" + // Position X
    "va0.y" + // Position Y
    "va0.z" + // Position Z
 
    "va1.x" + // Color R
    "va1.y" + // Color G
    "va1.z" + // Color B
    "va1.w" + // Color A
 
    "va2.x" + // Tex Coord U
    "va2.y";  // Tex Coord V

Now let’s look at how the vertex attributes are created. The method to call is Context3D.createVertexBuffer which will create a “vertex buffer” to hold vertex attributes for many vertices:

public function createVertexBuffer(
    numVertices:int,
    data32PerVertex:int
): VertexBuffer3D

Here you have to specify how many vertices of vertex attributes you want the buffer to hold. You also have to specify the often-confusing data32PerVertex. What is this? Well, it is the total number of floating point values in the vertex attributes for each vertex. It is not the number of vertex attributes per vertex, but the number of individual floating point values you plan to use for each vertex. The above format has three vertex attributes but three of their individual floating point values are not used. This means that data32PerVertex would be 9 in this case.

Next you’ll need to call uploadFromVector or uploadFromByteArray on the VertexBuffer3D you got back from Context3D.createVertexBuffer. The data you pass it is nothing more than a big list of vertex attributes for the vertices your vertex buffer holds. For example, here’s some sample data and upload call for a vertex buffer in the above format:

var vertexData:Vector.<Number> = new <Number>[
    // Vertex 0
    0, 0, 0, // Position XYZ
    1, 0, 0, 1, // Color RGBA
    0, 0, // Tex Coord UV
 
    // Vertex 1
    1, 0, 0, // Position XYZ
    0, 1, 0, 1, // Color RGBA
    1, 0, // Tex Coord UV
 
    // Vertex 2
    1, 1, 0, // Position XYZ
    0, 0, 1, 1, // Color RGBA
    1, 1, // Tex Coord UV
];
myVertexBuffer.uploadFromVector(vertexData, 0, 3);

Finally, every frame you want to draw with the vertices in this vertex buffer you’ll need to call Context3D.setVertexBufferAt at least once. Here is what it looks like:

public function setVertexBufferAt(
    index:int,
    buffer:VertexBuffer3D,
    bufferOffset:int = 0,
    format:String = "float4"
):void

This function maps some of the values from the vertex buffer to the shader to use via its vaX syntax. That X is the index parameter to setVertexBufferAt.

Second is simply the VertexBuffer3D you got back from Context3D.createVertexBuffer.

Third is an offset into the data for each vertex, not the whole Vector you uploaded earlier. So if you want to skip the three position floating point values, you’d pass 3 here.

Lastly, you specify format which has a double meaning. First, it species the number of values you want to map- 1, 2, 3, or 4. Second, it specifies the type of those values. Here are the values you can pass as specified by Context3DVertexBufferFormat:

  • BYTES_4 — four bytes
  • FLOAT_4 — four floating point values
  • FLOAT_3 — three floating point values
  • FLOAT_2 — two floating point values
  • FLOAT_1 — one floating point value

So, for the above data you would make three calls:

myContext.setVertexBufferAt(0, myVertexBuffer, 0, Context3DVertexBufferFormat.FLOAT_3);
myContext.setVertexBufferAt(1, myVertexBuffer, 3, Context3DVertexBufferFormat.FLOAT_4);
myContext.setVertexBufferAt(2, myVertexBuffer, 7, Context3DVertexBufferFormat.FLOAT_2);

Now you can go ahead and write your vertex shader (as above) using these vertex attributes and start drawing some triangles via Context3D.drawTriangles. This is just one piece of the overall picture, but hopefully it makes a little more sense to you if you’ve been having trouble with the subject.

Spot a bug? Have a suggestion or question? Post a comment!