Commits

Jason McKesson committed 78a3c9f Merge

Merge

Comments (0)

Files changed (8)

Documents/Illumination/Tutorial 08.xml

             </orderedlist>
             <para>This is what we do in the <phrase role="propername">Basic Lighting</phrase>
                 tutorial. It renders a cylinder above a flat plane, with a single directional light
-                source illuminating both objects.</para>
+                source illuminating both objects. One of the nice things about a cylinder is that it
+                has both curved and flat surfaces, thus making an adequate demonstration of how
+                light interacts with a surface.</para>
             <!--TODO: Show an image of the tutorial output.-->
             <para>The light is at a fixed direction; the model and camera both can be
                 rotated.</para>
                 <glossterm>directional light source</glossterm>
                 <glossdef>
                     <para>A light source that emits light along a particular direction. Every point
-                        in the scene to be rendered receives light from the same direction.</para>
+                        in the scene to be rendered receives light from the same direction. This
+                        models a very distant light source that lights the scene evenly from a
+                        single direction.</para>
                 </glossdef>
             </glossentry>
             <glossentry>

Documents/Illumination/Tutorial 09.xml

         point, light position, and surface normal must all be in the same space for this equation to
         make sense.</para>
     <section>
+        <?dbhtml filename="Tut09 Vertex Point Lighting.html" ?>
         <title>Vertex Point Lighting</title>
         <para>Thus far, we have computed the lighting equation at each vertex and interpolated the
             results across the surface of the triangle. We will continue to do so for point lights.
                 <function>normalize</function> call is just to convert it into a unit vector.</para>
     </section>
     <section>
+        <?dbhtml filename="Tut09 Interpolation.html" ?>
         <title>Interpolation</title>
         <para>As you can see, doing point lighting is quite simple. Unfortunately, the visual
             results are not.</para>
         <para>And our lighting equation is this:</para>
         <!--TODO: eq: D * I * dot(L, N)-->
         <para>If the surface normal N is being interpolated, then at any particular point on the
-            surface, we get this equation:</para>
+            surface, we get this equation for a directional light (the light direction L does not
+            change):</para>
         <!--TODO: D * I * dot(L, Na& + Nb(1-&))-->
         <para>The dot product is distributive, like scalar multiplication. So we can distribute the
             L to both sides of the dot product term:</para>
             correct.</para>
     </section>
     <section>
+        <?dbhtml filename="Tut09 Fragment Lighting.html" ?>
         <title>Fragment Lighting</title>
         <para>So, in order to deal with interpolation artifacts, we need to interpolate the actual
             light direction and normal, instead of just the results of the lighting equation. This
             space.</para>
         <para>The <phrase role="propername">Fragment Point Lighting</phrase> tutorial shows off how
             fragment lighting works.</para>
+        <para>This tutorial is controlled as before, with a few exceptions. Pressing the
+                <keycap>t</keycap> key will toggle a scale factor onto to be applied to the
+            cylinder, and pressing the <keycap>h</keycap> key will toggle between per-fragment
+            lighting and per-vertex lighting.</para>
+        <!--TODO: Show a picture of the tutorial.-->
+        <para>Much better.</para>
+        <para>The rendering code has changed somewhat, considering the use of model space for
+            lighting instead of camera space. The start of the rendering looks as follows:</para>
+        <example>
+            <title>Initial Per-Fragment Rendering</title>
+            <programlisting language="cpp">Framework::MatrixStack modelMatrix;
+modelMatrix.SetMatrix(g_mousePole.CalcMatrix());
+
+const glm::vec4 &amp;worldLightPos = CalcLightPosition();
+
+glm::vec4 lightPosCameraSpace = modelMatrix.Top() * worldLightPos;</programlisting>
+        </example>
+        <para>The new code is the last line, where we transform the world-space light into camera
+            space. This is done to make the math much easier. Since our matrix stack is building up
+            the transform from model to camera space, the inverse of this matrix would be a
+            transform from camera space to model space. So we need to put our light position into
+            camera space before we transform it by the inverse.</para>
+        <para>After doing that, it uses a variable to switch between per-vertex and per-fragment
+            lighting. This just selects which shaders to use; both sets of shaders take the same
+            uniform values, even though they use them in different program stages.</para>
+        <para>The ground plane is rendered with this code:</para>
+        <example>
+            <title>Ground Plane Per-Fragment Rendering</title>
+            <programlisting language="cpp">glUseProgram(pWhiteProgram->theProgram);
+glUniformMatrix4fv(pWhiteProgram->modelToCameraMatrixUnif, 1, GL_FALSE,
+    glm::value_ptr(modelMatrix.Top()));
+
+glm::mat4 invTransform = glm::inverse(modelMatrix.Top());
+glm::vec4 lightPosModelSpace = invTransform * lightPosCameraSpace;
+glUniform3fv(pWhiteProgram->modelSpaceLightPosUnif, 1, glm::value_ptr(lightPosModelSpace));
+
+g_pPlaneMesh->Render();
+glUseProgram(0);</programlisting>
+        </example>
+        <para>We compute the inverse matrix using <function>glm::inverse</function> and store it.
+            Then we use that to compute the model space light position and pass that to the shader.
+            Then the plane is rendered.</para>
+        <para>The cylinder is rendered using similar code. It simply does a few transformations to
+            the model matrix before computing the inverse and rendering.</para>
+        <para>The shaders are where the real action is. As with previous lighting tutorials, there
+            are two sets of shaders: one that take a per-vertex color, and one that uses a constant
+            white color. The vertex shaders that do per-vertex lighting computations should be
+            familiar:</para>
+        <example>
+            <title>Model Space Per-Vertex Lighting Vertex Shader</title>
+            <programlisting language="glsl">#version 330
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec4 inDiffuseColor;
+layout(location = 2) in vec3 normal;
+
+out vec4 interpColor;
+
+uniform vec3 modelSpaceLightPos;
+uniform vec4 lightIntensity;
+uniform vec4 ambientIntensity;
+
+uniform mat4 cameraToClipMatrix;
+uniform mat4 modelToCameraMatrix;
+
+void main()
+{
+    gl_Position = cameraToClipMatrix * (modelToCameraMatrix * vec4(position, 1.0));
+    
+    vec3 dirToLight = normalize(modelSpaceLightPos - position);
+    
+    float cosAngIncidence = dot( normal, dirToLight);
+    cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+    
+    interpColor = (lightIntensity * cosAngIncidence * inDiffuseColor) +
+        (ambientIntensity * inDiffuseColor);
+}</programlisting>
+        </example>
+        <para>The main differences between this version and the previous version are simply what one
+            would expect from the change from camera-space lighting to model space lighting. The
+            per-vertex inputs are used directly, rather than being transformed into camera space.
+            There is a second version that omits the <varname>inDiffuseColor</varname> input.</para>
+        <para>With per-vertex lighting, we have two vertex shaders:
+                <filename>ModelPosVertexLighting_PCN.vert</filename> and
+                <filename>ModelPosVertexLighting_PN.vert</filename>. With per-fragment lighting, we
+            also have two shaders: <filename>FragmentLighting_PCN.vert</filename> and
+                <filename>FragmentLighting_PN.vert</filename>. They are disappointingly
+            simple:</para>
+        <example>
+            <title>Model Space Per-Fragment Lighting Vertex Shader</title>
+            <programlisting language="glsl">#version 330
+
+layout(location = 0) in vec3 position;
+layout(location = 1) in vec4 inDiffuseColor;
+layout(location = 2) in vec3 normal;
+
+out vec4 diffuseColor;
+out vec3 vertexNormal;
+out vec3 modelSpacePosition;
+
+uniform mat4 cameraToClipMatrix;
+uniform mat4 modelToCameraMatrix;
+
+void main()
+{
+    gl_Position = cameraToClipMatrix * (modelToCameraMatrix * vec4(position, 1.0));
+    
+    modelSpacePosition = position;
+    vertexNormal = normal;
+    diffuseColor = inDiffuseColor;
+}</programlisting>
+        </example>
+        <para>Since our lighting is done in the fragment shader, there isn't much to do except pass
+            variables through and set the output clip-space position. The version that takes no
+            diffuse color just passes a <type>vec4</type> containing just 1.0.</para>
+        <para>The fragment shader is much more interesting:</para>
+        <example>
+            <title>Per-Fragment Lighting Fragment Shader</title>
+            <programlisting language="glsl">#version 330
+
+in vec4 diffuseColor;
+in vec3 vertexNormal;
+in vec3 modelSpacePosition;
+
+out vec4 outputColor;
+
+uniform vec3 modelSpaceLightPos;
+
+uniform vec4 lightIntensity;
+uniform vec4 ambientIntensity;
+
+void main()
+{
+    vec3 lightDir = normalize(modelSpaceLightPos - modelSpacePosition);
+    
+    float cosAngIncidence = dot(normalize(vertexNormal), lightDir);
+    cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+    
+    outputColor = (diffuseColor * lightIntensity * cosAngIncidence) +
+        (diffuseColor * ambientIntensity);
+}</programlisting>
+        </example>
+        <para>The math is essentially identical between the per-vertex and per-fragment case. The
+            main difference is the normalization of <varname>vertexNormal</varname>. This is
+            necessary because interpolating between two unit vectors does not mean you will get a
+            unit vector after interpolation. Indeed, interpolating the 3 components guarantees that
+            you will not get a unit vector.</para>
+        <section>
+            <title>Caught in a Lie</title>
+            <para>While this may look perfect, there is still one problem. Use the <keycombo>
+                    <keycap>Shift</keycap>
+                    <keycap>J</keycap>
+                </keycombo> key to move the light really close to the cylinder, but without putting
+                the light inside the cylinder. You should see something like this:</para>
+            <!--TODO: Picture of the cylinder with a close light.-->
+            <para>This looks like the same problem we had before. Wasn't doing lighting at the
+                fragment level supposed to fix this?</para>
+            <para>Actually, this is a completely different problem. And it is one that is
+                essentially impossible to solve. Well, not without changing our geometry.</para>
+            <para>The source of the problem is this: the light finally caught us in our lie.
+                Remember what we are actually doing. We are not rendering a true cylinder; we are
+                rendering a group of flat triangles that we attempt to make
+                    <emphasis>appear</emphasis> to be a round cylinder. We are rendering a polygonal
+                approximation of a cylinder, then using our lighting computations to make it seem
+                like the faceted shape is really round.</para>
+            <para>This works quite well when the light is fairly far from the surface. But when the
+                light is very close, as it is here, it reveals our fakery for what it is.
+                Why?</para>
+            <para>Let's take a top-down view of our cylinder approximation, but let's also draw what
+                the actual cylinder would look like:</para>
+            <!--TODO: Show a diagram of a faceted cylinder inscribed within a circle.-->
+            <para>Now, consider our lighting equation at a particular point on the fake
+                cylinder:</para>
+            <!--TODO: Extend the previous diagram with two lights, one close and one far. Also show a point on the fake
+cylinder, and a normal. And show the corresponding point/normal.-->
+            <para>The problem comes from the difference between the actual position being rendered
+                and the corresponding position on the circle that has the normal that we are
+                pretending our point has. When the light is somewhat distant, the difference between
+                the actual light direction we use, and the one we would use on a real cylinder is
+                pretty small. But when the light is very close, the difference is
+                substantial.</para>
+            <para>The lighting computations are correct for the vertices near the edges of a
+                triangle. But the farther from the edges you get, the more incorrect it
+                becomes.</para>
+            <para>The key point is that there isn't much we can do about this problem. The only
+                solution is to add more vertices to the approximation of the cylinder. It should
+                also be noted that adding more triangles would also make per-vertex lighting look
+                more correct. Thus making the whole exercise in using per-fragment lighting somewhat
+                pointless; if the mesh is fine enough so that each vertex effectively becomes a
+                fragment, then there is no difference between them.</para>
+            <para>For our simple case, adding triangles is easy, since a cylinder is a
+                mathematically defined object. For a complicated case like a human being, this is
+                usually rather more difficult. It also takes up more performance, since there are
+                more vertices, more executions of our (admittedly simple) vertex shader, and more
+                triangles rasterized or discarded for being back-facing.</para>
+        </section>
     </section>
     <section>
-        <title>Distance and Points</title>
-        <para/>
+        <?dbhtml filename="Tut09 Distant Points of Light.html" ?>
+        <title>Distant Points of Light</title>
+        <para>There is another issue with our current example. Use the <keycap>i</keycap> key to
+            raise the light up really high. Notice how bright all of the upwardly-facing surfaces
+            get:</para>
+        <!--TODO: Show a picture of a high light's effect on the surface.-->
+        <para>You probably have no experience with this in real life. Holding a light farther from
+            the surface in reality does not make the light brighter. So obviously something is
+            happening in reality that our simple lighting model is not accounting for.</para>
+        <para>In reality, lights emit a certain quantity of light per unit time. For a point-like
+            light such as a light bulb, it emits this light radially, in all directions. The farther
+            from the light source one gets, the more area that this must ultimately cover.</para>
+        <para>Light is essentially a wave. The farther away from the source of the wave, the less
+            intense the wave is. For light, this is called <glossterm>light
+            attenuation.</glossterm></para>
+        <para>Our model does not include light attenuation, so let's fix that.</para>
+        <para>Attenuation is a well-understood physical phenomenon. In the absence of other factors
+            (atmospheric light scattering, etc), the light intensity varies with the inverse of the
+            square of the distance. An object 2 units away from the light feels like with one-fourth
+            the intensity. So our equation for light attenuation is as follows:</para>
+        <!--TODO: An equation for light attenuation. AttenLight = Light * (1 / (1.0 + k * r^2))-->
+        <para>There is a constant in the equation, which is used for unit correction. Of course, we
+            can (and will) use it as a fudge factor to make things look right.</para>
+        <para>The constant can take on a physical meaning. If you want to specify a distance at
+            which half of the light intensity is lost, <varname>k</varname> simply becomes:</para>
+        <!--TODO: An equation for k = 1/khalf^2-->
+        <para>However physically correct this equation is, it has certain drawbacks. And this brings
+            us back to the light intensity problem we touched on earlier.</para>
+        <para>Since our lights are clamped on the [0, 1] range, it doesn't take much distance from
+            the light before the contribution from a light to become effectively nil. In reality,
+            with an unclamped range, we could just pump the light's intensity up to realistic
+            values. But we're working with a clamped range.</para>
+        <para>Therefore, a more common attenuation scheme is to use the inverse of just the distance
+            instead of the inverse of the distance squared:</para>
+        <!--TODO: Light equation with 1/(1.0 + kr)-->
+        <para>It looks brighter for more distant lights. It isn't physically correct, but so much
+            about our rendering is at this point that it won't be noticed much.</para>
+        <section>
+            <title>Reverse of the Transform</title>
+            <para>However, there is a problem. We previously did per-fragment lighting in model
+                space. And while this is a perfectly useful space to do lighting in, model space is
+                not world space.</para>
+            <para>We want to specify the attenuation constant factor in terms of world space
+                distances. But we aren't dealing in world space; we are in model space. And model
+                space distances are, naturally, in model space, which may well be scaled relative to
+                world space. Here, any kind of scale in the model-to-world transform is a problem,
+                not just non-uniform scales. Although if there was a uniform scale, we could apply
+                theoretically apply the scale to the attenuation constant.</para>
+            <para>So now we cannot use model space. Fortunately, camera space is a space that has
+                the same scale as world space, just with a rotation/translation applied to it. So we
+                can do our lighting in that space.</para>
+            <para>Doing it in camera space requires computing a camera space position and passing it
+                to the fragment shader to be interpolated. And while we could do this, that's not
+                clever enough. Isn't there some way to get around that?</para>
+            <para>Yes, there is. Recall <varname>gl_FragCoord</varname>, an intrinsic value given to
+                every fragment shader. It represents the location of the fragment in window space.
+                So instead of transforming from model space to camera space, we will transform from
+                window space to camera space.</para>
+            <note>
+                <para>The use of this reverse-transformation technique here should not be taken as a
+                    suggestion to use it in all, or even most cases like this. In all likelihood, it
+                    will be much slower than just passing the camera space position to the fragment
+                    shader. It is here primarily for demonstration purposes; it is useful for other
+                    techniques that we will see in the future.</para>
+            </note>
+            <para>The sequence of transformations that take a position from camera space to window
+                space is as follows:</para>
+            <!--TODO: The sequence of transforms to get gl_FragCoord from camera space.-->
+            <para>Therefore, given <varname>gl_FragCoord</varname>, we will need to perform the
+                reverse of these:</para>
+            <!--TODO: Invert the sequence and the functions.-->
+            <para>In order for our fragment shader to perform this transformation, it must be given
+                the following values:</para>
+            <itemizedlist>
+                <listitem>
+                    <para>The inverse projection matrix.</para>
+                </listitem>
+                <listitem>
+                    <para>The viewport width/height.</para>
+                </listitem>
+                <listitem>
+                    <para>The depth range.</para>
+                </listitem>
+            </itemizedlist>
+        </section>
+        <section>
+            <title>Applied Attenuation</title>
+            <para>The <phrase role="propername">Fragment Attenuation</phrase> tutorial performs
+                per-fragment attenuation, both with linear and quadratic attenuation.</para>
+            <!--TODO: Show a picture of the tutorial.-->
+            <para>This tutorial controls as before, with the following exceptions. The
+                    <keycap>O</keycap> and <keycap>U</keycap> keys increase and decrease the
+                attenuation constant. However, remember that decreasing the constant makes the
+                attenuation less, which makes the light appear <emphasis>brighter</emphasis> at a
+                particular distance. Using the shift key in combination with them will
+                increase/decrease the attenuation by smaller increments. The <keycap>H</keycap> key
+                swaps between the linear and quadratic interpolation functions.</para>
+            <para>The drawing code is mostly the same as we saw in the per-vertex point light
+                tutorial, since both this and that one perform lighting in camera space. The vertex
+                shader is also nothing new; passes the vertex normal and color to the fragment
+                shader. The vertex normal is multiplied by the normal matrix, which allows us to use
+                non-uniform scaling.</para>
+        </section>
+        <section>
+            <title>New Uniform Types</title>
+            <para>The more interesting part is the fragment shader. The definitions are not much
+                changed from the last one, but there have been some additions:</para>
+            <example>
+                <title>Light Attenuation Fragment Shader Definitions</title>
+                <programlisting language="glsl">uniform mat4 clipToCameraMatrix;
+uniform ivec2 windowSize;
+uniform vec2 depthRange;
+
+uniform float lightAttenuation;
+uniform bool bUseRSquare;</programlisting>
+            </example>
+            <para>The first three lines are the information we need to perform the
+                previously-discussed reverse-transformation, so that we can turn
+                    <varname>gl_FragCoord</varname> into a camera-space position. Notice that the
+                    <varname>windowSize</varname> uses a new type: <type>ivec2</type>. This is a
+                2-dimensional vector of integers.</para>
+            <para>Thus far, we have only modified uniforms of floating-point type. All of the vector
+                uploading functions we have used have been of the forms
+                    <function>glUniform#f</function> or <function>glUniform#fv</function>, where the
+                # is a number 1-4. The <quote>v</quote> represents a vector version, which takes a
+                pointer instead of 1-4 parameters.</para>
+            <para>Uploading to an integer uniform uses functions of the form
+                    <function>glUniform#i</function> and <function>glUniform#iv</function>. So the
+                code we use to set the window size uniform is this:</para>
+            <programlisting language="cpp">glUniform2i(g_FragWhiteDiffuseColor.windowSizeUnif, w, h);</programlisting>
+            <para>The <varname>w</varname> and <varname>h</varname> values are the width and height
+                passed to the <function>reshape</function> function. That is where this line of code
+                is, and that is where we set both the clip-to-camera matrix and the window size. The
+                    <varname>depthRange</varname> is set in the initialization code, since it never
+                changes.</para>
+            <para>The <varname>lightAttenuation</varname> uniform is just a float, but
+                    <varname>bUseRSquare</varname> uses a new type: boolean.</para>
+            <para>GLSL has the <type>bool</type> type just like C++ does. The
+                    <literal>true</literal> and <literal>false</literal> values work just like C++'s
+                equivalents. Where they differ is that GLSL also has vectors of bools, called
+                    <type>bvec#</type>, where the # can be 2, 3, or 4. We don't use that here, but
+                it is important to note.</para>
+            <para>OpenGL's API, however, is still a C API. And C (at least, pre-C99) has no
+                    <type>bool</type> type. Uploading a boolean value to a shader looks like
+                this:</para>
+            <programlisting language="cpp">glUniform1i(g_FragWhiteDiffuseColor.bUseRSquareUnif, g_bUseRSquare ? 1 : 0);</programlisting>
+            <para>The integer form of uniform uploading is used, but the floating-point form could
+                be allowed as well. The number 0 represents false, and any other number is
+                true.</para>
+        </section>
+        <section>
+            <title>Functions in GLSL</title>
+            <para>For the first time, we have a shader complex enough that splitting it into
+                different functions makes sense. So we do that. The first function is one that
+                computes the camera-space position:</para>
+            <example>
+                <title>Window to Camera Space Function</title>
+                <programlisting language="glsl">vec3 CalcCameraSpacePosition()
+{
+    vec3 ndcPos;
+    ndcPos.xy = ((gl_FragCoord.xy / windowSize.xy) * 2.0) - 1.0;
+    ndcPos.z = 2.0 * (gl_FragCoord.z - depthRange.x - depthRange.y) / (depthRange.y - depthRange.x);
+    
+    vec4 clipPos;
+    clipPos.w = 1.0f / gl_FragCoord.w;
+    clipPos.xyz = ndcPos.xyz * clipPos.w;
+    
+    return vec3(clipToCameraMatrix * clipPos);
+}</programlisting>
+            </example>
+            <para>Not unsurprisingly, GLSL functions are defined much like C and C++
+                functions.</para>
+            <para>The first three lines compute the position in normalized device coordinates.
+                Notice that the computation of the X and Y coordinates is simplified from the
+                original function. This is because our viewport always sets the lower-left position
+                of the viewport to (0, 0). This is what you get when you plug zeros into that
+                equation.</para>
+            <para>From there, the clip-space position is computed as previously shown. Then the
+                result is multiplied through the clip-to-camera matrix, and that vector is returned
+                to the caller.</para>
+            <para>This is a simple function that uses only uniforms to compute a value. It takes no
+                inputs and outputs. The second function is not quite as simple.</para>
+            <example>
+                <title>Light Intensity Application Function</title>
+                <programlisting language="glsl">vec4 ApplyLightIntensity(in vec3 cameraSpacePosition, out vec3 lightDirection)
+{
+    vec3 lightDifference =  cameraSpaceLightPos - cameraSpacePosition;
+    float lightDistanceSqr = dot(lightDifference, lightDifference);
+    lightDirection = lightDifference * inversesqrt(lightDistanceSqr);
+    
+    float distFactor = bUseRSquare ? lightDistanceSqr : sqrt(lightDistanceSqr);
+    
+    return lightIntensity * (1 / ( 1.0 + lightAttenuation * distFactor));
+}</programlisting>
+            </example>
+            <para>The function header looks rather different from the standard C/C++ function
+                definition syntax. Parameters to GLSL functions are designated as being inputs,
+                outputs, or inputs and outputs.</para>
+            <para>Parameters designated with <literal>in</literal> are input parameters. Functions
+                can change these values, but they will have no effect on the variable or expression
+                used in the function call. So any changes . This is much like the default in C/C++,
+                where parameter changes are local. Naturally, this is the default with GLSL
+                parameters if you do not specify a qualifier.</para>
+            <para>Parameters designated with <literal>out</literal> can be written to, and its value
+                will be returned to the calling function. These are similar to non-const reference
+                parameter types in C++. And just as with reference parameters, the caller of a
+                function must call it with a real variable (called an <quote>l-value</quote>). And
+                this variable must be a variable that can be <emphasis>changed</emphasis>, so you
+                cannot pass a uniform or shader stage input value as this parameter.</para>
+            <para>However, the initial value of parameters declared as outputs is
+                    <emphasis>not</emphasis> initialized from the calling function. This means that
+                the initial value is uninitialized and therefore undefined (ie: it could be
+                anything). Because of this, you can pass shader stage outputs as
+                    <literal>out</literal> parameters. Shader stage output variables can be written
+                to, but <emphasis>never</emphasis> read from.</para>
+            <para>Parameters designated as <literal>inout</literal> will have its value initialized
+                by the caller and have the final value returned to the caller. These are exactly
+                like non-const reference parameters in C++. The main difference is that the value is
+                initialized with the one that the user passed in, which forbids the passing of
+                shader stage outputs as <literal>inout</literal> parameters.</para>
+            <para>This function is semi-complex, as an optimization. Previously, our functions
+                simply normalized the difference between the vertex position and the light position.
+                In computing the attenuation, we need the distance between the two. And the process
+                of normalization computes the distance. So instead of calling the GLSL function to
+                normalize the direction, we do it ourselves, so that the distance is not computed
+                twice (once in the GLSL function and once for us).</para>
+            <para>The second line performs a dot product with the same vector. Remember that the dot
+                product between two vectors is the cosine of the angle between them, multiplied by
+                each of the lengths of the vectors. Well, the angle between a vector and itself is
+                zero, and the cosine of zero is always one. So what you get is just the length of
+                the two vectors times one another. And since the vectors are the same, the lengths
+                are the same. Thus, the dot product of a vector with itself is the square of its
+                length.</para>
+            <para>To normalize a vector, we must divide the vector by it's length. And the length of
+                    <varname>lightDifference</varname> is the square root of
+                    <varname>lightDistanceSqr</varname>. The <function>inversesqrt</function>
+                computes 1 / the square root of the given value, so all we need to do is multiply
+                this with the <varname>lightDifference</varname> to get the light direction as a
+                normalized vector. This value is written to our output variable.</para>
+            <para>The next line computes our lighting term. Notice the use of the ?: operator. This
+                works just like in C/C++. If we are using the square of the distance, that's what we
+                store. Otherwise we get the square-root and store that.</para>
+            <note>
+                <para>The assumption in using ?: here is that only one or the other of the two
+                    expressions will be evaluated. That's why the expensive call to
+                        <function>sqrt</function> is done here. However, this may not be the case.
+                    It is entirely possible (and quite likely) that the shader will always evaluate
+                        <emphasis>both</emphasis> expressions and simply store one value or the
+                    other as needed. So do not rely on such conditional logic to save
+                    performance.</para>
+            </note>
+            <para>After that, things proceed as expected.</para>
+            <para>Making these separate functions makes the main function look almost identical to
+                prior versions:</para>
+            <example>
+                <title>Main Light Attenuation</title>
+                <programlisting language="glsl">void main()
+{
+    vec3 cameraSpacePosition = CalcCameraSpacePosition();
+    
+    vec3 lightDir = vec3(0.0);
+    vec4 attenIntensity = ApplyLightIntensity(cameraSpacePosition, lightDir);
+    
+    float cosAngIncidence = dot(normalize(vertexNormal), lightDir);
+    cosAngIncidence = clamp(cosAngIncidence, 0, 1);
+    
+    outputColor = (diffuseColor * attenIntensity * cosAngIncidence) +
+        (diffuseColor * ambientIntensity);
+}</programlisting>
+            </example>
+            <para>Function calls appear very similar to C/C++, with the exceptions about parameters
+                noted before. The camera-space position is determined. Then the light intensity,
+                modified by attenuation, is computed. From there, things proceed as before.</para>
+            <sidebar>
+                <title>Alternative Attenuation</title>
+                <para>As nice as these somewhat-realistic attenuation schemes are, it is often
+                    useful to model light attenuation in a very different way. This is in no way
+                    physically accurate, but it can look reasonably good.</para>
+                <para>We simply do linear interpolation based on the distance. When the distance is
+                    0, the light has full intensity. When the distance is beyond a given distance,
+                    the maximum light range (which varies per-light), the intensity is 1.</para>
+                <para>Note that <quote>reasonably good</quote> depends on your needs. The closer you
+                    get in other way to providing physically accurate lighting, the closer you get
+                    to photorealism, the less you can rely on less accurate phenomena. It does no
+                    good to implement a complicated sub-surface scattering lighting model that
+                    includes Fresnel factors and so forth, while simultaneously using a simple
+                    interpolation lighting attenuation model.</para>
+            </sidebar>
+        </section>
     </section>
     <section>
         <?dbhtml filename="Tut09 In Review.html" ?>
         <title>In Review</title>
-        <para/>
+        <para>In this tutorial, you have learned the following:</para>
+        <itemizedlist>
+            <listitem>
+                <para>Point lights are lights that have a position within the world, radiating light
+                    equally in all directions. The light direction at a particular point on the
+                    surface must be computed using the position at that point and the position of
+                    the light.</para>
+            </listitem>
+            <listitem>
+                <para>Attempting to perform per-vertex lighting computations with point lights leads
+                    to artifacts.</para>
+            </listitem>
+            <listitem>
+                <para>Lighting can be computed per-fragment by passing the fragment's position in an
+                    appropriate space.</para>
+            </listitem>
+            <listitem>
+                <para>Lighting can be computed in model space.</para>
+            </listitem>
+            <listitem>
+                <para>Point lights have a falloff with distance, called attenuation. Not performing
+                    this can cause odd effects, where a light appears to be brighter when it moves
+                    farther from a surface. Light attenuation varies with the inverse of the square
+                    of the distance, but other attenuation models can be used.</para>
+            </listitem>
+            <listitem>
+                <para>Fragment shaders can compute the camera space position of the fragment in
+                    question by using <varname>gl_FragCoord</varname> and a few uniform variables
+                    holding information about the camera to window space transform.</para>
+            </listitem>
+            <listitem>
+                <para>GLSL can have integer vectors, boolean values, and functions.</para>
+            </listitem>
+        </itemizedlist>
         <section>
             <title>Further Study</title>
             <para>Try doing these things with the given programs.</para>
                         the same stack, so long as the pop function puts the two matrices in the
                         proper places.</para>
                 </listitem>
+                <listitem>
+                    <para>Implement the alternative attenuation described at the end of the section
+                        on attenuation.</para>
+                </listitem>
             </itemizedlist>
         </section>
+        <section>
+            <title>GLSL Functions of Note</title>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>vec <function>inversesqrt</function></funcdef>
+                    <paramdef>vec <parameter>x</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+            <para>This function computes 1 / the square root of <varname>x</varname>. This is a
+                component-wise computation, so vectors may be used. The return value will have the
+                same type as <varname>x</varname>.</para>
+            <funcsynopsis>
+                <funcprototype>
+                    <funcdef>vec <function>sqrt</function></funcdef>
+                    <paramdef>vec <parameter>x</parameter></paramdef>
+                </funcprototype>
+            </funcsynopsis>
+            <para>This function computes the square root of <varname>x</varname>. This is a
+                component-wise computation, so vectors may be used. The return value will have the
+                same type as <varname>x</varname>.</para>
+        </section>
     </section>
     <section>
         <?dbhtml filename="Tut09 Glossary.html" ?>
         <title>Glossary</title>
         <glosslist>
             <glossentry>
-                <glossterm>point light</glossterm>
+                <glossterm>point light source</glossterm>
                 <glossdef>
-                    <para/>
+                    <para>A light source that emits light from a particular location in the world.
+                        The light is emitted in all directions evenly.</para>
                 </glossdef>
             </glossentry>
             <glossentry>
                 <glossterm>fragment lighting</glossterm>
                 <glossdef>
-                    <para/>
+                    <para>Evaluating the lighting equation at every fragment.</para>
+                    <para>This is also called Phong shading, in contrast with Goroud shading, but
+                        this name has fallen out of favor due to similarities with names for other
+                        lighting models.</para>
+                </glossdef>
+            </glossentry>
+            <glossentry>
+                <glossterm>light attenuation</glossterm>
+                <glossdef>
+                    <para>The decrease of the intensity of light with distance from the source of
+                        that light.</para>
                 </glossdef>
             </glossentry>
         </glosslist>

Documents/Tutorials.xml

                 light/surface models and explain how to implement them.</para>
         </partintro>
         <xi:include href="Illumination/tutorial 08.xml"/>
+        <xi:include href="Illumination/tutorial 09.xml"/>
     </part>
     <part>
         <?dbhtml filename="Texturing.html" dir="Texturing" ?>

Tut 09 Plane Lights/Fragment Attenuation.cpp

 	glClearDepth(1.0f);
 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-	if(g_pPlaneMesh && g_pCylinderMesh)
+	if(g_pPlaneMesh && g_pCylinderMesh && g_pCubeMesh)
 	{
 		Framework::MatrixStack modelMatrix;
 		modelMatrix.SetMatrix(g_mousePole.CalcMatrix());
 void keyboard(unsigned char key, int x, int y)
 {
 	bool bChangedAtten = false;
+	bool bChangedType = false;
 	switch (key)
 	{
 	case 27:
 	case 'L': g_fLightRadius += 0.05f; break;
 	case 'J': g_fLightRadius -= 0.05f; break;
 
-	case 'o': g_fLightAttenuation += 1.0f; bChangedAtten = true; break;
-	case 'u': g_fLightAttenuation -= 1.0f; bChangedAtten = true; break;
-	case 'O': g_fLightAttenuation += 0.1f; bChangedAtten = true; break;
-	case 'U': g_fLightAttenuation -= 0.1f; bChangedAtten = true; break;
+	case 'o': g_fLightAttenuation *= 1.5f; bChangedAtten = true; break;
+	case 'u': g_fLightAttenuation /= 1.5f; bChangedAtten = true; break;
+	case 'O': g_fLightAttenuation *= 1.1f; bChangedAtten = true; break;
+	case 'U': g_fLightAttenuation /= 1.1f; bChangedAtten = true; break;
 
 	case 'y': g_bDrawLight = !g_bDrawLight; break;
 	case 't': g_bScaleCyl = !g_bScaleCyl; break;
-	case 'h': g_bUseRSquare = !g_bUseRSquare; break;
+
+	case 'h':
+		g_bUseRSquare = !g_bUseRSquare;
+		if(g_bUseRSquare)
+			printf("Inverse Squared Attenuation\n");
+		else
+			printf("Plain Inverse Attenuation\n");
+		break;
 	}
 
 	if(g_fLightRadius < 0.2f)

Tut 09 Plane Lights/Fragment Point Lighting.cpp

 	glClearDepth(1.0f);
 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-	if(g_pPlaneMesh && g_pCylinderMesh)
+	if(g_pPlaneMesh && g_pCylinderMesh && g_pCubeMesh)
 	{
 		Framework::MatrixStack modelMatrix;
 		modelMatrix.SetMatrix(g_mousePole.CalcMatrix());
 				if(g_bScaleCyl)
 					modelMatrix.Scale(1.0f, 1.0f, 0.2f);
 
+				glm::mat4 invTransform = glm::inverse(modelMatrix.Top());
+				glm::vec4 lightPosModelSpace = invTransform * lightPosCameraSpace;
+
 				if(g_bDrawColoredCyl)
 				{
 					glUseProgram(pVertColorProgram->theProgram);
 					glUniformMatrix4fv(pVertColorProgram->modelToCameraMatrixUnif, 1, GL_FALSE,
 						glm::value_ptr(modelMatrix.Top()));
 
-					glm::mat4 invTransform = glm::inverse(modelMatrix.Top());
-					glm::vec4 lightPosModelSpace = invTransform * lightPosCameraSpace;
 					glUniform3fv(pVertColorProgram->modelSpaceLightPosUnif, 1, glm::value_ptr(lightPosModelSpace));
 
 					g_pCylinderMesh->Render("tint");
 					glUniformMatrix4fv(pWhiteProgram->modelToCameraMatrixUnif, 1, GL_FALSE,
 						glm::value_ptr(modelMatrix.Top()));
 
-					glm::mat4 invTransform = glm::inverse(modelMatrix.Top());
-					glm::vec4 lightPosModelSpace = invTransform * lightPosCameraSpace;
 					glUniform3fv(pWhiteProgram->modelSpaceLightPosUnif, 1, glm::value_ptr(lightPosModelSpace));
 
 					g_pCylinderMesh->Render("flat");
 
 	case 'y': g_bDrawLight = !g_bDrawLight; break;
 	case 't': g_bScaleCyl = !g_bScaleCyl; break;
+	case 'h': g_bUseFragmentLighting = !g_bUseFragmentLighting; break;
 	}
 
 	if(g_fLightRadius < 0.2f)

Tut 09 Plane Lights/Vertex Point Lighting.cpp

 	glClearDepth(1.0f);
 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
 
-	if(g_pPlaneMesh && g_pCylinderMesh)
+	if(g_pPlaneMesh && g_pCylinderMesh && g_pCubeMesh)
 	{
 		Framework::MatrixStack modelMatrix;
 		modelMatrix.SetMatrix(g_mousePole.CalcMatrix());

Tut 09 Plane Lights/data/FragLightAtten.frag

 	return vec3(clipToCameraMatrix * clipPos);
 }
 
-vec4 CalcLightIntensity(in vec3 cameraSpacePosition)
+vec4 ApplyLightIntensity(in vec3 cameraSpacePosition, out vec3 lightDirection)
 {
-	float lightDistance = length(cameraSpaceLightPos - cameraSpacePosition);
+	vec3 lightDifference =  cameraSpaceLightPos - cameraSpacePosition;
+	float lightDistanceSqr = dot(lightDifference, lightDifference);
+	lightDirection = lightDifference * inversesqrt(lightDistanceSqr);
 	
-	float distFactor = bUseRSquare ? lightDistance * lightDistance : lightDistance;
+	float distFactor = bUseRSquare ? lightDistanceSqr : sqrt(lightDistanceSqr);
 
 	return lightIntensity * (1 / ( 1.0 + lightAttenuation * distFactor));
 }
 void main()
 {
 	vec3 cameraSpacePosition = CalcCameraSpacePosition();
-	vec3 lightDir = normalize(cameraSpaceLightPos - cameraSpacePosition);
 
-	float cosAngIncidence = dot(normalize(vertexNormal),
-		normalize(lightDir));
+	vec3 lightDir = vec3(0.0);
+	vec4 attenIntensity = ApplyLightIntensity(cameraSpacePosition, lightDir);
+
+	float cosAngIncidence = dot(normalize(vertexNormal), lightDir);
 	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
 	
-	outputColor = (diffuseColor * CalcLightIntensity(cameraSpacePosition) * cosAngIncidence) +
+	outputColor = (diffuseColor * attenIntensity * cosAngIncidence) +
 		(diffuseColor * ambientIntensity);
 }

Tut 09 Plane Lights/data/FragmentLighting.frag

 {
 	vec3 lightDir = normalize(modelSpaceLightPos - modelSpacePosition);
 
-	float cosAngIncidence = dot(normalize(vertexNormal),
-		normalize(lightDir));
+	float cosAngIncidence = dot(normalize(vertexNormal), lightDir);
 	cosAngIncidence = clamp(cosAngIncidence, 0, 1);
 	
 	outputColor = (diffuseColor * lightIntensity * cosAngIncidence) +