Source

gltut / Documents / Texturing / Tutorial 16.xml

  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
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
<?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">
    <?dbhtml filename="Tutorial 16.html" ?>
    <title>Gamma and Textures</title>
    <para>In the last tutorial, we had our first picture texture. That was a simple, flat scene;
        now, we are going to introduce lighting. But before we can do that, we need to have a
        discussion about what is actually stored in the texture.</para>
    <section>
        <?dbhtml filename="Tut16 What Textures Mean.html" ?>
        <title>The sRGB Colorspace</title>
        <para>One of the most important things you should keep in mind with textures is the answer
            to the question, <quote>what does the data in this texture mean?</quote> In the first
            texturing tutorial, we had many textures with various meanings. We had:</para>
        <itemizedlist>
            <listitem>
                <para>A 1D texture that represented the Gaussian model of specular reflections for a
                    specific shininess value.</para>
            </listitem>
            <listitem>
                <para>A 2D texture that represented the Gaussian model of specular reflections,
                    where the S coordinate represented the angle between the normal and the
                    half-angle vector. The T coordinate is the shininess of the surface.</para>
            </listitem>
            <listitem>
                <para>A 2D texture that assigned a specular shininess to each position on the
                    surface.</para>
            </listitem>
        </itemizedlist>
        <para>Without this knowledge, one could not effectively use those textures. It is vital to
            know what data a texture stores and what its texture coordinates mean.</para>
        <para>Earlier, we discussed how important colors in a linear colorspace was to getting
            accurate color reproduction in lighting and rendering. Gamma correction was applied to
            the output color, to map the linear RGB values to the gamma-correct RGB values the
            display expects.</para>
        <para>At the time, we said that our lighting computations all assume that the colors of the
            vertices were linear RGB values. Which means that it was important that the creator of
            the model, the one who put the colors in the mesh, ensure that the colors being added
            were in fact linear RGB colors. If the modeller failed to do this, if the modeller's
            colors were in a non-linear RGB colorspace, then the mesh would come out with colors
            that were substantially different from what he expected.</para>
        <para>The same goes for textures, only much moreso. And that is for one very important
            reason. Load up the <phrase role="propername">Gamma Ramp</phrase> tutorial.</para>
        <figure>
            <title>Gamma Ramp </title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="Gamma%20Ramp.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>These are just two rectangles with a texture mapped to them. The top one is rendered
            without the shader's gamma correction, and the bottom one is rendered with gamma
            correction. These textures are 320x64 in size, and they are rendered at exactly this
            size.</para>
        <para>The texture contains five greyscale color blocks. Each block increases in brightness
            from the one to its left, in 25% increments. So the second block to the left is 25% of
            maximum brightness, the middle block is 50% and so on. This means that the second block
            to the left should appear half as bright as the middle, and the middle should appear
            half as bright as the far right block.</para>
        <para>Gamma correction exists to make linear values appear properly linear on a non-linear
            display. It corrects for the display's non-linearity. Given everything we know, the
            bottom rectangle, the one with gamma correction which takes linear values and converts
            them for proper display, should appear correct. The top rectangle should appear
            wrong.</para>
        <para>And yet, we see the exact opposite. The relative brightness of the various blocks is
            off in the bottom block, but not the top. Why does this happen?</para>
        <para>Because, while the apparent brightness of the texture values increases in 25%
            increments, the color values that are used by that texture do not. This texture was not
            created by simply putting 0.0 in the first block, 0.25 in the second, and so forth. It
            was created by an image editing program. The colors were selected by their
                <emphasis>apparent</emphasis> relative brightness, not by simply adding 0.25 to the
            values.</para>
        <para>This means that the color values have <emphasis>already been</emphasis> gamma
            corrected. They cannot be in a linear colorspace, because the person creating the image
            selected colors based on their appearance. Since the appearance of a color is affected
            by the non-linearity of the display, the texture artist was effectively selected
            post-gamma corrected color values. To put it simply, the colors in the texture are
            already in a non-linear color space.</para>
        <para>Since the top rectangle does not use gamma correction, it is simply passing the
            pre-gamma corrected color values to the display. It simply works itself out. The bottom
            rectangle effectively performs gamma correction twice.</para>
        <para>This is all well and good, when we are drawing a texture directly to the screen. But
            if the colors in that texture were intended to represent the diffuse reflectance of a
            surface as part of the lighting equation, then there is a major problem. The color
            values retrieved from the texture are non-linear, and all of our lighting equations
                <emphasis>need</emphasis> the input values to be linear.</para>
        <para>We could gamma uncorrect the texture values manually, either at load time or in the
            shader. But that is entirely unnecessary and wasteful. Instead, we can just tell OpenGL
            the truth: that the texture is not in a linear colorspace.</para>
        <para>Virtually every image editing program you will ever encounter, from the almighty
            Photoshop to the humble Paint, displays colors in a non-linear colorspace. But they do
            not use just any non-linear colorspace; they have settled on a specific colorspace
            called the <glossterm>sRGB colorspace.</glossterm> So when an artist selects a shade of
            green for example, they are selecting it from the sRGB colorspace, which is
            non-linear.</para>
        <para>How commonly used is the sRGB colorspace? It's built into every JPEG. It's used by
            virtually every video compression format and tool. It is assumed by virtual every image
            editing program. In general, if you get an image from an unknown source, it would be
            perfectly reasonable to assume the RGB values are in sRGB unless you have specific
            reason to believe otherwise.</para>
        <para>The sRGB colorspace is an approximation of a gamma of 2.2. It is not exactly 2.2, but
            it is close enough that you can display an sRGB image to the screen without gamma
            correction. Which is exactly what we did with the top rectangle.</para>
        <para>Because of the ubiquity of the sRGB colorspace, sRGB decoding logic is built directly
            into GPUs these days. And naturally OpenGL supports it. This is done via special image
            formats.</para>
        <example>
            <title>sRGB Image Format</title>
            <programlisting language="cpp">std::auto_ptr&lt;glimg::ImageSet> pImageSet(glimg::loaders::stb::LoadFromFile(filename.c_str()));

glimg::SingleImage image = pImageSet->GetImage(0, 0, 0);
glimg::Dimensions dims = image.GetDimensions();

glimg::OpenGLPixelTransferParams pxTrans = glimg::GetUploadFormatType(pImageSet->GetFormat(), 0);

glBindTexture(GL_TEXTURE_2D, g_textures[0]);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, dims.width, dims.height, 0,
    pxTrans.format, pxTrans.type, image.GetImageData());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, pImageSet->GetMipmapCount() - 1);
		
glBindTexture(GL_TEXTURE_2D, g_textures[1]);
glTexImage2D(GL_TEXTURE_2D, 0, GL_SRGB8, dims.width, dims.height, 0,
    pxTrans.format, pxTrans.type, image.GetImageData());
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, pImageSet->GetMipmapCount() - 1);

glBindTexture(GL_TEXTURE_2D, 0);</programlisting>
        </example>
        <para>This code loads the same texture data twice, but with a different texture format. The
            first one uses the <literal>GL_RGB8</literal> format, while the second one uses
                <literal>GL_SRGB8</literal>. The latter identifies the texture's color data as being
            in the sRGB colorspace.</para>
        <para>To see what kind of effect this has on our rendering, you can switch between which
            texture is used. The <keycap>1</keycap> key switches the top texture between linear RGB
            and sRGB, while <keycap>2</keycap> does the same for the bottom.</para>
        <figure>
            <title>Gamma Ramp with sRGB Images</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="Gamma%20Ramp%20sRGB.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>When using the sRGB version for both the top and the bottom, we can see that the gamma
            correct bottom one is right.</para>
        <para>When a texture uses one of the sRGB formats, texture access functions to those
            textures do things slightly differently. When they fetch a texel, OpenGL automatically
            linearizes the color from the sRGB colorspace. This is exactly what we want. And the
            best part is that the linearisation cost is negligible. So there is no need to play with
            the data or otherwise manually linearize it. OpenGL does it for us.</para>
        <para>Note that the shader does not change. It still uses a regular <type>sampler2D</type>,
            accesses it with a 2D texture coordinate and the <function>texture</function> function,
            etc. The shader does not have to know or care whether the image data is in the sRGB
            colorspace or a linear one. It simply calls the <function>texture</function> function
            and expects it to return linear RGB color values.</para>
        <section>
            <title>Pixel Positioning</title>
            <para>There is an interesting thing to note about the rendering in this tutorial. Not
                only does it use an orthographic projection (unlike most of our tutorials since
                Tutorial 4), it does something special with its orthographic projection. In the
                pre-perspective tutorials, the orthographic projection was used essentially by
                default. We were drawing vertices directly in clip-space. And since the W of those
                vertices was 1, clip-space is identical to NDC space, and we therefore had an
                orthographic projection.</para>
            <para>It is often useful to want to draw something certain objects using window-space
                pixel coordinates. This is commonly used for drawing text, but it can also be used
                for displaying images exactly as they appear in a texture, as we do here. Since a
                vertex shader must output clip-space values, the key is to develop a matrix that
                transforms window-space coordinates into clip-space. OpenGL will handle the
                conversion back to window-space internally.</para>
            <para>This is done via the <function>reshape</function> function, as with most of our
                projection matrix functions. The computation is actually quite simple.</para>
            <example>
                <title>Window to Clip Matrix Computation</title>
                <programlisting language="cpp">Framework::MatrixStack persMatrix;
persMatrix.Translate(-1.0f, 1.0f, 0.0f);
persMatrix.Scale(2.0f / w, -2.0f / h, 1.0f);</programlisting>
            </example>
            <para>The goal is to transform window-space coordinates into clip-space, which is
                identical to NDC space since the W component remains 1.0. Window-space coordinates
                have an X range of [0, w) and Y range of [0, h). NDC space has X and Y ranges of
                [-1, 1].</para>
            <para>The first step is to scale our two X and Y ranges from [0, w/h) to [0, 2]. The
                next step is to apply a simply offset to shift it over to the [-1, 1] range. Don't
                forget that the transforms are applied in the reverse order from how they are
                applied to the matrix stack.</para>
            <para>There is one thing to note however. NDC space has +X going right and +Y going up.
                OpenGL's window-space agrees with this; the origin of window-space is at the
                lower-left corner. That is nice and all, but many people are used to a top-left
                origin, with +Y going down.</para>
            <para>In this tutorial, we use a top-left origin window-space. That is why the Y scale
                is negated and why the Y offset is positive (for a lower-left origin, we would want
                a negative offset).</para>
            <note>
                <para>By negating the Y scale, we flip the winding order of objects rendered. This
                    is normally not a concern; most of the time you are working in window-space, you
                    aren't relying on face culling to strip out certain triangles. In this tutorial,
                    we do not even enable face culling. And oftentimes, when you are rendering with
                    pixel-accurate coordinates, face culling is irrelevant and should be
                    disabled.</para>
            </note>
        </section>
        <section>
            <title>Vertex Formats</title>
            <para>In all of the previous tutorials, our vertex data has been arrays of
                floating-point values. For the first time, that is not the case. Since we are
                working in pixel coordinates, we want to specify vertex positions with integer pixel
                coordinates. This is what the vertex data for the two rectangles look like:</para>
            <programlisting language="cpp">const GLushort vertexData[] = {
     90, 80,	0,		0,
     90, 16,	0,		65535,
    410, 80,	65535,	0,
    410, 16,	65535,	65535,
    
     90, 176,	0,		0,
     90, 112,	0,		65535,
    410, 176,	65535,	0,
    410, 112,	65535,	65535,
};</programlisting>
            <para>Our vertex data has two attributes: position and texture coordinates. Our
                positions are 2D, as are our texture coordinates. These attributes are interleaved,
                with the position coming first. So the first two columns above are the positions and
                the second two columns are the texture coordinates.</para>
            <para>Instead of floats, our data is composed of <type>GLushort</type>s, which are
                2-byte integers. How OpenGL interprets them is specified by the parameters to
                    <function>glVertexAttribPointer</function>. It can interpret them in two ways
                (technically 3, but we don't use that here):</para>
            <example>
                <title>Vertex Format</title>
                <programlisting language="cpp">glBindVertexArray(g_vao);
glBindBuffer(GL_ARRAY_BUFFER, g_dataBufferObject);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_UNSIGNED_SHORT, GL_FALSE, 8, (void*)0);
glEnableVertexAttribArray(5);
glVertexAttribPointer(5, 2, GL_UNSIGNED_SHORT, GL_TRUE, 8, (void*)4);

glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);</programlisting>
            </example>
            <para>Attribute 0 is our position. We see that the type is not
                    <literal>GL_FLOAT</literal> but <literal>GL_UNSIGNED_SHORT</literal>. This
                matches the C++ type we use. But the attribute taken by the GLSL shader is a
                floating point <type>vec2</type>, not an integer 2D vector (which would be
                    <type>ivec2</type> in GLSL). How does OpenGL reconcile this?</para>
            <para>It depends on the fourth parameter, which defines whether the integer value is
                normalized. If it is set to <literal>GL_FALSE</literal>, then it is not normalized.
                Therefore, it is converted into a float as though by standard C/C++ casting. An
                integer value of 90 is cast into a floating-point value of 90.0f. And this is
                exactly what we want.</para>
            <para>Well, that is what we want to for the position; the texture coordinate is a
                different matter. Normalized texture coordinates should range from [0, 1] (unless we
                want to employ wrapping of some form). To accomplish this, integer texture
                coordinates are often, well, normalized. By passing <literal>GL_TRUE</literal> to
                the fourth parameter (which only works if the third parameter is an integer type),
                we tell OpenGL to normalize the integer value when converting it to a float.</para>
            <para>This normalization works exactly as it does for texel value normalization. Since
                the maximum value of a <type>GLushort</type> is 65535, that value is mapped to 1.0f,
                while the minimum value 0 is mapped to 0.0f. So this is just a slightly fancy way of
                setting the texture coordinates to 1 and 0.</para>
            <para>Note that all of this conversion is <emphasis>free</emphasis>, in terms of
                performance. Indeed, it is often a useful performance optimization to compact vertex
                attributes as small as is reasonable. It is better in terms of both memory and
                rendering performance, since reading less data from memory takes less time.</para>
            <para>OpenGL is just fine with using normalized shorts alongside 32-bit floats,
                normalized unsigned bytes (useful for colors), etc, all in the same vertex data
                (though not within the same <emphasis>attribute</emphasis>). The above array could
                have use <literal>GLubyte</literal> for the texture coordinate, but it would have
                been difficult to write that directly into the code as a C-style array. In a real
                application, one would generally not get meshes from C-style arrays, but from
                files.</para>
        </section>
    </section>
    <section>
        <?dbhtml filename="Tut16 Mipmaps and Linearity.html" ?>
        <title>sRGB and Mipmaps</title>
        <para>The principle reason lighting functions require linear RGB values is because they
            perform linear operations. They therefore produce inaccurate results on non-linear
            colors. This is not limited to lighting functions; <emphasis>all</emphasis> linear
            operations on colors require a linear RGB value to produce a reasonable result.</para>
        <para>One important linear operation performed on texel values is filtering. Whether
            magnification or minification, non-nearest filtering does some kind of linear
            arithmetic. Since this is all handled by OpenGL, the question is this: if a texture is
            in an sRGB format, does OpenGL's texture filtering <emphasis>before</emphasis>
            converting the texel values to linear RGB or after?</para>
        <para>The answer is quite simple: filtering comes after linearizing. So it does the right
            thing.</para>
        <note>
            <para>It's not quite that simple. The OpenGL specification technically leaves it
                undefined. However, if your hardware can run these tutorials without modifications
                (ie: your hardware is OpenGL 3.3 capable), then odds are it will do the right thing.
                It is only on pre-3.0 hardware where this is a problem.</para>
        </note>
        <para>A bigger question is this: do you generate the mipmaps correctly for your textures?
            Mipmap generation was somewhat glossed over in the last tutorial, as tools generally do
            this for you. In general, mipmap generation involves some form of linear operation on
            the colors. For this process to produce correct results for sRGB textures, it needs to
            linearize the sRGB color values, perform its filtering on them, then convert them back
            to sRGB for storage.</para>
        <para>Unless you are writing texture processing tools, this question is answered by asking
            your texture tools themselves. Most freely available texture tools are completely
            unaware of non-linear colorspaces. You can tell which ones are aware based on the
            options you are given at mipmap creation time. If you can specify a gamma for your
            texture, or if there is some setting to specify that the texture's colors are sRGB, then
            the tool can do the right thing. If no such option exists, then it cannot. For sRGB
            textures, you should use a gamma of 2.2, which is what sRGB approximates.</para>
        <note>
            <para>The DDS plugin for GIMP is a good, free tool that is aware of linear colorspaces.
                NVIDIA's command-line texture tools, also free, are as well.</para>
        </note>
        <para>To see how this can affect rendering, load up the <phrase role="propername">Gamma
                Checkers</phrase> project.</para>
        <figure>
            <title>Gamma Checkers</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="Gamma%20Checkers.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>This works like the filtering tutorials. The <keycap>1</keycap> and<keycap>2</keycap>
            keys respectively select linear mipmap filtering and anisotropic filtering (using the
            maximum possible anisotropy).</para>
        <para>We can see that this looks a bit different from the last time we saw it. The distanct
            grey field is much darker than it was. This is because we are using sRGB colorspace
            textures. While the white and black are the same in sRGB (1.0 and 0.0 respectively), a
            50% blend of them (0.5) is not. The sRGB texture assumes the 0.5 color is the sRGB 0.5,
            so it becomes darker than we would expect.</para>
        <para>Initially, we render with no gamma correction. To toggle gamma correction, press the
                <keycap>a</keycap> key. This restores the view to what we saw previously.</para>
        <para>However, the texture we are using is actually wrong. 0.5, as previously stated, is not
            the sRGB color for a 50% blend of black and white. In the sRGB colorspace, that color
            would be ~0.73. The texture is wrong because its mipmaps were not generated in the
            correct colorspace.</para>
        <para>To switch to a texture who's mipmaps were properly generated, press the
                <keycap>g</keycap> key.</para>
        <figure>
            <title>Gamma Correct with Gamma Mipmaps</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="Gamma%20Checkers%20Gamma.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>This still looks different from the last tutorial. Which naturally tells us that not
            rendering with gamma correction before was actually a problem, as this version looks
            much better. The take-home point here is that ensuring linearity in all stages of the
            pipeline is always important. This includes mipmap generation.</para>
    </section>
    <section>
        <?dbhtml filename="Tut16 Free Gamma Correction.html" ?>
        <title>sRGB and the Screen</title>
        <para>Thus far, we have seen how to use sRGB textures to store gamma-corrected images, such
            that they are automatically linearized upon being fetched from a shader. Since the sRGB
            colorspace closely approximates a gamma of 2.2, if we could use an sRGB image as the
            image we render to, we would automatically get gamma correction without having to put it
            into our shaders. But this would require two things: the ability to specify that the
            screen image is sRGB, and the ability to state that we are outputting linear values and
            want them converted to the sRGB colorspace when stored.</para>
        <para>Naturally, OpenGL provides both of these. To see how they work, load up the last
            project, <phrase role="propername">Gamma Landscape</phrase>. This shows off some
            textured terrain with a day/night cycle and a few lights running around.</para>
        <figure>
            <title>Gamma Landscape</title>
            <mediaobject>
                <imageobject>
                    <imagedata fileref="Gamma%20Landscape.png"/>
                </imageobject>
            </mediaobject>
        </figure>
        <para>It uses the standard mouse-based controls to move around. As before, the
                <keycap>1</keycap> and<keycap>2</keycap> keys respectively select linear mipmap
            filtering and anisotropic filtering. The main feature is the non-shader-based gamma
            correction. This is enabled by default and can be toggled by pressing the
                <keycap>SpaceBar</keycap>.</para>
        <formalpara>
            <title>sRGB Screen Image</title>
            <para>The process for setting this up is a bit confusing, but is ultimately quite simple
                for our tutorials. The OpenGL specification specifies how to use the OpenGL
                rendering system, but it does not specify how to <emphasis>create</emphasis> the
                OpenGL rendering system. That is relegated to platform-specific APIs. Therefore,
                while code that uses OpenGL is platform-neutral, that code is ultimately dependent
                on platform-specific initialization code to create the OpenGL context.</para>
        </formalpara>
        <para>These tutorials rely on FreeGLUT for setting up the OpenGL context and managing the
            platform-specific APIs. The <filename>framework.cpp</filename> file is responsible for
            doing the initialization setup work, telling FreeGLUT exactly how we want our screen
            image set up. In order to allow different tutorials to adjust how we set up our FreeGLUT
            screen image, the framework calls the <function>defaults</function> function.</para>
        <example>
            <title>Gamma Landscape defaults Function</title>
            <programlisting language="cpp">unsigned int defaults(unsigned int displayMode, int &amp;width, int &amp;height)
{
    return displayMode | GLUT_SRGB;
}</programlisting>
        </example>
        <para>The <varname>displayMode</varname> argument is a bitfield that contains the standard
            FreeGLUT display mode flags set up by the framework. This function must return that
            bitfield, and all of our prior tutorials have returned it unchanged. Here, we change it
            to include the <literal>GLUT_SRGB</literal> flag. That flag tells FreeGLUT that we want
            the screen image to be in the sRGB colorspace.</para>
        <formalpara>
            <title>Linear to sRGB Conversion</title>
            <para>This alone is insufficient. We must also tell OpenGL that our shaders will be
                writing linear colorspace values and that these values should be converted to sRGB
                before being written to the screen. This is done with a simple
                    <function>glEnable</function> command:</para>
        </formalpara>
        <example>
            <title>Enable sRGB Conversion</title>
            <programlisting language="cpp">if(g_useGammaDisplay)
    glEnable(GL_FRAMEBUFFER_SRGB);
else
    glDisable(GL_FRAMEBUFFER_SRGB);</programlisting>
        </example>
        <para>The need for this is not entirely obvious, especially since we cannot manually turn
            off sRGB-to-linear conversion when reading from textures. The ability to disable
            linear-to-sRGB conversion for screen rendering is useful when we are drawing something
            directly in the sRGB colorspace. For example, it is often useful to have parts of the
            interface drawn directly in the sRGB colorspace, while the actual scene being rendered
            uses color conversion.</para>
        <para>Note that the color conversion is just as free in terms of performance as it is for
            texture reads. So you should not fear using this as much and as often as
            reasonable.</para>
        <para>Having this automatic gamma correction is better than manual gamma correction because
            it covers everything that is written to the screen. In prior tutorials, we had to
            manually gamma correct the clear color and certain other colors used to render solid
            objects. Here, we simply enable the conversion and everything is affected.</para>
        <para>The process of ensuring a linear pipeline from texture creation, through lighting,
            through to the screen is commonly called <glossterm>gamma-correct texturing.</glossterm>
            The name is a bit of a misnomer, as <quote>texturing</quote> is not a requirement; we
            have been gamma-correct since Tutorial 12's introduction of that concept (except for
            Tutorial 15, where we looked at filtering). However, textures are the primary source of
            potential failures to maintain a linear pipeline, as many image formats on disc have no
            way of saying if the image data is in sRGB or linear. So the name still makes some
            sense.</para>
    </section>
    <section>
        <?dbhtml filename="Tut16 In Review.html" ?>
        <title>In Review</title>
        <para>In this tutorial, you have learned the following:</para>
        <itemizedlist>
            <listitem>
                <para>At all times, it is important to remember what the meaning of the data stored
                    in a texture is.</para>
            </listitem>
            <listitem>
                <para>Most of the time, when a texture represents actual colors, those colors are in
                    the sRGB colorspace. An appropriate image format must be selected.</para>
            </listitem>
            <listitem>
                <para>Linear operations like filtering must be performed on linear values. All of
                    OpenGL's operations on sRGB textures do this.</para>
            </listitem>
            <listitem>
                <para>Similarly, the generation of mipmaps, a linear operation, must perform
                    conversion from sRGB to linear RGB, do the filtering, and then convert back.
                    Since OpenGL does not (usually) generate mipmaps, it is incumbent upon the
                    creator of the image to ensure that the mipmaps were generated properly.</para>
            </listitem>
            <listitem>
                <para>Lighting operations need linear values.</para>
            </listitem>
            <listitem>
                <para>The framebuffer can also be in the sRGB colorspace. OpenGL also requires a
                    special enable when doing so, thus allowing for some parts of the rendering to
                    be sRGB encoded and other parts not.</para>
            </listitem>
        </itemizedlist>
        <section>
            <title>OpenGL Functions of Note</title>
            <glosslist>
                <glossentry>
                    <glossterm>glEnable/glDisable(GL_FRAMEBUFFER_SRGB)</glossterm>
                    <glossdef>
                        <para>Enables/disables the conversion from linear to sRGB. When this is
                            enabled, colors written by the fragment shader to an sRGB image are
                            assumed to be linear. They are therefore converted into the sRGB
                            colorspace. When this is disabled, the colors written by the fragment
                            shader are assumed to already be in the sRGB colorspace; they are
                            written exactly as given.</para>
                    </glossdef>
                </glossentry>
            </glosslist>
        </section>
        
    </section>
    <section>
        <?dbhtml filename="Tut16 Glossary.html" ?>
        <title>Glossary</title>
        <glosslist>
            <glossentry>
                <glossterm>sRGB colorspace</glossterm>
                <glossdef>
                    <para>A non-linear RGB colorspace, which approximates a gamma of 2.2. The
                            <emphasis>vast</emphasis> majority of image editing programs and
                        operating systems work in the sRGB colorspace by default. Therefore, most
                        images you will encounter will be in the sRGB colorspace.</para>
                    <para>OpenGL has the ability to work with sRGB textures and screen images
                        directly. Accesses to sRGB textures will return linear RGB values, and
                        writes to sRGB screen images can be converted from linear to sRGB
                        values.</para>
                </glossdef>
            </glossentry>
            <glossentry>
                <glossterm>gamma-correct texturing</glossterm>
                <glossdef>
                    <para>The process of ensuring that all textures, images, and other sources and
                        destinations of colors (vertex attributes), either are in the linear
                        colorspace or are converted to/from the linear colorspace as needed.
                        Textures in the sRGB format are part of that, but so is rendering to an sRGB
                        screen image (or manually doing gamma correction). These provide automatic
                        correction. Manual correction may need to be applied to vertex color
                        attributes.</para>
                </glossdef>
            </glossentry>
        </glosslist>
        
    </section>
</chapter>
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.