Vertex Shader Inputs Demystified
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:
- You get 128 vertex constants
- 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 bytesFLOAT_4
— four floating point valuesFLOAT_3
— three floating point valuesFLOAT_2
— two floating point valuesFLOAT_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!
#1 by Clark on September 2nd, 2013 ·
Amazing! Thanks for the explanation Jackson.
#2 by Etherlord on September 2nd, 2013 ·
Demystification indeed!
#3 by mastrobardo on October 21st, 2013 ·
Hi there, thanks for the post (i’m a stage3d noob, i’m readig a lot of you articles ;) )!
I have a question : How you will draw in stage3d a triangle formed by :
new Point(50, 50) , new Point(100,100), new Point(100,50) ? these are values i need on screen. It’s not really clear to me how to convert these :
var vertexData:Vector. = new [
// 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
];
to real onscreen values!
Many thanks !
#4 by jackson on October 21st, 2013 ·
Check out my series on draw calls. In there I have a few variations of a 2D sprite class for
Stage3D
. The first article in the series presents the simplest version, which should be a good reference to get you started. If you want to improve the performance by reducing draw calls, follow along with the next two articles.#5 by Waetherman on January 16th, 2017 ·
Greetings from 2017! :)