Commits

Jason McKesson committed a709e3c

Tutorial 9 text done.

Comments (0)

Files changed (3)

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

         <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>
             unit vector after interpolation. Indeed, interpolating the 3 components guarantees that
             you will not get a unit vector.</para>
         <section>
-            <title>Close Lights</title>
-            <para>While this may look perfect, there are still a few problem spots. Use the <keycombo>
+            <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>
+                    <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.-->
                 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: we're lying and the light finally caught us.
-                Remember what we are actually doing. We are not rendering a cylinder; we are
+            <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
 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 away, the difference that this
-                creates in the direction to the light is pretty small. But when the light is very
-                close, the difference is substantial.</para>
+                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. For our
-                simple case, this is easy. For a complicated case like a human being, this is
+                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>
             <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, distance stops
-                making sense in model space.</para>
+                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 is a problem, not just non-uniform scales.
-                Although if there was a uniform scale, we could apply theoretically apply it to the
-                attenuation constant.</para>
+                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>However, that is not clever enough. Doing it in camera space requires computing a
-                camera space position and passing it to the fragment shader to be interpolated.
-                Isn't there some way to get around that?</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 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, though we will eventually get to use it in
-                    a more legitimate way.</para>
+                <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 for camera to window coordinates.-->
+            <!--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>This means that our fragment shader needs to be given each of those values.</para>
+            <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>
                     <para>The depth range.</para>
                 </listitem>
             </itemizedlist>
-            <para/>
+        </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>
                         proper places.</para>
                 </listitem>
                 <listitem>
-                    <para>Since we're dealing with fake lighting anyway, we can make attenuation
-                        behave in a way that is useful, but doesn't even come close to reality. We
-                        can do a linear interpolation between a distance of zero, where the
-                        intensity is full, and a given distance, where the intensity is 0. So
-                        instead of a smooth falloff, we have a linear decrease to a distance where
-                        the light is no longer applied to points. Implement this.</para>
+                    <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/>
-                </glossdef>
-            </glossentry>
-            <glossentry>
-                <glossterm/>
-                <glossdef>
-                    <para/>
+                    <para>The decrease of the intensity of light with distance from the source of
+                        that light.</para>
                 </glossdef>
             </glossentry>
         </glosslist>

Tut 09 Plane Lights/data/FragLightAtten.frag

 
 vec4 ApplyLightIntensity(in vec3 cameraSpacePosition, out vec3 lightDirection)
 {
-	lightDirection =  cameraSpaceLightPos - cameraSpacePosition;
-	float lightDistanceSqr = dot(lightDirection, lightDirection);
-	lightDirection = lightDirection * inversesqrt(lightDistanceSqr);
+	vec3 lightDifference =  cameraSpaceLightPos - cameraSpacePosition;
+	float lightDistanceSqr = dot(lightDifference, lightDifference);
+	lightDirection = lightDifference * inversesqrt(lightDistanceSqr);
 	
 	float distFactor = bUseRSquare ? lightDistanceSqr : sqrt(lightDistanceSqr);