Jason McKesson committed 6cfb4c6

Documentation for tutorial 2. Also, made a few corrections to the source code for this tutorial.

  • Participants
  • Parent commits 5851bf4

Comments (0)

Files changed (5)

Documents/Basics/Tutorial 02.xml

 <chapter xmlns="" xmlns:xi=""
     xmlns:xlink="" version="5.0">
     <title>Playing with Colors</title>
-    <para>G</para>
+    <para>This tutorial will show how to provide some color to the triangle from the previous
+        tutorial. Instead of just giving the triangle a solid color, we will use two methods to
+        provide it with varying color across its surface. The methods are to using the fragment's
+        position to compute a color and to using per-vertex data to compute a color.</para>
+    <section>
+        <title>Fragment Position Display</title>
+        <para>As we stated in the overview, part of the fragment's data includes the position of the
+            fragment on the screen. Thus, if we want to vary the color of a triangle across its
+            surface, We can access this data in our fragment shader and use it to compute the final
+            color for that fragment. This is done in the <phrase role="propername">Fragment
+                Position</phrase> tutorial, who's main file is
+            <filename>FragPosition.cpp</filename>.</para>
+        <para>In this tutorial, and all future ones, shaders will be loaded from files instead of
+            hard-coded strings in the .cpp file. To support this, the framework has added the
+                <function>Framework::LoadShader</function> and
+                <function>Framework::CreateProgram</function> functions. These work similarly to the
+            previous tutorial's <function>CreateShader</function> and
+                <function>CreateProgram</function>, except that <function>LoadShader</function>
+            takes a filename instead of a shader file.</para>
+        <para>The FragPosition tutorial loads two shaders, the vertex shader
+                <filename>data/FragPosition.vert</filename> and the fragment shader
+                <filename>data/FragPosition.frag</filename>. The vertex shader is identical to the
+            one in the last tutorial. The fragment shader is very new, however:</para>
+        <example>
+            <title>FragPosition's Fragment Shader</title>
+            <programlisting><![CDATA[#version 150
+out vec4 outputColor;
+void main()
+    float lerpValue = gl_FragCoord.y / 500.0f;
+    outputColor = mix(vec4(1.0f, 1.0f, 1.0f, 1.0f), vec4(0.2f, 0.2f, 0.2f, 1.0f), lerpValue);
+        </example>
+        <para><varname>gl_FragCoord</varname> is a built-in variable that is only available in a
+            fragment shader. It is a <classname>vec3</classname>, so it has an X, Y, and Z
+            component. The X and Y values are in <emphasis>window</emphasis> coordinates, so the
+            absolute value of these numbers will change based on the window's resolution. Recall
+            that window coordinates put the origin at the bottom-left corner. So the bottom of the
+            triangle would have a lower Y value than the top.</para>
+        <para>The idea with this shader is that the color of a fragment will be based on the Y value
+            of its window position. The 500.0f is the height of the window (unless you resize the
+            window). The division in the first line of the function simply converts the Y position
+            to the [0, 1] range, where 1 is at the top of the window and 0 is at the bottom.</para>
+        <para>The second line uses this [0, 1] value to perform a linear interpolation between two
+            colors. The <function>mix</function> function is one of the many,
+                <emphasis>many</emphasis> standard functions that the OpenGL Shading Language
+            provides. Many of these functions, like <function>mix</function>, are vectorized. That
+            is, some of their parameters can be vectors, and when they are, they will perform their
+            operations on each component of the vector simultaneously. In this case, the
+            dimensionality of the first two parameters must match.</para>
+        <para>The <function>mix</function> function performs a linear interpolation. It will return
+            exactly the first parameter if the third parameter is 0, and it will return exactly the
+            second parameter if the third parameter is 1. If the third parameter is between 0 and 1,
+            it will return a value that much between the two other parameters.</para>
+        <note>
+            <para>The third parameter to <function>mix</function> must be on the range [0, 1].
+                However, GLSL will not check this or do the clamping for you. If it is not on this
+                range, the result of the <function>mix</function> function will be undefined.</para>
+        </note>
+        <para>In this case, the bottom of the triangle, the one closest to a Y of 0, will be the
+            most white. While the top of the triangle, the parts closest to a Y of 500, will have
+            the darkest color.</para>
+        <para>Other than the fragment shader, nothing much changes in the code.</para>
+    </section>
+    <section>
+        <title>Vertex Attributes</title>
+        <para>Using the fragment position in a fragment shader is quite useful, but it is far from
+            the best tool for controlling the color of triangles. A much more useful tool is to give
+            each vertex a color explicitly. The <phrase role="propername">Vertex Colors</phrase>
+            tutorial implements this; the main file for this tutorial is
+                <filename>VertexColors.cpp</filename>.</para>
+        <para>We want to affect the data being passed through the system. The sequence of events we
+            want to happen is as follows.</para>
+        <orderedlist>
+            <listitem>
+                <para>For every position that we pass to a vertex shader, we want to pass a
+                    corresponding color value.</para>
+            </listitem>
+            <listitem>
+                <para>For every output position in the vertex shader, we also want to output a color
+                    value that is the same as the input color value the vertex shader
+                    received.</para>
+            </listitem>
+            <listitem>
+                <para>In the fragment shader, we want to receive an input color from the vertex
+                    shader and use that as the output color of that fragment.</para>
+            </listitem>
+        </orderedlist>
+        <para>You most likely have some serious questions about that sequence of events, notably
+            about how steps 2 and 3 could possibly work. We'll get to that. We will follow the
+            revised flow of data through the OpenGL pipeline.</para>
+        <section>
+            <title>Multiple Vertex Arrays and Attributes</title>
+            <para>In order to accomplish the first step, we need to change our vertex array data.
+                That data now looks like this:</para>
+            <example>
+                <title>New Vertex Array Data</title>
+                <programlisting><![CDATA[const float vertexData[] = {
+     0.0f,    0.5f, 0.0f, 1.0f,
+     0.5f, -0.366f, 0.0f, 1.0f,
+    -0.5f, -0.366f, 0.0f, 1.0f,
+     1.0f,    0.0f, 0.0f, 1.0f,
+     0.0f,    1.0f, 0.0f, 1.0f,
+     0.0f,    0.0f, 1.0f, 1.0f,
+            </example>
+            <para>The first 3 sets of values are the three positions of the triangle, and the next 3
+                sets of values are the three colors at these vertices. What we really have is two
+                arrays that just happen to be adjacent to one another in memory. One starts at the
+                memory address <literal>&amp;vertexData[0]</literal>, and the other starts at the
+                memory address <literal>&amp;vertexData[12]</literal></para>
+            <para>As with all vertex data, this is put into a buffer object. We have seen this code
+                before:</para>
+            <example>
+                <title>Buffer Object Initialization</title>
+                <programlisting><![CDATA[void InitializeVertexBuffer()
+    glGenBuffers(1, &vertexBufferObject);
+    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
+    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STREAM_DRAW);
+    glBindBuffer(GL_ARRAY_BUFFER, 0);
+            </example>
+            <para>The code has not changed, because the sizes of the array is computed by the
+                compiler with the <literal>sizeof</literal> directive. Since we added a few elements
+                to the buffer, the computed size naturally grows bigger.</para>
+            <para>Also, you may notice that the positions are different from prior tutorials. The
+                original triangle, and the one that was used in the <phrase role="propername"
+                    >Fragment Position</phrase> code, was a right triangle (one of the angles of the
+                triangle is 90 degrees) that was isosceles (two of its three sides are the same
+                length). This new triangle is an equilateral triangle (all three sides are the same
+                length) centered at the origin.</para>
+            <para>Recall from above that we are sending two pieces of data per-vertex: a position
+                and a color. We have two arrays, one for each piece of data. They may happen to be
+                adjacent to one another, but this changes nothing; there are two arrays of data. We
+                need to tell OpenGL how to get each of these pieces of data.</para>
+            <para>This is done as follows:</para>
+            <example>
+                <title>Rendering the Scene</title>
+                <programlisting><![CDATA[void display()
+    glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
+    glClear(GL_COLOR_BUFFER_BIT);
+    glUseProgram(theProgram);
+    glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
+    glEnableVertexAttribArray(positionAttrib);
+    glEnableVertexAttribArray(colorAttrib);
+    glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, 0, 0);
+    glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);
+    glDrawArrays(GL_TRIANGLES, 0, 3);
+    glDisableVertexAttribArray(positionAttrib);
+    glDisableVertexAttribArray(colorAttrib);
+    glUseProgram(0);
+    glutSwapBuffers();
+    glutPostRedisplay();
+            </example>
+            <para>Since we have two pieces of data, we have two vertex attributes. For each
+                attribute, we must call <function>glEnableVertexAttribArray</function> to enable
+                that particular attribute. The parameter is the attribute location we query from the
+                shader program (we'll get to that in the next section) that refers to that
+                particular attribute.</para>
+            <para>Then, we call <function>glVertexAttribPointer</function> for each of the attribute
+                arrays we want to use. The only difference in the two calls are which attribute
+                location to attach this to and the last parameter. The last parameter, if you
+                recall, is the byte offset into the buffer of where the data for this attribute
+                starts. This offset, in this case, is 4 (the size of a float) * 12 (the number of
+                floats in the position data).</para>
+            <note>
+                <para>If you're wondering why it is <literal>(void*)48</literal> and not just 48,
+                    that is because of some legacy API cruft. The reason why the function name is
+                            <function>glVertexAttrib<emphasis>Pointer</emphasis></function> is
+                    because the last parameter is technically a pointer to client memory. Or at
+                    least, it could be in the past.</para>
+            </note>
+            <para>After this, we use <function>glDrawArrays</function> to render, then disable the
+                arrays with <function>glDisableVertexAttribArray.</function></para>
+            <section>
+                <title>A Look at Drawing</title>
+                <para>In the last tutorial, we skimmed past the details of what exactly
+                        <function>glDrawArrays</function> does. Let us take a closer look
+                    now.</para>
+                <para>The various attribute array functions set up arrays for OpenGL to read from
+                    when rendering. In our case here, we have two arrays. Each array has a buffer
+                    object and an offset into that buffer where the array begins, but the arrays do
+                    not have an explicit size.</para>
+            </section>
+        </section>
+        <section>
+            <title>Vertex Shader</title>
+            <para/>
+        </section>
+        <section>
+            <title>Fragment Interpolation</title>
+            <para/>
+        </section>
+    </section>
+    <section>
+        <title>In Review</title>
+        <para/>
+        <section>
+            <title>Further Study</title>
+            <para>Here are some ideas to play around with.</para>
+            <itemizedlist>
+                <listitem>
+                    <para/>
+                </listitem>
+                <listitem>
+                    <para>Change the viewport in the FragPosition tutorial. Put the viewport in the
+                        top half of the display, and then put it in the bottom half. See how this
+                        affects the shading on the triangle.</para>
+                </listitem>
+            </itemizedlist>
+        </section>
+    </section>

Documents/Tutorial Documents.xpr

     <projectTree name="Tutorial%20Documents.xpr">
         <folder name="Basics">
-            <file name="Basics/Tutorial%200.xml"/>
+            <file name="Basics/Tutorial%2000.xml"/>
             <file name="Basics/Tutorial%2001.xml"/>
             <file name="Basics/Tutorial%2002.xml"/>

Tut 02 Playing with Colors/FragPosition.cpp

 GLuint theProgram;
 GLuint positionAttrib;
 GLuint elapsedTimeUniform;
-GLuint wndDimensions;
 void InitializeProgram()
 void reshape (int w, int h)
 	glViewport(0, 0, (GLsizei) w, (GLsizei) h);
-	glUseProgram(theProgram);
-	glUniform2f(wndDimensions, float(w), float(h));
-	glUseProgram(0);
 //Called whenever a key on the keyboard was pressed.

Tut 02 Playing with Colors/VertexColors.cpp

 	glBindBuffer(GL_ARRAY_BUFFER, vertexBufferObject);
+	glEnableVertexAttribArray(colorAttrib);
 	glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, 0, 0);
-	glEnableVertexAttribArray(colorAttrib);
 	glVertexAttribPointer(colorAttrib, 4, GL_FLOAT, GL_FALSE, 0, (void*)48);
 	glDrawArrays(GL_TRIANGLES, 0, 3);
+	glDisableVertexAttribArray(colorAttrib);

Tut 02 Playing with Colors/premake4.lua

-SetupProject("Fragment Position", "FragPosition.cpp")
-SetupProject("Vertex Colors", "VertexColors.cpp")
+SetupProject("Fragment Position", "FragPosition.cpp",
+	"data/FragPosition.frag", "data/FragPosition.vert")
+SetupProject("Vertex Colors", "VertexColors.cpp",
+	"data/VertexColors.frag", "data/VertexColors.vert")