Alex McGilvray

PSM Tutorial #2-2 : Verticies, Indicies and Vertex Colors

January 5th, 2013

In the last tutorial we went in depth over what a Shader Program is and how it relates to graphics hardware in the Vita (although many of these concepts carry over to other platforms).

Now we will look at the next set of declarations in the code. For easy reference as always I will start by including the full code sample.

public class AppMain
	{
		static protected GraphicsContext graphics;
		static ShaderProgram shaderProgram;
		static Texture2D texture;

		static float[] vertices=new float[12];

		static float[] texcoords = {
			0.0f, 0.0f,	// 0 top left.
			0.0f, 1.0f,	// 1 bottom left.
			1.0f, 0.0f,	// 2 top right.
			1.0f, 1.0f,	// 3 bottom right.
		};

		static float[] colors = {
			1.0f,	1.0f,	1.0f,	1.0f,	// 0 top left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 1 bottom left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 2 top right.
			1.0f,	1.0f,	1.0f,	1.0f,	// 3 bottom right.
		};

		const int indexSize = 4;
		static ushort[] indices;

		static VertexBuffer vertexBuffer;

		// Width of texture.
		static float Width;

		// Height of texture.
		static float Height;

		static Matrix4 unitScreenMatrix;

		public static void Main (string[] args)
		{
			Initialize ();

			while (true) {
				SystemEvents.CheckEvents ();
				Update ();
				Render ();
			}
		}

		public static void Initialize ()
		{
			graphics = new GraphicsContext();
			ImageRect rectScreen = graphics.Screen.Rectangle;

			texture = new Texture2D("/Application/resources/Player.png", false);
			shaderProgram = new ShaderProgram("/Application/shaders/Sprite.cgx");
			shaderProgram.SetUniformBinding(0, "u_WorldMatrix");

			Width = texture.Width;
			Height = texture.Height;

			vertices[0]=0.0f;	// x0
			vertices[1]=0.0f;	// y0
			vertices[2]=0.0f;	// z0

			vertices[3]=0.0f;	// x1
			vertices[4]=1.0f;	// y1
			vertices[5]=0.0f;	// z1

			vertices[6]=1.0f;	// x2
			vertices[7]=0.0f;	// y2
			vertices[8]=0.0f;	// z2

			vertices[9]=1.0f;	// x3
			vertices[10]=1.0f;	// y3
			vertices[11]=0.0f;	// z3

			indices = new ushort[indexSize];
			indices[0] = 0;
			indices[1] = 1;
			indices[2] = 2;
			indices[3] = 3;

			//												vertex pos,               texture,       color
			vertexBuffer = new VertexBuffer(4, indexSize, VertexFormat.Float3, VertexFormat.Float2, VertexFormat.Float4);

			vertexBuffer.SetVertices(0, vertices);
			vertexBuffer.SetVertices(1, texcoords);
			vertexBuffer.SetVertices(2, colors);

			vertexBuffer.SetIndices(indices);
			graphics.SetVertexBuffer(0, vertexBuffer);

			unitScreenMatrix = new Matrix4(
				 Width*2.0f/rectScreen.Width,	0.0f,	    0.0f, 0.0f,
				 0.0f,   Height*(-2.0f)/rectScreen.Height,	0.0f, 0.0f,
				 0.0f,   0.0f, 1.0f, 0.0f,
				 -1.0f,  1.0f, 0.0f, 1.0f
			);

		}

		public static void Update ()
		{

		}

		public static void Render ()
		{
			graphics.Clear();

			graphics.SetShaderProgram(shaderProgram);
			graphics.SetTexture(0, texture);
			shaderProgram.SetUniformValue(0, ref unitScreenMatrix);

			graphics.DrawArrays(DrawMode.TriangleStrip, 0, indexSize);

			graphics.SwapBuffers();
		}
	}

Now lets focus on the next set of declarations. The vertex array, index array and color array. Ignore the “texcoords” array for now. That will be covered in the next tutorial.

		static float[] vertices=new float[12];

		static float[] colors = {
			1.0f,	1.0f,	1.0f,	1.0f,	// 0 top left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 1 bottom left.
			1.0f,	1.0f,	1.0f,	1.0f,	// 2 top right.
			1.0f,	1.0f,	1.0f,	1.0f,	// 3 bottom right.
		};

		const int indexSize = 4;
		static ushort[] indices;

First up we have a declaration of floats that represent our vertices:

		static float[] vertices=new float[12];

If we want to draw an image on the screen we need to take an image and draw it onto a piece of geometry. In the case of drawing an image you would want to draw onto a plane with a width and height that have the same relative proportions as the image. So if you have a 100 by 200 image you would want a 100 by 200 sized plane. You could also have a 50 by 100 sized plane or a 200 by 400 sized plane. As long as the proportions match. If the proportions don’t match then you will have a stretched or squashed enemy. This is sometimes desirable. One example I can think of is if you had a Mario-Like game where you jump on an enemies head you could squish the height of the plane to make the enemy look squished.

To draw a plane we will need an array of 12 floats to store the 4 vertices we need to draw a plane. Each vertex has 3 floats it needs to describe its X, Y and Z coordinates in space. Here is the code to declare the array:

		static float[] vertices=new float[12];

Here is the code used later to define the array.

			vertices[0]=0.0f;	// x0
			vertices[1]=0.0f;	// y0
			vertices[2]=0.0f;	// z0

			vertices[3]=0.0f;	// x1
			vertices[4]=1.0f;	// y1
			vertices[5]=0.0f;	// z1

			vertices[6]=1.0f;	// x2
			vertices[7]=0.0f;	// y2
			vertices[8]=0.0f;	// z2

			vertices[9]=1.0f;	// x3
			vertices[10]=1.0f;	// y3
			vertices[11]=0.0f;	// z3

Here is a example image to help you understand how the vertices of the plane fit into the array:

Now that we have our vertices we need to explain to OpenGL how to use those vertices to make a plane. The majority of modern graphics hardware looks at everything as triangles so what we need to do is define 2 triangles to make ourselves a plane (also referred to as a quad).

Here we define the indices of the triangle.

indices = new ushort[indexSize];
indices[0] = 0;
indices[1] = 1;
indices[2] = 2;
indices[3] = 3;

This is a way to tell OpenGL which vertices are used to make triangles. In this case we are defining 4 indices. There are multiple ways to define indices depending on how you tell OpenGL to render.

If we do each triangle separately then we would need 6 indices to define 2 triangles. The first triangle would be indices 0,1,2 and the second triangle would be indices 1,2,3.

In this case you may notice only 4 indices are defined. That is because in this example the triangles are rendered in triangle strip mode. What this essentially means is that you define the first triangle with 3 indices then every subsequent triangle in the array uses the previous 2 indices plus one new index. So in the case of the array we have defined here the first triangle would use indices 0,1,2 and triangle two would use 1,2,3. If you try to visualize this you will see that it makes a strip of triangles formation.

Personally when I write game code I use the method where every triangles indices are defined separately with no triangle stripping optimization. The reason for this is simplicity. If I were to write a 3d model importer it’s much easier to parse common 3d model formats for each triangles index. Few formats have strips defined natively so you would have to write some pretty elaborate code to identify triangle strips. Also in my personal experience I’ve found little performance benefit of using strips over basic indexing.

Next up we have our vertex colors defined.


static float[] colors = {
	1.0f,	1.0f,	1.0f,	1.0f,	// 0 top left.
	1.0f,	1.0f,	1.0f,	1.0f,	// 1 bottom left.
	1.0f,	1.0f,	1.0f,	1.0f,	// 2 top right.
	1.0f,	1.0f,	1.0f,	1.0f,	// 3 bottom right.
};

Vertex colors define how a vertex is colored. These colors are linearly interpolated across the face of a triangle to make a smooth blend of color. Each vertex needs 4 floats for its colors. These 4 floats represent Red,Green,Blue and Alpha (RGBA). Here is a screenshot of a triangle with red on the top vertex, blue on the left and green on the right. Notice how it blends moving across the face from one vert to the next?

colorindices

If you had a textured triangle it would shade the triangle with those colors. If you were making a 2d game and you didn’t want any added color you would set these colors to white or maybe not even use them. They are useful though. One very useful way is to add a faux-lighting system to your game. You can perhaps have everything set to 0.5f to give the sprite half-brightness then depending on each of the sprites vertices distance from lightsources you increase the color value. The linear interpolation of the lighting values will help make the lighting look natural.

In the next tutorial we will discuss texture coordinates. Then we will discuss vertex buffers. A very important part of OpenGL and PSM. After that we will go over the rendering code which at this point should be familiar to you. Then we are ready to move on to making some games!