Source

gltut / Documents / Basics / Tutorial 02.xml

Full commit
<?xml version="1.0" encoding="UTF-8"?>
<?oxygen RNGSchema="http://docbook.org/xml/5.0/rng/docbookxi.rng" type="xml"?>
<?oxygen SCHSchema="http://docbook.org/xml/5.0/rng/docbookxi.rng"?>
<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">
    <title>Playing with Colors</title>
    <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);
}]]></programlisting>
        </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,
};]]></programlisting>
            </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);
}]]></programlisting>
            </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();
}]]></programlisting>
            </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>
</chapter>