Source

gltut / Documents / Basics / Tutorial 02.xml

Full commit
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
<?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 xml:id="FragPosition">
        <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.
                    <quote>Undefined</quote> is the OpenGL shorthand for, <quote>I don't know, but
                    it's probably not what you want.</quote></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
                        glVertexAttrib<quote>Pointer</quote> 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>Drawing in Detail</title>
                <para>In the last tutorial, we skimmed over 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. If we look at everything as C++ pseudo-code, what we
                    have is this:</para>
                <example>
                    <title>Vertex Arrays</title>
                    <programlisting><![CDATA[GLbyte *bufferObject = (void*){0.0f, 0.5f, 0.0f, 1.0f, 0.5f, -0.366f, ...};
float *positionAttribArray[4] = (float *[4])(&(bufferObject + 0));
float *colorAttribArray[4] = (float *[4])(&(bufferObject + 48));]]></programlisting>
                </example>
                <para>Each element of the <varname>positionAttribArray</varname> contains 4
                    components. This is the case because the second parameter of
                        <function>glVertexAttribPointer</function> is 4. Each component is a
                    floating-point number; similarly because the third parameter is
                        <literal>GL_FLOAT</literal>. The array takes its data from
                        <varname>bufferObject</varname> because this was the buffer object that was
                    bound at the time that <function>glVertexAttribPointer</function> was called.
                    And the offset fro the beginning of the buffer object is 0 because that is the
                    last parameter of <function>glVertexAttribPointer</function>.</para>
                <para>The same goes for <varname>colorAttribArray</varname>.</para>
                <para>Using the above pseudo-code, <function>glDrawArrays</function> is implemented
                    as follows:</para>
                <example>
                    <title>Draw Arrays Implementation</title>
                    <programlisting><![CDATA[void glDrawArrays(GLenum type, GLint start, GLint count)
{
    for(int element = start; element < start + count; element++)
    {
        VertexShader(positionAttribArray[element], colorAttribArray[element]);
    }
}
]]></programlisting>
                </example>
                <para>This means that the vertex shader will be executed <varname>count</varname>
                    times, and it will be given data beginning with the <varname>start</varname>-th
                    element and continuing for <varname>count</varname> elements. So the first time
                    the vertex shader gets run, it takes the position attribute from
                        <literal>bufferObject[0 + (0 * 4 * sizeof(float))]</literal> and the color
                    attribute from <literal>bufferObject[48 + (0 * 4 * sizeof(float))]</literal>.
                    The second time pulls the position from <literal>bufferObject[0 + (1 * 4 *
                        sizeof(float))]</literal> and color from <literal>bufferObject[48 + (1 * 4 *
                        sizeof(float))]</literal>. And so on.</para>
            </section>
        </section>
        <section>
            <title>Vertex Shader</title>
            <para>Our new vertex shader looks like this:</para>
            <example>
                <title>Multi-input Vertex Shader</title>
                <programlisting><![CDATA[#version 150

in vec4 position;
in vec4 color;

smooth out vec4 theColor;

void main()
{
    gl_Position = position;
    theColor = color;
}]]></programlisting>
            </example>
            <para>There are three new lines here. Let us take them one at a time.</para>
            <para>The declaration of the global <varname>color</varname> defines a new input for the
                vertex shader. So this shader, in addition to taking an input named
                    <varname>position</varname> also takes a second input named
                    <varname>color</varname>. As with the <varname>position</varname> input, the
                tutorial queries the attribute location after the program is linked with the
                    <function>glGetAttribLocation</function>:</para>
            <example>
                <title>Querying the Attributes</title>
                <programlisting>positionAttrib = glGetAttribLocation(theProgram, "position");
colorAttrib = glGetAttribLocation(theProgram, "color");</programlisting>
            </example>
            <para>The <varname>colorAttrib</varname> variable is used later when defining the
                attribute arrays for rendering.</para>
            <para>That much only gets the data into the vertex shader. We want to pass this data out
                of the vertex shader. To do this, we must define an <glossterm>output
                    variable</glossterm>. This is done using the <literal>out</literal> keyword. In
                this case, the output variable is called <varname>theColor</varname> and is of type
                    <type>vec4</type>.</para>
            <para>The <literal>smooth</literal> keyword is an <glossterm>interpolation
                    qualifier</glossterm>. We will see what this means in a bit later.</para>
            <para>Of course, this simply defines the output variable. In <function>main</function>,
                we actually write to it, assigning to it the value of <varname>color</varname> that
                was given as a vertex attribute. This being shader code, we could have used some
                other heuristic or arbitrary algorithm to compute the color. But for the purpose of
                this tutorial, it is simply passing the value of an attribute passed to the vertex
                shader.</para>
            <para>Technically, the built-in variable <varname>gl_Position</varname> is defined as
                    <literal>out vec4 gl_Position</literal>. So it is an output variable as well. It
                is a special output because this value is directly used by the system, rather than
                used only by shaders. User-defined outputs, like <varname>theColor</varname> above,
                have no intrinsic meaning to the system. They only have an effect in so far as other
                shader stages use them, as we will see next.</para>
        </section>
        <section>
            <title>Fragment Program</title>
            <para>The new fragment shader looks like this:</para>
            <example>
                <title>Fragment Shader with Input</title>
                <programlisting><![CDATA[#version 150

smooth in vec4 theColor;

out vec4 outputColor;

void main()
{
    outputColor = theColor;
}]]></programlisting>
            </example>
            <para>This fragment shader defines an input variable. It is no coincidence that this
                input variable is named and typed the same as the output variable from the vertex
                shader. We are trying to feed information from the vertex shader to the fragment
                shader. To do this, OpenGL requires that the output from the previous stage have the
                same name and type as the input to the next stage. It also must use the same
                interpolation qualifier; if the vertex shader used <literal>smooth</literal>, the
                fragment shader must do the same.</para>
            <para>This is a good part of the reason why OpenGL requires vertex and fragment shaders
                to be linked together; if the names, types, or interpolation qualifiers do not
                match, then OpenGL can raise an error at link time.</para>
            <para>So the fragment shader receives the value output from the vertex shader. The
                shader simply takes this value and copies it to the output. Thus, the color of each
                fragment will simply be whatever the vertex shader passed along.</para>
        </section>
        <section>
            <title>Fragment Interpolation</title>
            <para>Now we come to the elephant in the room, so to speak. There is a basic
                communication problem.</para>
            <para>See, our vertex shader is run 3 times. This execution produces 3 output positions
                    (<varname>gl_Position</varname>) and 3 output colors
                    (<varname>theColor</varname>). The 3 positions are used to construct and
                rasterize a triangle, producing a number of fragments.</para>
            <para>The fragment shader is not run 3 times. It is run once for every fragment produced
                by the rasterizer for this triangle. The number of fragments produced by a triangle
                depends on the viewing resolution and how much area of the screen the triangle
                covers. An equilateral triangle the length of who's sides is 1 has an area of
                ~0.433. The total screen area (on the range [-1, 1] in X and Y) is 4, so our
                triangle covers approximately one-tenth of the screen. The window's natural
                resolution is 500x500 pixels. 500*500 is 250,000 pixels; one-tenth of this is
                25,000. So our fragment shader gets executed about 25,000 times.</para>
            <para>There's a slight disparity here. If the vertex shader is directly communicating
                with the fragment shader, and the vertex shader is outputting only 3 total color
                values, where do the other 24,997 values come from?</para>
            <para>The answer is <glossterm>fragment interpolation</glossterm>.</para>
            <para>By using the interpolation qualifier <literal>smooth</literal> when defining the
                vertex output and fragment input, we are telling OpenGL to do something special with
                this value. Instead of each fragment receiving one of the values, what each fragment
                gets is a <emphasis>blend</emphasis> between the three output values. The closer the
                fragment is to one vertex, the more that vertex's output contributes to the value
                that the fragment program receives.</para>
            <para>Because such interpolation is by far the most common ode for communicating between
                the vertex shader and the fragment shader, if you do not provide an interpolation
                keyword, <literal>smooth</literal> will be used by default. There are two other
                alternatives: <literal>noperspective</literal> and <literal>flat</literal>.</para>
            <para>If you were to modify the tutorial and change <literal>smooth</literal> to
                    <literal>noperspective</literal>, you would see no change. That does not mean a
                change did not happen; our example is too simple for there to actually be a change.
                The difference between <literal>smooth</literal> and
                    <literal>noperspective</literal> is subtle, and only matters once we start using
                more concrete and real tutorials. We will discuss this different much later.</para>
            <para>The <literal>flat</literal> interpolation actually turns interpolation off. It
                essentially says that, rather than interpolating between three values, each fragment
                of the triangle will simply get the first of the three vertex shader output
                variables. The fragment shader gets a flat value across the surface of the triangle,
                hence the term <quote>flat.</quote></para>
            <para>Each rasterized triangle has its own set of 3 outputs that are interpolated to
                compute the value for the fragments created by that triangle. So if you render 2
                triangles, the interpolated values from one triangle do not directly affect the
                interpolated values from another triangle. Thus, each triangle can be taken
                independently from the rest.</para>
            <para>It is possible, and highly desirable in many cases, to build multiple triangles
                from shared vertices and vertex data. But we will discuss this at a later
                time.</para>
        </section>
        <section>
            <title>The Final Image</title>
            <para>When you run the tutorial, you will get the following image.</para>
            <!--TODO: Insert image of interpolated colors here.-->
            <para>The colors at each tip of the triangle are the pure red, green, and blue colors.
                They blend together towards the center of the triangle.</para>
        </section>
    </section>
    <section>
        <title>In Review</title>
        <para>In this tutorial, you learned how to use much of the <varname>gl_FragCoord</varname>
            built-in variable in a fragment shader. You learned that this data is in screen-space,
            and how to convert screen space into more useful forms. You also learned how to pass
            data directly from vertex shaders to fragment shaders. You learned about the
            interpolation that happens to vertex shader outputs across the surface of a
            triangle.</para>
        <section>
            <title>Further Study</title>
            <para>Here are some ideas to play around with.</para>
            <itemizedlist>
                <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>
                <listitem>
                    <para>Combine the FragPosition tutorial with the Vertex Color tutorial. Use
                        interpolated color from the vertex shader and combine that with a constant
                        color, based on the screen-space position of the fragment.</para>
                </listitem>
            </itemizedlist>
        </section>
    </section>
    <glossary>
        <title>Glossary</title>
        <glossentry>
            <glossterm>Fragment Interpolation</glossterm>
            <glossdef>
                <para>This is the process of taking 3 corresponding vertex shader outputs and
                    interpolating them across the surface of the triangle. For each fragment
                    generated, there will also be an interpolated value generated for each of the
                    vertex shader's outputs (except for certain built-in outputs, like
                        <varname>gl_Position</varname>.) The way that the interpolation is handled
                    depends on the <glossterm>interpolation qualifier</glossterm> on the vertex
                    output and fragment input.</para>
            </glossdef>
        </glossentry>
        <glossentry>
            <glossterm>Interpolation Qualifier</glossterm>
            <glossdef>
                <para>A GLSL keyword assigned to outputs of vertex shaders and the corresponding
                    inputs of fragment shaders. It determines how the three values of the triangle
                    are interpolated across that triangle's surface. The qualifier used on the
                    vertex shader output must match with the one used on the fragment shader input
                    of the same name.</para>
                <para>Valid interpolation qualifiers are <literal>smooth</literal>,
                        <literal>flat</literal>, and <literal>noperspective</literal>.</para>
            </glossdef>
        </glossentry>
    </glossary>
</chapter>