Jason McKesson avatar Jason McKesson committed 39e880e

Tut17: Double projection text done.
Also a couple of code fixes.

Comments (0)

Files changed (5)

Documents/Texturing/Tutorial 17.xml

 <chapter xmlns="http://docbook.org/ns/docbook" xmlns:xi="http://www.w3.org/2001/XInclude"
     xmlns:xlink="http://www.w3.org/1999/xlink" version="5.0">
     <?dbhtml filename="Tutorial 17.html" ?>
-    <title>Variable Lighting</title>
+    <title>Spotlight on Textures</title>
     <para>Previously, we have seen textures used to vary surface parameters. But we can use textures
         to vary something else: light intensity. In this way, we can simulate light sources who's
         intensity changes with something more than just distance from the light.</para>
     <section>
         <?dbhtml filename="Tut17 Post Projection Space.html" ?>
         <title>Post-Projection Space</title>
-        <para>Before we can look at how to use a texture to make a flashlight, we need to understand
-            a special piece of mathematics. We need to revisit perspective projection. Specifically,
-            we need to look at what happens when transforming after a projection operation.</para>
-        <para/>
-        <para/>
+        <para>Before we can look at how to use a texture to make a flashlight, we need to make a
+            short digression. Perspective projection will be an important part of how we make a
+            texture into a flashlight, so we need to revisit perspective projection. Specifically,
+            we need to look at what happens when transforming after a perspective projection
+            operation.</para>
+        <para>Open up the project called <phrase role="propername">Double Projection</phrase>. It
+            renders four objects, using various textures, in a scene with a single directional light
+            and a green point light.</para>
+        <!--TODO: Add image of Double Projection tutorial.-->
+        <para>This tutorial displays two images of the same scene. The image on the left is the view
+            of the scene from one camera, while the image on the right is the view of the scene from
+            another camera. The difference between the two cameras is mainly in where the camera
+            transformation matrices are applied.</para>
+        <para>The left camera works normally. It is controlled by the left mouse button, the mouse
+            wheel, and the WASD keys, as normal. The right camera however provides the view
+            direction that is applied after the perspective projection matrix. The sequence of
+            transforms thus looks like this: Model -> Left Camera -> Projection -> Right Camera. The
+            right camera is controlled by the right mouse button; only orientation controls work on
+            it.</para>
+        <para>The idea is to be able to look at the shape of objects in normalized device coordinate
+                (<acronym>NDC</acronym>) space after a perspective projection. NDC space is a [-1,
+            1] box centered at the origin; by rotating objects in NDC space, you will be able to see
+            what those objects look like from a different perspective. Pressing the
+                <keycap>SpaceBar</keycap> will reset the right camera back to a neutral view.</para>
+        <para>Note that post-perspective projection space objects are very distorted, particularly
+            in the Z direction. Also, recall one of the fundamental tricks of the perspective
+            projection: it rescales objects based on their Z-distance from the camera. Thus, objects
+            that are farther away are physically smaller in NDC space than closer ones. Thus,
+            rotating NDC space around will produce results that are not intuitively what you might
+            expect and may be very disorienting at first.</para>
+        <para>For example, if we rotate the right camera to an above view, relative to whatever the
+            left camera is, we see that all of the objects seems to shrink down into a very small
+            width.</para>
+        <!--TODO: Add image of Double Projection, with right camera from the top, relatively far from the scene.-->
+        <para>This is due to the particulars of the perspective projection's work on the Z
+            coordinate. The Z coordinate in NDC space is the result of the clip-space Z divided by
+            the negative of the camera-space Z. This forces it into the [-1, 1] range, but the
+            clip-space Z also is affected by the zNear and zFar of the perspective matrix. The wider
+            these are, the more narrowly the Z is compressed. Objects farther from the camera are
+            compressed into smaller ranges of Z; we saw this in our look at the effect of the camera
+            Z-range on precision. Close objects use more of the [-1, 1] range than those farther
+            away.</para>
+        <para>This can be seen by moving the left camera close to an object. The right camera, from
+            a top-down view, has a much thicker view of that object in the Z direction.</para>
+        <!--TODO: Add image of Double Projection, with the right camera from the top, really close to an object.
+This will let us see that the object takes up a large part of the [-1, 1] Z range.-->
+        <para>Pressing the <keycap>Y</keycap> key will toggle depth clamping in the right camera.
+            This can explain some of the unusual things that will be seen there. Sometimes the wrong
+            objects will appear on top of each other; when this happens, it is almost always due to
+            a clamped depth.</para>
+        <para>The reason why depth clamping matters so much in the right screen is obvious if you
+            think about it. NDC space is a [-1, 1] square. But that is not the NDC space we actually
+            render to. We are rendering to a rotated portion of this space. So the actual [-1, 1]
+            space that gets clipped or clamped is different from the one we see. We are effectively
+            rotating a square and cutting off any parts of it that happen to be outside of the
+            square viewing area. This is easy to see in the X and Y directions, but in Z, it results
+            in some unusual views.</para>
+        <section>
+            <title>Scene Graphs</title>
+            <para>This is the first code in the tutorial to use the scene graph part of the
+                framework. The term <glossterm>scene graph</glossterm> refers to a piece of software
+                that manages a collection of objects, typically in some kind of object hierarchy. In
+                this case, the <filename>Scene.h</filename> part of the framework contains a class
+                that loads an XML description of a scene. This description includes meshes, shaders,
+                and textures to load. These assets are then associated with named objects within the
+                scene. So a mesh combined with a shader can be rendered with one or more
+                textures.</para>
+            <para>The purpose of this system is to remove a <emphasis>lot</emphasis> of the
+                boilerplate code from the tutorial files. The setup work for the scene graph is far
+                less complicated than the setup work seen in previous tutorials.</para>
+            <para>As an example, here is the scene graph to load and link a particular
+                shader:</para>
+            <example>
+                <title>Scene Graph Shader Definition</title>
+                <programlisting language="xml">&lt;prog
+    xml:id="p_unlit"
+    vert="Unlit.vert"
+    frag="Unlit.frag"
+    model-to-camera="modelToCameraMatrix">
+    &lt;block name="Projection" binding="0"/>
+&lt;/prog></programlisting>
+            </example>
+            <para>The <literal>xml:id</literal> gives it a name; this is used by objects in the
+                scene to refer to this program. It also provides a way for other code to talk to it.
+                Most of the rest is self-explanatory. <literal>model-to-camera</literal> deserves
+                some explanation.</para>
+            <para>Rendering the scene graph is done by calling the scene graph's render function
+                with a camera matrix. Since the objects in the scene graph store their own
+                transformations, the scene graph combines each object's local transform with the
+                given camera matrix. But it still needs to know how to provide that matrix to the
+                shader. Thus, <literal>model-to-camera</literal> specifies the name of the
+                    <type>mat4</type> uniform that receives the model-to-camera transformation
+                matrix. There is a similar matrix for normals that is given the inverse-transpose of
+                the model-to-camera matrix.</para>
+            <para>The <literal>block</literal> element is the way we associate a uniform block in
+                the program with a uniform block binding point. There is a similar element for
+                    <literal>sampler</literal> that specifies which texture unit that a particular
+                GLSL sampler is bound to.</para>
+            <para>Objects in scene graph systems are traditionally called <quote>nodes,</quote> and
+                this scene graph is no exception.</para>
+            <example>
+                <title>Scene Graph Node Definition</title>
+                <programlisting language="xml">&lt;node
+    name="spinBar"
+    mesh="m_longBar"
+    prog="p_lit"
+    pos="-7 0 8"
+    orient="-0.148446 0.554035 0.212003 0.791242"
+    scale="4">
+    &lt;texture name="t_stone_pillar" unit="0" sampler="anisotropic"/>
+&lt;/node></programlisting>
+            </example>
+            <para>Nodes have a number of properties. They have a name, so that other code can
+                reference them. They have a mesh that they render and a program they use to render
+                that mesh. They have a position, orientation, and scale transform. The orientation
+                is specified as a quaternion, with the W component specified last (this is different
+                from how <type>glm::fquat</type> specifies it. The W there comes first). The order
+                of these transforms is scale, then orientation, then translation.</para>
+            <para>This node also has a texture bound to it. <literal>t_stone_pillar</literal> was a
+                texture that was loaded in a <literal>texture</literal> command. The
+                    <literal>unit</literal> property specifies the texture unit to use. And the
+                    <literal>sampler</literal> property defines which of the predefined samplers to
+                use. In this case, it uses a sampler with ansiotropic filtering to the maximum
+                degree allowed by the hardware. The texture wrapping modes of this sampler are to
+                wrap the S and T coordinates.</para>
+            <para>This is what the C++ setup code looks like for the entire scene:</para>
+            <example>
+                <title>Double Projection LoadAndSetupScene</title>
+                <programlisting language="cpp">std::auto_ptr&lt;Framework::Scene> pScene(new Framework::Scene("dp_scene.xml"));
+
+std::vector&lt;Framework::NodeRef> nodes;
+nodes.push_back(pScene->FindNode("cube"));
+nodes.push_back(pScene->FindNode("rightBar"));
+nodes.push_back(pScene->FindNode("leaningBar"));
+nodes.push_back(pScene->FindNode("spinBar"));
+
+AssociateUniformWithNodes(nodes, g_lightNumBinder, "numberOfLights");
+SetStateBinderWithNodes(nodes, g_lightNumBinder);
+
+GLuint unlit = pScene->FindProgram("p_unlit");
+Framework::Mesh *pSphereMesh = pScene->FindMesh("m_sphere");
+
+//No more things that can throw.
+g_spinBarOrient = nodes[3].NodeGetOrient();
+g_unlitProg = unlit;
+g_unlitModelToCameraMatrixUnif = glGetUniformLocation(unlit, "modelToCameraMatrix");
+g_unlitObjectColorUnif = glGetUniformLocation(unlit, "objectColor");
+
+std::swap(nodes, g_nodes);
+nodes.clear();	//If something was there already, delete it.
+
+std::swap(pSphereMesh, g_pSphereMesh);
+
+Framework::Scene *pOldScene = g_pScene;
+g_pScene = pScene.release();
+pScene.reset(pOldScene);	//If something was there already, delete it.</programlisting>
+            </example>
+            <para>This code does some fairly simple things. The scene graph system is good, but we
+                still need to be able to control uniforms not in blocks manually from external code.
+                Specifically in this case, the number of lights is a uniform, not a uniform block.
+                To do this, we need to use a uniform state binder,
+                    <varname>g_lightNumBinder</varname>, and set it into all of the nodes in the
+                scene. Also, we need references to those nodes to do rotation animations.</para>
+            <para>The <literal>p_unlit</literal> shader is never actually used in the scene graph;
+                we just use the scene graph as a convenient way to load the shader. Similarly, the
+                    <literal>m_sphere</literal> mesh is not used in a scene graph node. We pull
+                references to both of these out of the graph and use them ourselves where needed. We
+                extract some uniform locations from the unlit shader, so that we can draw unlit
+                objects with colors.</para>
+            <note>
+                <para>The code as written here is designed to be exception safe. Most of the
+                    functions that find nodes by name will throw if the name is not found. What this
+                    exception safety means is that it is easy to make the scene reloadable. It only
+                    replaces the old values in the global variables after executing all of the code
+                    that could throw an exception. This way, the entire scene, along with all
+                    meshes, textures, and shaders, can be reloaded by pressing
+                        <keycap>Enter</keycap>. If something goes wrong, the new scene will not be
+                    loaded and an error message is displayed.</para>
+            </note>
+            <para>Two of the objects in the scene rotate. This is easily handled using our list of
+                objects. In the <function>display</function> method:</para>
+            <programlisting language="cpp">g_nodes[0].NodeSetOrient(glm::rotate(glm::fquat(),
+    360.0f * g_timer.GetAlpha(), glm::vec3(0.0f, 1.0f, 0.0f)));
+
+g_nodes[3].NodeSetOrient(g_spinBarOrient * glm::rotate(glm::fquat(),
+    360.0f * g_timer.GetAlpha(), glm::vec3(0.0f, 0.0f, 1.0f)));</programlisting>
+            <para>We simply set the orientation based on a timer. For the second one, we previously
+                stored the object's orientation after loading it, and use that as the reference.
+                This allows us to rotate about its local Z axis.</para>
+        </section>
+        <section>
+            <title>Multiple Scenes</title>
+            <para>The split-screen trick used here is actually quite simple to pull off. It's also
+                one of the advantages of the scene graph: the ability to easily re-render the same
+                scene from a different perspective.</para>
+            <para>The first thing that must change is that the projection matrix cannot be set in
+                the old <function>reshape</function> function. It simply sets the width and height
+                of the screen into global variables. This is important because we will be using two
+                projection matrices.</para>
+            <para>The projection matrix used for the left scene is set up like this:</para>
+            <example>
+                <title>Left Projection Matrix</title>
+                <programlisting language="cpp">glm::ivec2 displaySize(g_displayWidth / 2, g_displayHeight);
+
+{
+    glutil::MatrixStack persMatrix;
+    persMatrix.Perspective(60.0f, (displaySize.x / (float)displaySize.y), g_fzNear, g_fzFar);
+    
+    ProjectionBlock projData;
+    projData.cameraToClipMatrix = persMatrix.Top();
+    
+    glBindBuffer(GL_UNIFORM_BUFFER, g_projectionUniformBuffer);
+    glBufferData(GL_UNIFORM_BUFFER, sizeof(ProjectionBlock), &amp;projData, GL_STREAM_DRAW);
+    glBindBuffer(GL_UNIFORM_BUFFER, 0);
+}
+
+glViewport(0, 0, (GLsizei)displaySize.x, (GLsizei)displaySize.y);
+g_pScene->Render(modelMatrix.Top());</programlisting>
+            </example>
+            <para>Notice that <varname>displaySize</varname> uses only half of the width. And this
+                half width is passed into the <function>glViewport</function> call. It is also used
+                to generate the aspect ratio for the perspective projection matrix. It is the
+                    <function>glViewport</function> function that causes our window to be split into
+                two halves.</para>
+            <para>What is more interesting is the right projection matrix computation:</para>
+            <example>
+                <title>Right Projection Matrix</title>
+                <programlisting language="cpp">{
+    glutil::MatrixStack persMatrix;
+    persMatrix.ApplyMatrix(glm::mat4(glm::mat3(g_persViewPole.CalcMatrix())));
+    persMatrix.Perspective(60.0f, (displaySize.x / (float)displaySize.y), g_fzNear, g_fzFar);
+    
+    ProjectionBlock projData;
+    projData.cameraToClipMatrix = persMatrix.Top();
+    
+    glBindBuffer(GL_UNIFORM_BUFFER, g_projectionUniformBuffer);
+    glBufferData(GL_UNIFORM_BUFFER, sizeof(ProjectionBlock), &amp;projData, GL_STREAM_DRAW);
+    glBindBuffer(GL_UNIFORM_BUFFER, 0);
+}
+
+if(!g_bDepthClampProj)
+    glDisable(GL_DEPTH_CLAMP);
+glViewport(displaySize.x + (g_displayWidth % 2), 0,
+    (GLsizei)displaySize.x, (GLsizei)displaySize.y);
+g_pScene->Render(modelMatrix.Top());
+glEnable(GL_DEPTH_CLAMP);</programlisting>
+            </example>
+            <para>Notice that we first take the camera matrix from the perspective view and apply it
+                to the matrix stack before the perspective projection itself. Remember that
+                transforms applied to the stack happen in <emphasis>reverse</emphasis> order. This
+                means that vertices are projected into 4D homogeneous clip-space coordinates, then
+                are transformed by a matrix. Only the rotation portion of the right camera matrix is
+                used. The translation is removed by the conversion to a <type>mat3</type> (which
+                takes only the top-left 3x3 part of the matrix, which if you recall contains the
+                rotation), then turns it back into a <type>mat4</type>.</para>
+            <para>Notice also that the viewport's X location is biased based on whether the
+                display's width is odd or not (<literal>g_displayWidth % 2</literal> is 0 if it is
+                even, 1 if it is odd). This means that if the width is stretched to an odd number,
+                there will be a one pixel gap between the two views of the scene.</para>
+        </section>
+        <section>
+            <title>Intermediate Projection</title>
+            <para>One question may occur to you: how is it possible for our right camera to provide
+                a rotation in NDC space, if it is being applied to the end of the projection matrix?
+                After all, the projection matrix goes from camera space to
+                <emphasis>clip</emphasis>-space. The clip-space to NDC space transform is done by
+                OpenGL after our vertex shader has done this matrix multiply. Do we not need the
+                shader to divide the clip-space values by W, then do the rotation?</para>
+            <para>Obviously not, since this code works. But just because code happens to work
+                doesn't mean that it should. So let's see if we can prove that it does. To do this,
+                we must prove that:</para>
+            <!--TODO: Equation of: T * (v/w) == (T * v)/w'-->
+            <para>This might look like a simple proof by inspection due to the associative nature of
+                these, but it is not. Vector math and matrix math simply don't combine that way.
+                However, it does provide a clue to the real proof.</para>
+            <para>This last part is really where much of the confusion dissipates. See, W (the W
+                component of the post-projection vertex) is not the same as W' on the other side of
+                the equation. W' is the W component of V <emphasis>after</emphasis> the
+                transformation by T.</para>
+            <para>So let us look at things symbolically. For the sake of simplicity, let's look at
+                just the X component of the output. The X component of the left side of the equation
+                is as follows:</para>
+            <!--TODO: Show what T*(v/w) equals, just for the X component
+The X component looks like this: m00*(x/w) + m10*(y/w) + m20*(z/w) + m30(w/w)-->
+            <para>The right side looks like this:</para>
+            <!--TODO: Show what T*(v/w) equals, just for the X component
+The X component looks like this: (m00*x + m10*y + m20*z + m30w)/(m03*x + m13*y + m23*z + m33*w)-->
+            <para>This tells us that the above equation is true if, and only if, the bottom row of T
+                is the vector (0, 0, 0, 1). If it is anything else, then we get different numbers.
+                Fortunately, the only matrix we have that has a different bottom row is the
+                projection matrix, and T is the rotation matrix we apply after translation.</para>
+            <para>So this works, as long as we use the right matrices. We can rotate, translate, and
+                scale post-projective clip-space exactly as we would post-projective NDC
+                space.</para>
+            <para>The take-home lesson here is very simple: projections are not that special as far
+                as transforms are concerned. Post-projective space is mostly just another space. It
+                may be a 4-dimensional homogeneous coordinate system, and that may be an odd thing
+                to fully understand. But that does not mean that you can't apply a regular matrix to
+                objects in this space.</para>
+        </section>
     </section>
     <section>
         <?dbhtml filename="Tut17 Projective Texture.html" ?>
         <title>Projective Texture</title>
         <para/>
+        <para/>
+        <para/>
     </section>
     <section>
         <?dbhtml filename="Tut17 Variable Point Light.html" ?>
         <title>Glossary</title>
         <glosslist>
             <glossentry>
+                <glossterm>scene graph</glossterm>
+                <glossdef>
+                    <para/>
+                </glossdef>
+            </glossentry>
+            <glossentry>
                 <glossterm/>
                 <glossdef>
                     <para/>

Tut 17 Spotlight on Textures/Cube Point Light.cpp

 #include <GL/freeglut.h>
 #include <glutil/MatrixStack.h>
 #include <glutil/MousePoles.h>
-#include "../framework/framework.h"
-#include "../framework/Mesh.h"
-#include "../framework/Timer.h"
-#include "../framework/UniformBlockArray.h"
-#include "../framework/directories.h"
-#include "../framework/MousePole.h"
-#include "../framework/Interpolators.h"
-#include "../framework/Scene.h"
-#include "../framework/SceneBinders.h"
+#include "../framework/framework_all.h"
 #include <glm/glm.hpp>
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtc/quaternion.hpp>

Tut 17 Spotlight on Textures/Double Projection.cpp

 #include <GL/freeglut.h>
 #include <glutil/MatrixStack.h>
 #include <glutil/MousePoles.h>
-#include "../framework/framework.h"
-#include "../framework/Mesh.h"
-#include "../framework/Timer.h"
-#include "../framework/UniformBlockArray.h"
-#include "../framework/directories.h"
-#include "../framework/MousePole.h"
-#include "../framework/Interpolators.h"
-#include "../framework/Scene.h"
-#include "../framework/SceneBinders.h"
+#include "../framework/framework_all.h"
 #include <glm/glm.hpp>
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtc/quaternion.hpp>

Tut 17 Spotlight on Textures/Projected Light.cpp

 #include <GL/freeglut.h>
 #include <glutil/MatrixStack.h>
 #include <glutil/MousePoles.h>
-#include "../framework/framework.h"
-#include "../framework/Mesh.h"
-#include "../framework/Timer.h"
-#include "../framework/UniformBlockArray.h"
-#include "../framework/directories.h"
-#include "../framework/MousePole.h"
-#include "../framework/Interpolators.h"
-#include "../framework/Scene.h"
-#include "../framework/SceneBinders.h"
+#include "../framework/framework_all.h"
 #include <glm/glm.hpp>
 #include <glm/gtc/type_ptr.hpp>
 #include <glm/gtc/quaternion.hpp>

framework/framework_all.h

+
+#ifndef FRAMEWORK_ALL_H
+#define FRAMEWORK_ALL_H
+
+#include "framework.h"
+#include "Mesh.h"
+#include "MousePole.h"
+#include "Scene.h"
+#include "SceneBinders.h"
+#include "Timer.h"
+#include "UniformBlockArray.h"
+#include "Interpolators.h"
+
+
+#endif //FRAMEWORK_ALL_H
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.