Commits

Jason McKesson  committed 1bb9e93

Starting to write the code for the first lighting tutorial.

  • Participants
  • Parent commits decc201

Comments (0)

Files changed (12)

File Documents/Illumination/Tutorial 08.xml

     xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0">
     <?dbhtml filename="Tutorial 08.html" ?>
     <title>Lights On</title>
-    <para/>
+    <para>It is always best to start simply. And since lighting is a big topic, we will begin with
+        the simplest possible scenario.</para>
     <section>
         <title>Modelling Lights</title>
         <para>Lighting is complicated. Very complicated. The interaction between a surface and a
                 These lighting models and rendering techniques can attempt to model cartoon styles
                 (typically called <quote>cel shading</quote>), paintbrush effects, pencil-sketch, or
                 other similar things. NPR techniques including lighting models, but they also do
-                other, non-lighting things.</para>
+                other, non-lighting things, like drawing object silhouettes in an dark, ink
+                color.</para>
             <para>Developing good NPR techniques is at least as difficult as developing good
                 photorealistic lighting models. For the most part, in this book, we will focus on
                 approximating photorealism.</para>
         <para>A <glossterm>lighting model</glossterm> is an algorithm, a mathematical function, that
             determines how a surface interacts with light.</para>
         <para>In the real world, our eyes see by detecting light that hits them. The structure of
-            our iris and lenses allow us to use a number of photorecepters, light-sensitive cells,
-            to resolve a pair of images. The light we see can have one of two sources. A light
-            emitting object can emit light that is directly captured by our eyes. Or a surface can
-            reflect light that is captured by our eyes.</para>
+            our iris and lenses use a number of photorecepters (light-sensitive cells) to resolve a
+            pair of images. The light we see can have one of two sources. A light emitting object
+            like the sun or a lamp can emit light that is directly captured by our eyes. Or a
+            surface can reflect light from another source that is captured by our eyes.</para>
         <para>The interaction between a light and a surface is the most important part of a lighting
             model. It is also the most difficult to get right. The way light interacts with atoms on
             a surface alone involves complicated quantum mechanics to understand. And even that does
             light or reflect it.</para>
         <para>A surface looks blue under white light because the surface absorbs all non-blue parts
             of the light and only reflects the blue parts. If one were to shine a red light on the
-            surface, the surface would appear very dark, as the surface absorbs non-blue
-            light.</para>
+            surface, the surface would appear very dark, as the surface absorbs non-blue light, and
+            the red light doesn't have much non-blue light in it.</para>
         <para>Therefore, the apparent color of a surface is a combination of the absorbing
             characteristics of the surface (which wavelengths are absorbed or reflected) and the
             wavelengths of light shone upon that surface.</para>
             depends on the angle between the light and the surface.</para>
         <para>Consider a perfectly flat surface. If you shine a column of light with a known
             intensity directly onto that surface, the intensity of that light at each point under
-            the surface will be a known value, based on the intensity of the light divide by the
+            the surface will be a known value, based on the intensity of the light divided by the
             area projected on the surface.</para>
         <!--TODO: Show a column of light shining directly onto the surface.-->
         <para>If the light is shone instead at an angle, the area on the surface is much wider. This
         <para>Therefore, the intensity of the light on a surface is a function of the original
             light's intensity and the angle between the surface and the light source. This angle is
             called the <glossterm>angle of incidence</glossterm> of the light.</para>
-        <para>A lighting model is a way to take these parameters (potentially other parameters as
-            well) and combine them into the reflected light color to be seen by the camera.</para>
+        <para>A lighting model is a function of all of these parameters. Complex lighting models can
+            add additional parameters.</para>
         <section>
             <title>Standard Diffuse Lighting</title>
             <para>The most common lighting model in use is <glossterm>diffuse lighting</glossterm>.
                 anything else correctly requires actual work.</para>
             <para>The diffuse lighting model makes the approximation that the intensity of the
                 reflected light depends <emphasis>only</emphasis> on the angle of incidence and the
-                intensity of the source light. In particular, this means that light is reflected in
-                all directions equally. So the view angle, the angle between the surface and the
-                camera, is irrelevant to diffuse lighting.</para>
+                intensity of the source light. In particular, this means that, for a particular
+                point on the surface, is reflected in all directions equally. So the view angle, the
+                angle between the surface and the camera, is irrelevant to diffuse lighting.</para>
             <para>The equation for diffuse lighting is quite simple:</para>
-            <!--TODO: Diffuse lighting equation. Reflected Color = Surface Color * light Color * cos(angle of incidence).-->
+            <!--TODO: Diffuse lighting equation. Reflected Color = Surface Color * light Intensity * cos(angle of incidence).-->
         </section>
         <section>
             <title>Surface Geometry</title>
             <para>Every point along a triangle has the same geometric surface normal. That's all
                 well and good.</para>
             <para>But polygonal models are supposed to be approximations of real, curved surfaces.
-                If one used the actual triangle's surface normal, the object would look very
-                faceted. This might be an accurate of what was actually drawn, but it reveals the
-                surface to be exactly what it is: an approximation.</para>
-            <para>Instead of using the triangle's normal, we can instead assign each vertex the
-                normal it <emphasis>would</emphasis> have on the surface it is approximating. That
-                is, while the mesh is an approximating, the normal for a vertex is the actual normal
-                for that surface. This actually works out surprisingly well.</para>
+                If we use the actual triangle's surface normal for all of the points on a triangle,
+                the object would look very faceted. This would certainly be an accurate
+                representation of the actual triangular mesh, but it reveals the surface to be
+                exactly what it is: a triangular mesh approximation of a curved surface. If we want
+                to create the illusion that the surface really is curved, we need to do something
+                else.</para>
+            <para>Instead of using the triangle's normal, we can assign to each vertex the normal
+                that it <emphasis>would</emphasis> have on the surface it is approximating. That is,
+                while the mesh is an approximating, the normal for a vertex is the actual normal for
+                that surface. This actually works out surprisingly well.</para>
             <para>This means that we must add to the vertex's information. In past tutorials, we
                 have had a position and sometimes a color. To that information, we add a normal. So
-                we will need a vertex shader input, a vertex attribute, that represents the
-                normal.</para>
-            <para/>
+                we will need a vertex attribute that represents the normal.</para>
         </section>
+        <section>
+            <title>Gouraud Shading</title>
+            <para>So each vertex has a normal. That is useful, but it is not sufficient, for one
+                simple reason. We don't draw the vertices of triangles; we draw the rasterized form
+                that makes up the interior of a triangle.</para>
+            <para>There are several ways to go about computing lighting across the surface of a
+                triangle. The simplest to code, and most efficient for rendering, is to perform the
+                lighting computations at every vertex, and then let the result of this computation
+                be interpolated across the surface of the triangle. This process is called
+                    <glossterm>Gouraud shading.</glossterm></para>
+            <para>Gouraud shading is a pretty decent approximation, when using the diffuse lighting
+                model. It usually looks OK, and was commonly used for a good decade or so.
+                Interpolation of vertex outputs is a very fast process, and not having to compute
+                lighting at every fragment generated from the triangle raises the performance
+                substantially.</para>
+            <para>That being said, modern games have essentially abandoned this technique. Part of
+                that is because the per-fragment computation isn't as limited as it used to be. And
+                part of it is simply that games tend to not use simple diffuse lighting
+                anymore.</para>
+        </section>
+        <section>
+            <title>Directional Light Source</title>
+            <para>The angle of incidence is the angle between the surface normal and the direction
+                towards the light. Computing the direction from the point in question to the light
+                can be done in a couple of ways.</para>
+            <para>If you have a light source that is very close to an object, then the direction
+                towards the light can change dramatically over the surface of that object. As the
+                light source is moved farther and farther away, the direction towards the light
+                varies less and less over the surface of the object.</para>
+            <!--TODO: Show a picture of a light source close to an object, and the direction twoards the light. The show a more distant light source,
+and the directions towards the light.-->
+            <para>If the light source is sufficiently distant, relative to the size of the scene
+                being rendered, then the direction towards the light is the same for every object
+                you render. Since the direction is the same for all objects, the direction can
+                simply be a single direction given to all of the objects. There is no need to
+                compute the direction based on the position of the point being illuminated.</para>
+            <para>This situation is called a <glossterm>directional light source.</glossterm> Light
+                from such a source effectively comes from a particular direction as a wall of
+                intensity, evenly distributed over the scene.</para>
+            <!--TODO: Show a diagram of directional light source lighting a scene in 2D.-->
+            <para>Direction light sources are a good model for lights like the sun relative to a
+                small region of the Earth. It would not be a good model for the sun relative to the
+                rest of the solar system. So scale is important.</para>
+            <para>Alternatives to directional lights will be discussed a bit later.</para>
+        </section>
+    </section>
+    <section>
+        <title>Normal Transformation</title>
+        <para/>
+    </section>
+    <section>
+        <title>Interreflection</title>
+        <para/>
+    </section>
+    <section>
+        <title>Positional Lights</title>
+        <para/>
     </section>
     <section>
         <title>Intensity of Light</title>
                     <para/>
                 </glossdef>
             </glossentry>
+            <glossentry>
+                <glossterm>surface normal, normal</glossterm>
+                <glossdef>
+                    <para/>
+                </glossdef>
+            </glossentry>
+            <glossentry>
+                <glossterm>Gouraud shading</glossterm>
+                <glossdef>
+                    <para/>
+                </glossdef>
+            </glossentry>
+            <glossentry>
+                <glossterm>directional light source</glossterm>
+                <glossdef>
+                    <para/>
+                </glossdef>
+            </glossentry>
         </glosslist>
     </section>
 </article>

File Documents/meshFormat.rnc

         element msh:mesh {mf.mesh.content}
 
     mf.mesh.content =
-        mf.attribute+, mf.rendering-commands+
+        mf.attribute+, mf.vao*, mf.rendering-commands+
         
     mf.attribute =
         ##A single attribute array. It can contain any kind of attribute data.
         
     mf.attribute.content =
         mf.attribute.attlist, text
+    
+    mf.vao =
+        ##These are named VAOs, which represent collections of attributes that can be used to render
+        ##the mesh.
+        element msh:vao { mf.vao.content }
         
+    mf.vao.content =
+        mf.vao.attlist, mf.source+
+        
+    mf.source =
+        ##This represents one of the attributes used by the VAO.
+        element msh:source { mf.source.content }
+        
+    mf.source.content =
+        mf.source.attlist
+    
     mf.rendering-commands =
         ##These are the possible commands for rendering this mesh.
         (mf.indices | mf.arrays)
     mf.attribute.attlist =
         mf.attribute.index.attribute, mf.attribute.integral.attribute?, mf.attribute.type.attribute, mf.attribute.size.attribute
         
+    mf.vao.attlist =
+        mf.vao.name.attribute
+
+    mf.source.attlist =
+        mf.source.attrib.attribute
+
     mf.indices.attlist =
         mf.indices.type.attribute, mf.cmd.attribute, mf.indices.primrestart.attribute?
         
         
     mf.attribute.index.attribute =
         ##The attribute index to be used for this vertex attribute.
-        attribute index { xsd:nonNegativeInteger { minInclusive = "0" maxExclusive = "16"} }
+        attribute index { acc.attribute.type }
         
     mf.attribute.size.attribute =
         ##The number of components in this vertex attribute. 1-4.
         ##True if this attribute is to be passed as an integral attribute.
         ##Defaults to false.
         attribute integral {"true"|"false"}
-        
+
+    mf.vao.name.attribute =
+        attribute name { text }
+    
+    mf.source.attrib.attribute =
+        attribute attrib { acc.attribute.type }
+
     mf.cmd.attribute =
         ##The primitive type used to render with this rendering command..
         attribute cmd { "triangles" | "tri-strip" | "tri-fan" | "lines" | "line-strip" |
         attribute count { xsd:positiveInteger }
 }
 
+## Accessories
+div
+{
+    acc.attribute.type =
+        xsd:nonNegativeInteger { minInclusive = "0" maxExclusive = "16"}
+}
+

File Tut 07 World in Motion/World Scene.cpp

 		glUseProgram(UniformColorTint.theProgram);
 		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
 		glUniform4f(UniformColorTint.baseColorUnif, 0.0f, 1.0f, 0.0f, 1.0f);
-		g_pConeMesh->Render();
+		g_pConeMesh->Render("all");
 		glUseProgram(0);
 	}
 }

File Tut 08 Lights on/Basic Lighting.cpp

+#include <string>
+#include <vector>
+#include <stack>
+#include <math.h>
+#include <glloader/gl_3_2_comp.h>
+#include <GL/freeglut.h>
+#include "../framework/framework.h"
+#include <glm/glm.hpp>
+#include <glm/gtc/type_ptr.hpp>
+
+#define ARRAY_COUNT( array ) (sizeof( array ) / (sizeof( array[0] ) * (sizeof( array ) != sizeof(void*) || sizeof( array[0] ) <= sizeof(void*))))
+
+struct ProgramData
+{
+	GLuint theProgram;
+	GLuint modelToWorldMatrixUnif;
+	GLuint worldToCameraMatrixUnif;
+	GLuint cameraToClipMatrixUnif;
+	GLuint baseColorUnif;
+};
+
+float g_fzNear = 1.0f;
+float g_fzFar = 1000.0f;
+
+ProgramData UniformColor;
+ProgramData ObjectColor;
+ProgramData UniformColorTint;
+
+ProgramData LoadProgram(const std::string &strVertexShader, const std::string &strFragmentShader)
+{
+	std::vector<GLuint> shaderList;
+
+	shaderList.push_back(Framework::LoadShader(GL_VERTEX_SHADER, strVertexShader));
+	shaderList.push_back(Framework::LoadShader(GL_FRAGMENT_SHADER, strFragmentShader));
+
+	ProgramData data;
+	data.theProgram = Framework::CreateProgram(shaderList);
+	data.modelToWorldMatrixUnif = glGetUniformLocation(data.theProgram, "modelToWorldMatrix");
+	data.worldToCameraMatrixUnif = glGetUniformLocation(data.theProgram, "worldToCameraMatrix");
+	data.cameraToClipMatrixUnif = glGetUniformLocation(data.theProgram, "cameraToClipMatrix");
+	data.baseColorUnif = glGetUniformLocation(data.theProgram, "baseColor");
+
+	return data;
+}
+
+void InitializeProgram()
+{
+	UniformColor = LoadProgram("PosOnlyWorldTransform.vert", "ColorUniform.frag");
+	ObjectColor = LoadProgram("PosColorWorldTransform.vert", "ColorPassthrough.frag");
+	UniformColorTint = LoadProgram("PosColorWorldTransform.vert", "ColorMultUniform.frag");
+}
+
+glm::mat4 CalcLookAtMatrix(const glm::vec3 &cameraPt, const glm::vec3 &lookPt, const glm::vec3 &upPt)
+{
+	glm::vec3 lookDir = glm::normalize(lookPt - cameraPt);
+	glm::vec3 upDir = glm::normalize(upPt);
+
+	glm::vec3 rightDir = glm::normalize(glm::cross(lookDir, upDir));
+	glm::vec3 perpUpDir = glm::cross(rightDir, lookDir);
+
+	glm::mat4 rotMat(1.0f);
+	rotMat[0] = glm::vec4(rightDir, 0.0f);
+	rotMat[1] = glm::vec4(perpUpDir, 0.0f);
+	rotMat[2] = glm::vec4(-lookDir, 0.0f);
+
+	rotMat = glm::transpose(rotMat);
+
+	glm::mat4 transMat(1.0f);
+	transMat[3] = glm::vec4(-cameraPt, 1.0f);
+
+	return rotMat * transMat;
+}
+
+Framework::Mesh *g_pConeMesh = NULL;
+Framework::Mesh *g_pCylinderMesh = NULL;
+Framework::Mesh *g_pCubeTintMesh = NULL;
+Framework::Mesh *g_pCubeColorMesh = NULL;
+Framework::Mesh *g_pPlaneMesh = NULL;
+
+//Called after the window and OpenGL are initialized. Called exactly once, before the main loop.
+void init()
+{
+	InitializeProgram();
+
+	try
+	{
+		g_pConeMesh = new Framework::Mesh("UnitConeTint.xml");
+		g_pCylinderMesh = new Framework::Mesh("UnitCylinderTint.xml");
+		g_pCubeTintMesh = new Framework::Mesh("UnitCubeTint.xml");
+		g_pCubeColorMesh = new Framework::Mesh("UnitCubeColor.xml");
+		g_pPlaneMesh = new Framework::Mesh("UnitPlane.xml");
+	}
+	catch(std::exception &except)
+	{
+		printf(except.what());
+	}
+
+	glEnable(GL_CULL_FACE);
+	glCullFace(GL_BACK);
+	glFrontFace(GL_CW);
+
+	glEnable(GL_DEPTH_TEST);
+	glDepthMask(GL_TRUE);
+	glDepthFunc(GL_LEQUAL);
+	glDepthRange(0.0f, 1.0f);
+	glEnable(GL_DEPTH_CLAMP);
+}
+
+static float g_fYAngle = 0.0f;
+static float g_fXAngle = 0.0f;
+
+//Trees are 3x3 in X/Z, and fTrunkHeight+fConeHeight in the Y.
+void DrawTree(Framework::MatrixStack &modelMatrix, float fTrunkHeight = 2.0f, float fConeHeight = 3.0f)
+{
+	//Draw trunk.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Scale(glm::vec3(1.0f, fTrunkHeight, 1.0f));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.694f, 0.4f, 0.106f, 1.0f);
+		g_pCylinderMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw the treetop
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(0.0f, fTrunkHeight, 0.0f));
+		modelMatrix.Scale(glm::vec3(3.0f, fConeHeight, 3.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.0f, 1.0f, 0.0f, 1.0f);
+		g_pConeMesh->Render();
+		glUseProgram(0);
+	}
+}
+
+const float g_fColumnBaseHeight = 0.25f;
+
+//Columns are 1x1 in the X/Z, and fHieght units in the Y.
+void DrawColumn(Framework::MatrixStack &modelMatrix, float fHeight = 5.0f)
+{
+	//Draw the bottom of the column.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Scale(glm::vec3(1.0f, g_fColumnBaseHeight, 1.0f));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 1.0f, 1.0f, 1.0f, 1.0f);
+		g_pCubeTintMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw the top of the column.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(0.0f, fHeight - g_fColumnBaseHeight, 0.0f));
+		modelMatrix.Scale(glm::vec3(1.0f, g_fColumnBaseHeight, 1.0f));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.9f, 0.9f, 0.9f, 0.9f);
+		g_pCubeTintMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw the main column.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(0.0f, g_fColumnBaseHeight, 0.0f));
+		modelMatrix.Scale(glm::vec3(0.8f, fHeight - (g_fColumnBaseHeight * 2.0f), 0.8f));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.9f, 0.9f, 0.9f, 0.9f);
+		g_pCylinderMesh->Render();
+		glUseProgram(0);
+	}
+}
+
+const float g_fParthenonWidth = 14.0f;
+const float g_fParthenonLength = 20.0f;
+const float g_fParthenonColumnHeight = 5.0f;
+const float g_fParthenonBaseHeight = 1.0f;
+const float g_fParthenonTopHeight = 2.0f;
+
+void DrawParthenon(Framework::MatrixStack &modelMatrix)
+{
+	//Draw base.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Scale(glm::vec3(g_fParthenonWidth, g_fParthenonBaseHeight, g_fParthenonLength));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.9f, 0.9f, 0.9f, 0.9f);
+		g_pCubeTintMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw top.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(0.0f, g_fParthenonColumnHeight + g_fParthenonBaseHeight, 0.0f));
+		modelMatrix.Scale(glm::vec3(g_fParthenonWidth, g_fParthenonTopHeight, g_fParthenonLength));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		glUniform4f(UniformColorTint.baseColorUnif, 0.9f, 0.9f, 0.9f, 0.9f);
+		g_pCubeTintMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw columns.
+	const float fFrontZVal = (g_fParthenonLength / 2.0f) - 1.0f;
+	const float fRightXVal = (g_fParthenonWidth / 2.0f) - 1.0f;
+
+	for(int iColumnNum = 0; iColumnNum < int(g_fParthenonWidth / 2.0f); iColumnNum++)
+	{
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+			modelMatrix.Translate(glm::vec3((2.0f * iColumnNum) - (g_fParthenonWidth / 2.0f) + 1.0f,
+				g_fParthenonBaseHeight, fFrontZVal));
+
+			DrawColumn(modelMatrix, g_fParthenonColumnHeight);
+		}
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+			modelMatrix.Translate(glm::vec3((2.0f * iColumnNum) - (g_fParthenonWidth / 2.0f) + 1.0f,
+				g_fParthenonBaseHeight, -fFrontZVal));
+
+			DrawColumn(modelMatrix, g_fParthenonColumnHeight);
+		}
+	}
+
+	//Don't draw the first or last columns, since they've been drawn already.
+	for(int iColumnNum = 1; iColumnNum < int((g_fParthenonLength - 2.0f) / 2.0f); iColumnNum++)
+	{
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+			modelMatrix.Translate(glm::vec3(fRightXVal,
+				g_fParthenonBaseHeight, (2.0f * iColumnNum) - (g_fParthenonLength / 2.0f) + 1.0f));
+
+			DrawColumn(modelMatrix, g_fParthenonColumnHeight);
+		}
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+			modelMatrix.Translate(glm::vec3(-fRightXVal,
+				g_fParthenonBaseHeight, (2.0f * iColumnNum) - (g_fParthenonLength / 2.0f) + 1.0f));
+
+			DrawColumn(modelMatrix, g_fParthenonColumnHeight);
+		}
+	}
+
+	//Draw interior.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(0.0f, 1.0f, 0.0f));
+		modelMatrix.Scale(glm::vec3(g_fParthenonWidth - 6.0f, g_fParthenonColumnHeight,
+			g_fParthenonLength - 6.0f));
+		modelMatrix.Translate(glm::vec3(0.0f, 0.5f, 0.0f));
+
+		glUseProgram(ObjectColor.theProgram);
+		glUniformMatrix4fv(ObjectColor.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		g_pCubeColorMesh->Render();
+		glUseProgram(0);
+	}
+
+	//Draw headpiece.
+	{
+		Framework::MatrixStackPusher push(modelMatrix);
+
+		modelMatrix.Translate(glm::vec3(
+			0.0f,
+			g_fParthenonColumnHeight + g_fParthenonBaseHeight + (g_fParthenonTopHeight / 2.0f),
+			g_fParthenonLength / 2.0f));
+		modelMatrix.RotateX(-135.0f);
+		modelMatrix.RotateY(45.0f);
+
+		glUseProgram(ObjectColor.theProgram);
+		glUniformMatrix4fv(ObjectColor.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+		g_pCubeColorMesh->Render();
+		glUseProgram(0);
+	}
+}
+
+struct TreeData
+{
+	float fXPos;
+	float fZPos;
+	float fTrunkHeight;
+	float fConeHeight;
+};
+
+static const TreeData g_forest[] =
+{
+	{-45.0f, -40.0f, 2.0f, 3.0f},
+	{-42.0f, -35.0f, 2.0f, 3.0f},
+	{-39.0f, -29.0f, 2.0f, 4.0f},
+	{-44.0f, -26.0f, 3.0f, 3.0f},
+	{-40.0f, -22.0f, 2.0f, 4.0f},
+	{-36.0f, -15.0f, 3.0f, 3.0f},
+	{-41.0f, -11.0f, 2.0f, 3.0f},
+	{-37.0f, -6.0f, 3.0f, 3.0f},
+	{-45.0f, 0.0f, 2.0f, 3.0f},
+	{-39.0f, 4.0f, 3.0f, 4.0f},
+	{-36.0f, 8.0f, 2.0f, 3.0f},
+	{-44.0f, 13.0f, 3.0f, 3.0f},
+	{-42.0f, 17.0f, 2.0f, 3.0f},
+	{-38.0f, 23.0f, 3.0f, 4.0f},
+	{-41.0f, 27.0f, 2.0f, 3.0f},
+	{-39.0f, 32.0f, 3.0f, 3.0f},
+	{-44.0f, 37.0f, 3.0f, 4.0f},
+	{-36.0f, 42.0f, 2.0f, 3.0f},
+
+	{-32.0f, -45.0f, 2.0f, 3.0f},
+	{-30.0f, -42.0f, 2.0f, 4.0f},
+	{-34.0f, -38.0f, 3.0f, 5.0f},
+	{-33.0f, -35.0f, 3.0f, 4.0f},
+	{-29.0f, -28.0f, 2.0f, 3.0f},
+	{-26.0f, -25.0f, 3.0f, 5.0f},
+	{-35.0f, -21.0f, 3.0f, 4.0f},
+	{-31.0f, -17.0f, 3.0f, 3.0f},
+	{-28.0f, -12.0f, 2.0f, 4.0f},
+	{-29.0f, -7.0f, 3.0f, 3.0f},
+	{-26.0f, -1.0f, 2.0f, 4.0f},
+	{-32.0f, 6.0f, 2.0f, 3.0f},
+	{-30.0f, 10.0f, 3.0f, 5.0f},
+	{-33.0f, 14.0f, 2.0f, 4.0f},
+	{-35.0f, 19.0f, 3.0f, 4.0f},
+	{-28.0f, 22.0f, 2.0f, 3.0f},
+	{-33.0f, 26.0f, 3.0f, 3.0f},
+	{-29.0f, 31.0f, 3.0f, 4.0f},
+	{-32.0f, 38.0f, 2.0f, 3.0f},
+	{-27.0f, 41.0f, 3.0f, 4.0f},
+	{-31.0f, 45.0f, 2.0f, 4.0f},
+	{-28.0f, 48.0f, 3.0f, 5.0f},
+
+	{-25.0f, -48.0f, 2.0f, 3.0f},
+	{-20.0f, -42.0f, 3.0f, 4.0f},
+	{-22.0f, -39.0f, 2.0f, 3.0f},
+	{-19.0f, -34.0f, 2.0f, 3.0f},
+	{-23.0f, -30.0f, 3.0f, 4.0f},
+	{-24.0f, -24.0f, 2.0f, 3.0f},
+	{-16.0f, -21.0f, 2.0f, 3.0f},
+	{-17.0f, -17.0f, 3.0f, 3.0f},
+	{-25.0f, -13.0f, 2.0f, 4.0f},
+	{-23.0f, -8.0f, 2.0f, 3.0f},
+	{-17.0f, -2.0f, 3.0f, 3.0f},
+	{-16.0f, 1.0f, 2.0f, 3.0f},
+	{-19.0f, 4.0f, 3.0f, 3.0f},
+	{-22.0f, 8.0f, 2.0f, 4.0f},
+	{-21.0f, 14.0f, 2.0f, 3.0f},
+	{-16.0f, 19.0f, 2.0f, 3.0f},
+	{-23.0f, 24.0f, 3.0f, 3.0f},
+	{-18.0f, 28.0f, 2.0f, 4.0f},
+	{-24.0f, 31.0f, 2.0f, 3.0f},
+	{-20.0f, 36.0f, 2.0f, 3.0f},
+	{-22.0f, 41.0f, 3.0f, 3.0f},
+	{-21.0f, 45.0f, 2.0f, 3.0f},
+
+	{-12.0f, -40.0f, 2.0f, 4.0f},
+	{-11.0f, -35.0f, 3.0f, 3.0f},
+	{-10.0f, -29.0f, 1.0f, 3.0f},
+	{-9.0f, -26.0f, 2.0f, 2.0f},
+	{-6.0f, -22.0f, 2.0f, 3.0f},
+	{-15.0f, -15.0f, 1.0f, 3.0f},
+	{-8.0f, -11.0f, 2.0f, 3.0f},
+	{-14.0f, -6.0f, 2.0f, 4.0f},
+	{-12.0f, 0.0f, 2.0f, 3.0f},
+	{-7.0f, 4.0f, 2.0f, 2.0f},
+	{-13.0f, 8.0f, 2.0f, 2.0f},
+	{-9.0f, 13.0f, 1.0f, 3.0f},
+	{-13.0f, 17.0f, 3.0f, 4.0f},
+	{-6.0f, 23.0f, 2.0f, 3.0f},
+	{-12.0f, 27.0f, 1.0f, 2.0f},
+	{-8.0f, 32.0f, 2.0f, 3.0f},
+	{-10.0f, 37.0f, 3.0f, 3.0f},
+	{-11.0f, 42.0f, 2.0f, 2.0f},
+
+
+	{15.0f, 5.0f, 2.0f, 3.0f},
+	{15.0f, 10.0f, 2.0f, 3.0f},
+	{15.0f, 15.0f, 2.0f, 3.0f},
+	{15.0f, 20.0f, 2.0f, 3.0f},
+	{15.0f, 25.0f, 2.0f, 3.0f},
+	{15.0f, 30.0f, 2.0f, 3.0f},
+	{15.0f, 35.0f, 2.0f, 3.0f},
+	{15.0f, 40.0f, 2.0f, 3.0f},
+	{15.0f, 45.0f, 2.0f, 3.0f},
+
+	{25.0f, 5.0f, 2.0f, 3.0f},
+	{25.0f, 10.0f, 2.0f, 3.0f},
+	{25.0f, 15.0f, 2.0f, 3.0f},
+	{25.0f, 20.0f, 2.0f, 3.0f},
+	{25.0f, 25.0f, 2.0f, 3.0f},
+	{25.0f, 30.0f, 2.0f, 3.0f},
+	{25.0f, 35.0f, 2.0f, 3.0f},
+	{25.0f, 40.0f, 2.0f, 3.0f},
+	{25.0f, 45.0f, 2.0f, 3.0f},
+};
+
+void DrawForest(Framework::MatrixStack &modelMatrix)
+{
+	for(int iTree = 0; iTree < ARRAY_COUNT(g_forest); iTree++)
+	{
+		const TreeData &currTree = g_forest[iTree];
+
+		Framework::MatrixStackPusher push(modelMatrix);
+		modelMatrix.Translate(glm::vec3(currTree.fXPos, 0.0f, currTree.fZPos));
+		DrawTree(modelMatrix, currTree.fTrunkHeight, currTree.fConeHeight);
+	}
+}
+
+static bool g_bDrawLookatPoint = false;
+static glm::vec3 g_camTarget(0.0f, 0.4f, 0.0f);
+
+//In spherical coordinates.
+static glm::vec3 g_sphereCamRelPos(67.5f, -46.0f, 150.0f);
+
+glm::vec3 ResolveCamPosition()
+{
+	Framework::MatrixStack tempMat;
+
+	float rho = Framework::DegToRad(g_sphereCamRelPos.x);
+	float theta = Framework::DegToRad(g_sphereCamRelPos.y + 90.0f);
+
+	float fSinTheta = sinf(theta);
+	float fCosTheta = cosf(theta);
+	float fCosRho = cosf(rho);
+	float fSinRho = sinf(rho);
+
+	glm::vec3 dirToCamera(fSinTheta * fCosRho, fCosTheta, fSinTheta * fSinRho);
+	return (dirToCamera * g_sphereCamRelPos.z) + g_camTarget;
+}
+
+//Called to update the display.
+//You should call glutSwapBuffers after all of your rendering to display what you rendered.
+//If you need continuous updates of the screen, call glutPostRedisplay() at the end of the function.
+void display()
+{
+	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+	glClearDepth(1.0f);
+	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+	if(g_pConeMesh && g_pCylinderMesh && g_pCubeTintMesh && g_pCubeColorMesh && g_pPlaneMesh)
+	{
+		const glm::vec3 &camPos = ResolveCamPosition();
+
+		Framework::MatrixStack camMatrix;
+		camMatrix.SetMatrix(CalcLookAtMatrix(camPos, g_camTarget, glm::vec3(0.0f, 1.0f, 0.0f)));
+
+		glUseProgram(UniformColor.theProgram);
+		glUniformMatrix4fv(UniformColor.worldToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(camMatrix.Top()));
+		glUseProgram(ObjectColor.theProgram);
+		glUniformMatrix4fv(ObjectColor.worldToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(camMatrix.Top()));
+		glUseProgram(UniformColorTint.theProgram);
+		glUniformMatrix4fv(UniformColorTint.worldToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(camMatrix.Top()));
+		glUseProgram(0);
+
+		Framework::MatrixStack modelMatrix;
+
+		//Render the ground plane.
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+
+			modelMatrix.Scale(glm::vec3(100.0f, 1.0f, 100.0f));
+
+			glUseProgram(UniformColor.theProgram);
+			glUniformMatrix4fv(UniformColor.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+			glUniform4f(UniformColor.baseColorUnif, 0.302f, 0.416f, 0.0589f, 1.0f);
+			g_pPlaneMesh->Render();
+			glUseProgram(0);
+		}
+
+		//Draw the trees
+		DrawForest(modelMatrix);
+
+		//Draw the building.
+		{
+			Framework::MatrixStackPusher push(modelMatrix);
+			modelMatrix.Translate(glm::vec3(20.0f, 0.0f, -10.0f));
+
+			DrawParthenon(modelMatrix);
+		}
+
+		if(g_bDrawLookatPoint)
+		{
+			glDisable(GL_DEPTH_TEST);
+			glm::mat4 idenity(1.0f);
+
+			Framework::MatrixStackPusher push(modelMatrix);
+
+			glm::vec3 cameraAimVec = g_camTarget - camPos;
+			modelMatrix.Translate(0.0f, 0.0, -glm::length(cameraAimVec));
+			modelMatrix.Scale(1.0f, 1.0f, 1.0f);
+		
+			glUseProgram(ObjectColor.theProgram);
+			glUniformMatrix4fv(ObjectColor.modelToWorldMatrixUnif, 1, GL_FALSE, glm::value_ptr(modelMatrix.Top()));
+			glUniformMatrix4fv(ObjectColor.worldToCameraMatrixUnif, 1, GL_FALSE, glm::value_ptr(idenity));
+			g_pCubeColorMesh->Render();
+			glUseProgram(0);
+			glEnable(GL_DEPTH_TEST);
+		}
+	}
+
+	glutSwapBuffers();
+}
+
+//Called whenever the window is resized. The new window size is given, in pixels.
+//This is an opportunity to call glViewport or glScissor to keep up with the change in size.
+void reshape (int w, int h)
+{
+	Framework::MatrixStack persMatrix;
+	persMatrix.Perspective(45.0f, (h / (float)w), g_fzNear, g_fzFar);
+
+	glUseProgram(UniformColor.theProgram);
+	glUniformMatrix4fv(UniformColor.cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(persMatrix.Top()));
+	glUseProgram(ObjectColor.theProgram);
+	glUniformMatrix4fv(ObjectColor.cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(persMatrix.Top()));
+	glUseProgram(UniformColorTint.theProgram);
+	glUniformMatrix4fv(UniformColorTint.cameraToClipMatrixUnif, 1, GL_FALSE, glm::value_ptr(persMatrix.Top()));
+	glUseProgram(0);
+
+	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
+	glutPostRedisplay();
+}
+
+//Called whenever a key on the keyboard was pressed.
+//The key is given by the ''key'' parameter, which is in ASCII.
+//It's often a good idea to have the escape key (ASCII value 27) call glutLeaveMainLoop() to 
+//exit the program.
+void keyboard(unsigned char key, int x, int y)
+{
+	switch (key)
+	{
+	case 27:
+		delete g_pConeMesh;
+		delete g_pCylinderMesh;
+		delete g_pCubeTintMesh;
+		delete g_pCubeColorMesh;
+		glutLeaveMainLoop();
+		break;
+	case 'w': g_camTarget.z -= 4.0f; break;
+	case 's': g_camTarget.z += 4.0f; break;
+	case 'd': g_camTarget.x += 4.0f; break;
+	case 'a': g_camTarget.x -= 4.0f; break;
+	case 'e': g_camTarget.y -= 4.0f; break;
+	case 'q': g_camTarget.y += 4.0f; break;
+	case 'W': g_camTarget.z -= 0.4f; break;
+	case 'S': g_camTarget.z += 0.4f; break;
+	case 'D': g_camTarget.x += 0.4f; break;
+	case 'A': g_camTarget.x -= 0.4f; break;
+	case 'E': g_camTarget.y -= 0.4f; break;
+	case 'Q': g_camTarget.y += 0.4f; break;
+	case 'i': g_sphereCamRelPos.y -= 11.25f; break;
+	case 'k': g_sphereCamRelPos.y += 11.25f; break;
+	case 'j': g_sphereCamRelPos.x -= 11.25f; break;
+	case 'l': g_sphereCamRelPos.x += 11.25f; break;
+	case 'o': g_sphereCamRelPos.z -= 5.0f; break;
+	case 'u': g_sphereCamRelPos.z += 5.0f; break;
+	case 'I': g_sphereCamRelPos.y -= 1.125f; break;
+	case 'K': g_sphereCamRelPos.y += 1.125f; break;
+	case 'J': g_sphereCamRelPos.x -= 1.125f; break;
+	case 'L': g_sphereCamRelPos.x += 1.125f; break;
+	case 'O': g_sphereCamRelPos.z -= 0.5f; break;
+	case 'U': g_sphereCamRelPos.z += 0.5f; break;
+		
+	case 32:
+		g_bDrawLookatPoint = !g_bDrawLookatPoint;
+		printf("Target: %f, %f, %f\n", g_camTarget.x, g_camTarget.y, g_camTarget.z);
+		printf("Position: %f, %f, %f\n", g_sphereCamRelPos.x, g_sphereCamRelPos.y, g_sphereCamRelPos.z);
+		break;
+	}
+
+	g_sphereCamRelPos.y = glm::clamp(g_sphereCamRelPos.y, -78.75f, -1.0f);
+	g_camTarget.y = g_camTarget.y > 0.0f ? g_camTarget.y : 0.0f;
+	g_sphereCamRelPos.z = g_sphereCamRelPos.z > 5.0f ? g_sphereCamRelPos.z : 5.0f;
+
+	glutPostRedisplay();
+}
+
+

File Tut 08 Lights on/data/ColorPassthrough.frag

+#version 330
+
+smooth in vec4 interpColor;
+
+out vec4 outputColor;
+
+void main()
+{
+	outputColor = interpColor;
+}

File Tut 08 Lights on/data/DirVertexLighting_PCN.vert

+#version 330
+
+layout(location = 0) in vec4 position;
+layout(location = 1) in vec4 diffuseColor;
+layout(location = 2) in vec3 normal;
+
+smooth out vec4 interpColor;
+
+uniform vec3 dirToLight;
+uniform vec4 lightIntensity;
+
+uniform mat4 cameraToClipMatrix;
+uniform mat4 modelToCameraMatrix;
+
+uniform mat4 normalModelToCameraMatrix;
+
+void main()
+{
+	vec4 temp = modelToWorldMatrix * position;
+	temp = worldToCameraMatrix * temp;
+	gl_Position = cameraToClipMatrix * temp;
+
+	vec4 normCamSpace = normalModelToCameraMatrix * vec4(normal, 0.0);
+	
+	float cosAngIncidence = dot(normCamSpace, dirToLight);
+	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+	
+	interpColor = lightIntensity * diffuseColor * cosAngIncidence;;
+}

File Tut 08 Lights on/data/GenCylinder.lua

+require "XmlWriter"
+require "vmath"
+
+local function GenStringFromArray(theArray)
+	local array = {" "}
+	for i, vector in ipairs(theArray) do
+		array[#array + 1] = "        " .. table.concat(vector, " ");
+	end
+	
+	return table.concat(array, "\n");
+end
+
+local positions = {};
+local colors = {};
+local topFan = {};
+local botFan = {};
+local cylStrip = {};
+
+local iSegCount, iColorRepeatCount = ...;
+iSegCount = iSegCount or 30;
+iColorRepeatCount = iColorRepeatCount or 3;
+
+local iAngle = 3.14159 * 2.0 / iSegCount;
+local iColorCycleAngle = 3.14159 * 2.0 / iColorRepeatCount;
+local highColor = vmath.vec4(0.9, 0.9, 0.9, 1.0);
+local lowColor = vmath.vec4(0.5, 0.5, 0.5, 1.0)
+
+positions[#positions + 1] = vmath.vec3(0.0, 0.5, 0.0);
+colors[#colors + 1] = vmath.vec4(1.0, 1.0, 1.0, 1.0);
+topFan[#topFan + 1] = 0;
+botFan[#botFan + 1] = (iSegCount * 2) + 1;
+
+for iSeg = 0, (iSegCount - 1), 1 do
+	local iCurrAngle = iSeg * iAngle;
+
+	positions[#positions + 1] =
+		vmath.vec3(0.5 * math.cos(iCurrAngle), 0.5, 0.5 * math.sin(iCurrAngle));
+	positions[#positions + 1] =
+		vmath.vec3(0.5 * math.cos(iCurrAngle), -0.5, 0.5 * math.sin(iCurrAngle));
+
+	local clrDist = math.mod(iCurrAngle, iColorCycleAngle) / iColorCycleAngle;
+	if(clrDist > 0.5) then
+		local interp = (clrDist - 0.5) * 2;
+		colors[#colors + 1] = (interp * highColor) +
+			((1 - interp) * lowColor);
+	else
+		local interp = clrDist * 2;
+		colors[#colors + 1] = (interp * lowColor) +
+			((1 - interp) * highColor);
+	end
+	
+	colors[#colors + 1] = colors[#colors];
+
+	topFan[#topFan + 1] = 1 + (iSeg * 2);
+	botFan[#botFan + 1] = 1 + (((iSegCount - iSeg) * 2) - 1);
+	cylStrip[#cylStrip + 1] = 1 + (iSeg * 2);
+	cylStrip[#cylStrip + 1] = 1 + (iSeg * 2) + 1;
+end
+
+topFan[#topFan + 1] = topFan[2];
+botFan[#botFan + 1] = botFan[2];
+
+cylStrip[#cylStrip + 1] = cylStrip[1];
+cylStrip[#cylStrip + 1] = cylStrip[2];
+
+positions[#positions + 1] = vmath.vec3(0.0, -0.5, 0.0);
+colors[#colors + 1] = vmath.vec4(1.0, 1.0, 1.0, 1.0);
+
+
+do
+	local writer = XmlWriter.XmlWriter("UnitCylinderTint.xml");
+	writer:AddPI("oxygen", [[RNGSchema="../../Documents/meshFormat.rnc" type="compact"]]);
+	writer:PushElement("mesh", "http://www.arcsynthesis.com/gltut/mesh");
+		writer:PushElement("attribute");
+			writer:AddAttribute("index", "0");
+			writer:AddAttribute("type", "float");
+			writer:AddAttribute("size", "3");
+			writer:AddText(GenStringFromArray(positions));
+		writer:PopElement();
+		writer:PushElement("attribute");
+			writer:AddAttribute("index", "1");
+			writer:AddAttribute("type", "float");
+			writer:AddAttribute("size", "4");
+			writer:AddText(GenStringFromArray(colors));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-fan");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(topFan, " "));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-fan");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(botFan, " "));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-strip");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(cylStrip, " "));
+		writer:PopElement();
+	writer:PopElement();
+	writer:Close();
+end
+
+do
+	local writer = XmlWriter.XmlWriter("UnitCylinder.xml");
+	writer:AddPI("oxygen", [[RNGSchema="../../Documents/meshFormat.rnc" type="compact"]]);
+	writer:PushElement("mesh", "http://www.arcsynthesis.com/gltut/mesh");
+		writer:PushElement("attribute");
+			writer:AddAttribute("index", "0");
+			writer:AddAttribute("type", "float");
+			writer:AddAttribute("size", "3");
+			writer:AddText(GenStringFromArray(positions));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-fan");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(topFan, " "));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-fan");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(botFan, " "));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "tri-strip");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(table.concat(cylStrip, " "));
+		writer:PopElement();
+	writer:PopElement();
+	writer:Close();
+end

File Tut 08 Lights on/data/GenPlane.lua

+require "XmlWriter"
+require "vmath"
+
+local function GenStringFromArray(theArray, bAsInt)
+	local array = {" "}
+	for i, vector in ipairs(theArray) do
+		local elements = vector;
+		if(bAsInt) then
+			elements = {};
+			for i, value in ipairs(vector) do
+				elements[#elements + 1] = string.format("%i", value);
+			end
+		end
+		
+		array[#array + 1] = "        " .. table.concat(vector, " ");
+	end
+	
+	return table.concat(array, "\n");
+end
+
+local positions =
+{
+	vmath.vec3(0.5, 0.0, -0.5),
+	vmath.vec3(0.5, 0.0, 0.5),
+	vmath.vec3(-0.5, 0.0, 0.5),
+	vmath.vec3(-0.5, 0.0, -0.5),
+};
+
+local indices =
+{
+	vmath.vec3(0, 1, 2),
+	vmath.vec3(0, 2, 1),
+	vmath.vec3(2, 3, 0),
+	vmath.vec3(2, 0, 3),
+};
+
+do
+	local writer = XmlWriter.XmlWriter("UnitPlane.xml");
+	writer:AddPI("oxygen", [[RNGSchema="../../Documents/meshFormat.rnc" type="compact"]]);
+	writer:PushElement("mesh", "http://www.arcsynthesis.com/gltut/mesh");
+		writer:PushElement("attribute");
+			writer:AddAttribute("index", "0");
+			writer:AddAttribute("type", "float");
+			writer:AddAttribute("size", "3");
+			writer:AddText(GenStringFromArray(positions));
+		writer:PopElement();
+		writer:PushElement("indices");
+			writer:AddAttribute("cmd", "triangles");
+			writer:AddAttribute("type", "ushort");
+			writer:AddText(GenStringFromArray(indices, true));
+		writer:PopElement();
+	writer:PopElement();
+	writer:Close();
+end

File Tut 08 Lights on/premake4.lua

+
+dofile("../framework/framework.lua")
+
+SetupSolution("Tutorial8")
+
+dofile("tutorials.lua")

File Tut 08 Lights on/tutorials.lua

+
+SetupProject("Tut 08 Basic Lighting", "Basic Lighting.cpp",
+	"data/DirVertexLighting_PCN.vert",
+	"data/ColorPassthrough.frag")
+

File framework/framework.cpp

 
 #include <string>
 #include <vector>
+#include <map>
+#include <utility>
 #include <fstream>
 #include <sstream>
 #include <functional>
 		std::vector<AttribData> dataArray;
 	};
 
+	void ProcessVAO(const TiXmlElement *pVaoElem, std::string &strName, std::vector<GLuint> &attributes)
+	{
+		if(pVaoElem->QueryStringAttribute("name", &strName) != TIXML_SUCCESS)
+			throw std::exception("Missing 'name' attribute in a 'vao' element.");
+
+		for(const TiXmlElement *pNode = pVaoElem->FirstChildElement();
+			pNode;
+			pNode = pNode->NextSiblingElement())
+		{
+			int iAttrib = -1;
+			if(pNode->QueryIntAttribute("attrib", &iAttrib) != TIXML_SUCCESS)
+				throw std::exception("Missing 'attrib' attribute in a 'source' element.");
+
+			attributes.push_back(iAttrib);
+		}
+	}
+
 	struct IndexData
 	{
 		IndexData(const TiXmlElement *pIndexElem)
 		return cmd;
 	}
 
+	typedef std::map<std::string, GLuint> VAOMap;
+	typedef VAOMap::value_type VAOMapData;
+
 	struct MeshData
 	{
 		MeshData()
 		GLuint oIndexBuffer;
 		GLuint oVAO;
 
+		VAOMap namedVAOs;
+
 		std::vector<RenderCmd> primatives;
 	};
 
 
 		std::vector<IndexData> indexData;
 
+		std::vector<std::pair<std::string, std::vector<GLuint> > > namedVaoList;
+
 		{
 			std::string strDataFilename = "data\\" + strFilename;
 			std::ifstream fileStream(strDataFilename.c_str());
 				pProcNode = pProcNode->NextSiblingElement();
 			}
 
+			while(pProcNode->ValueStr() == "vao")
+			{
+				namedVaoList.push_back(std::pair<std::string, std::vector<GLuint> >());
+				std::pair<std::string, std::vector<GLuint> > &namedVao = namedVaoList.back();
+				ProcessVAO(pProcNode, namedVao.first, namedVao.second);
+				pProcNode = pProcNode->NextSiblingElement();
+			}
+
 			for(; pProcNode; pProcNode = pProcNode->NextSiblingElement())
 			{
 				m_pData->primatives.push_back(ProcessRenderCmd(pProcNode));
 			attrib.SetupAttributeArray(attribStartLocs[iLoop]);
 		}
 
+		//Fill the named VAOs.
+		for(size_t iLoop = 0; iLoop < namedVaoList.size(); iLoop++)
+		{
+			std::pair<std::string, std::vector<GLuint> > &namedVao = namedVaoList[iLoop];
+			GLuint vao = -1;
+			glGenVertexArrays(1, &vao);
+			glBindVertexArray(vao);
+
+			for(size_t iAttribIx = 0; iAttribIx < namedVao.second.size(); iAttribIx++)
+			{
+				GLuint iAttrib = namedVao.second[iAttribIx];
+				const Attribute &attrib = attribs[iAttrib];
+				attrib.SetupAttributeArray(attribStartLocs[iAttrib]);
+			}
+
+			m_pData->namedVAOs[namedVao.first] = vao;
+		}
+
+		glBindVertexArray(0);
+
 		//Get the size of our index buffer data.
 		size_t iIndexBufferSize = 0;
 		std::vector<size_t> indexStartLocs;
 		//Create the index buffer object.
 		if(iIndexBufferSize)
 		{
+			glBindVertexArray(m_pData->oVAO);
+
 			glGenBuffers(1, &m_pData->oIndexBuffer);
 			glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pData->oIndexBuffer);
 			glBufferData(GL_ELEMENT_ARRAY_BUFFER, iIndexBufferSize, NULL, GL_STATIC_DRAW);
 					iCurrIndexed++;
 				}
 			}
+
+			VAOMap::iterator endIt = m_pData->namedVAOs.end();
+			for(VAOMap::iterator currIt = m_pData->namedVAOs.begin();
+				currIt != endIt;
+				++currIt)
+			{
+				VAOMapData &data = *currIt;
+				glBindVertexArray(data.second);
+				glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_pData->oIndexBuffer);
+			}
+
+			glBindVertexArray(0);
 		}
-
-		glBindVertexArray(0);
 	}
 
 	Mesh::~Mesh()
 		glBindVertexArray(0);
 	}
 
+	void Mesh::Render( const std::string &strMeshName )
+	{
+		VAOMap::iterator theIt = m_pData->namedVAOs.find(strMeshName);
+		if(theIt == m_pData->namedVAOs.end())
+			return;
+
+		glBindVertexArray(theIt->second);
+		std::for_each(m_pData->primatives.begin(), m_pData->primatives.end(),
+			std::mem_fun_ref(&RenderCmd::Render));
+		glBindVertexArray(0);
+	}
+
 	void Mesh::DeleteObjects()
 	{
 		glDeleteBuffers(1, &m_pData->oAttribArraysBuffer);
 		m_pData->oIndexBuffer = 0;
 		glDeleteVertexArrays(1, &m_pData->oVAO);
 		m_pData->oVAO = 0;
+
+		VAOMap::iterator endIt = m_pData->namedVAOs.end();
+		for(VAOMap::iterator currIt = m_pData->namedVAOs.begin();
+			currIt != endIt;
+			++currIt)
+		{
+			VAOMapData &data = *currIt;
+			glDeleteVertexArrays(1, &data.second);
+			data.second = 0;
+		}
 	}
 
 	float DegToRad(float fAngDeg)

File framework/framework.h

 		~Mesh();
 
 		void Render();
+		void Render(const std::string &strMeshName);
 		void DeleteObjects();
 
 	private: