Applying a 2D texture

In GLSL, applying a texture to a surface involves accessing texture memory to retrieve a color associated with a texture coordinate, and then applying that color to the output fragment. The application of the color to the output fragment could involve mixing the color with the color produced by a shading model, simply applying the color directly, using the color in the reflection model, or some other mixing process. In GLSL, textures are accessed via sampler variables. A sampler variable is a "handle" to a texture unit. It is typically declared as a uniform variable within the shader and initialized within the main OpenGL application to point to the appropriate texture unit.

In this recipe, we'll look at a simple example involving the application of a 2D texture to a surface as shown in the following image. We'll use the texture color to scale the color provided by the ambient, diffuse, and specular (ADS) reflection model. The following image shows the results of a brick texture applied to a cube. The texture is shown on the right and the rendered result is on the left.

Applying a 2D texture

Getting ready

Set up your OpenGL application to provide the vertex position in attribute location 0, the vertex normal in attribute location 1, and the texture coordinate in attribute location 2. The parameters for the ADS reflection model are declared again as uniform variables within the shader, and must be initialized from the OpenGL program. Make the handle to the shader available in a variable named programHandle.

How to do it...

To render a simple shape with a 2D texture, use the following steps:

  1. In your initialization of the OpenGL application, use the following code to load the texture. (The following makes use of a simple TGA image loader, provided with the sample code.)
    GLint width, height;
    GLubyte * data = TGAIO::read("brick1.tga", width, height);
    
    // Copy file to OpenGL
    glActiveTexture(GL_TEXTURE0);
    GLuint tid;
    glGenTextures(1, &tid);
    glBindTexture(GL_TEXTURE_2D, tid);
    glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, w, h);
    glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, 
                      GL_RGBA, GL_UNSIGNED_BYTE, data);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, 
                    GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, 
                    GL_LINEAR);
    
    delete [] data;
    
    // Set the Tex1 sampler uniform to refer to texture unit 0
    int loc = glGetUniformLocation(programHandle, "Tex1");
    if( loc >= 0 )
      glUniform1i(loc, 0);
  2. Use the following code for the vertex shader:
    layout (location = 0) in vec3 VertexPosition;
    layout (location = 1) in vec3 VertexNormal;
    layout (location = 2) in vec2 VertexTexCoord;
    
    out vec3 Position;
    out vec3 Normal;
    out vec2 TexCoord;
    
    uniform mat4 ModelViewMatrix;
    uniform mat3 NormalMatrix;
    uniform mat4 ProjectionMatrix;
    uniform mat4 MVP;
    
    void main()
    {
      TexCoord = VertexTexCoord;
      Normal = normalize( NormalMatrix * VertexNormal);
      Position = vec3( ModelViewMatrix * 
                        vec4(VertexPosition,1.0) );
    
      gl_Position = MVP * vec4(VertexPosition,1.0);
    }
  3. Use the following code for the fragment shader:
    in vec3 Position;
    in vec3 Normal;
    in vec2 TexCoord;
    
    uniform sampler2D Tex1;
    
    struct LightInfo {
      vec4 Position;  // Light position in eye coords.
      vec3 Intensity; // A,D,S intensity
    };
    uniform LightInfo Light;
    
    struct MaterialInfo {
      vec3 Ka;            // Ambient reflectivity
      vec3 Kd;            // Diffuse reflectivity
      vec3 Ks;            // Specular reflectivity
      float Shininess;    // Specular shininess factor
    };
    uniform MaterialInfo Material;
    
    layout( location = 0 ) out vec4 FragColor;
    
    void phongModel( vec3 pos, vec3 norm, out vec3 ambAndDiff, out vec3 spec ) {
        // Compute the ADS shading model here, return ambient
        // and diffuse color in ambAndDiff, and return specular
        // color in spec
      
    }
    void main() {
      vec3 ambAndDiff, spec;
      vec4 texColor = texture( Tex1, TexCoord );
      phongModel(Position, Normal, ambAndDiff, spec);
      FragColor = vec4(ambAndDiff, 1.0) * texColor + 
                    vec4(spec, 1.0);
    }

How it works...

The first code segment demonstrates the steps needed to load the texture from a file, copy the texture data to OpenGL memory, and initialize the sampler variable within the GLSL program. The first step, loading the texture image file, is accomplished via a simple TGA image loader that is provided along with the example code (TGAIO::read()). It reads the image data from a file in the TGA format, and stores the data into an array of unsigned bytes in RGBA order. The width and height of the image are returned via the last two parameters. We keep a pointer to the image data, simply named data.

Note

The TGA format is simple and easy to understand, it is free of any encumbering patents, and supports true color images with an alpha channel. This makes it a very convenient format for texture reading/writing. If your images are not in that format, just grab a copy of ImageMagick and convert. However, the TGA format is not very memory efficient. If you want to load images stored in other formats, there are a variety of other options. For example, check out ResIL (http://resil.sourceforge.net/), or Freeimage (http://freeimage.sourceforge.net/).

Experienced OpenGL programmers should be familiar with the next part of the code. First, we call glActiveTexture to set the current active texture unit to GL_TEXTURE0 (the first texture unit, also called a texture channel). The subsequent texture state calls will be effective on texture unit zero. The next two lines involve creating a new texture object by calling glGenTextures.. The handle for the new texture object is stored in the variable tid. Then, we call glBindTexture to bind the new texture object to the GL_TEXTURE_2D target. Once the texture is bound to that target, we allocate immutable storage for the texture with glTexStorage2D. After that, we copy the data for that texture into the texture object using glTexSubImage2D. The last argument to this function is a pointer to the raw data for the image.

The next steps involve setting the magnification and minimization filters for the texture object using glTexParameteri. For this example, we'll use GL_LINEAR.

Note

The texture filter setting determines whether any interpolation will be done prior to returning the color from the texture. This setting can have a strong effect on the quality of the results. In this example, GL_LINEAR indicates that it will return a weighted average of the four texels that are nearest to the texture coordinates. For details on the other filtering options, see the OpenGL documentation for glTexParameteri: http://www.opengl.org/wiki/GLAPI/glTexParameter.

Next, we delete the texture data pointed to by data. There's no need to hang on to this, because it was copied into texture memory via glTexSubImage2D.

Finally, we set the uniform variable Tex1 in the GLSL program to zero. This is our sampler variable. Note that it is declared within the fragment shader with type sampler2D. Setting its value to zero indicates to the OpenGL system that the variable should refer to texture unit zero (the same one selected previously with glActiveTexture).

The vertex shader is very similar to the one used in previous examples except for the addition of the texture coordinate input variable VertexTexCoord, which is bound to attribute location 2. Its value is simply passed along to the fragment shader by assigning it to the shader output variable TexCoord.

The fragment shader is also very similar to those used in the recipes of previous chapters. The important parts for the purpose of this recipe involve the variable Tex1. Tex1 is a sampler2D variable that was assigned by the OpenGL program to refer to texture unit zero. In the main function, we use that variable along with the texture coordinate (TexCoord) to access the texture. We do so by calling the built-in function texture. This is a general purpose function, used to access a texture. The first parameter is a sampler variable indicating which texture unit is to be accessed, and the second parameter is the texture coordinate used to access the texture. The return value is a vec4 containing the color obtained by the texture access (stored in texColor), which in this case is an interpolated value with the four nearest texture values (texels).

Next, the shading model is evaluated by calling phongModel and the results are returned in the parameters ambAndDiff and spec. The variable ambAndDiff contains only the ambient and diffuse components of the shading model. A color texture is often only intended to affect the diffuse component of the shading model and not the specular. So we multiply the texture color by the ambient and diffuse components and then add the specular. The final sum is then applied to the output fragment FragColor.

There's more...

There are several choices that could be made when deciding how to combine the texture color with other colors associated with the fragment. In this example, we decided to multiply the colors, but one could have chosen to use the texture color directly, or to mix them in some way based on the alpha value.

Another choice would be to use the texture value as the value of the diffuse and/or specular reflectivity coefficient(s) in the Phong reflection model. The choice is up to you!

Specifying the sampler binding within GLSL

As of OpenGL 4.2, we now have the ability to specify the default value of the sampler's binding (the value of the sampler uniform) within GLSL. In the previous example, we of set the value of the uniform variable from the OpenGL side using the following code:

int loc = glGetUniformLocation(programHandle, "Tex1");
if( loc >= 0 )
  glUniform1i(loc, 0);

Instead, if we're using OpenGL 4.2, we can specify the default value within the shader, using the layout qualifier as shown in the following statement:

layout (binding=0) uniform sampler2D Tex1;

Thus simplifying the code on the OpenGL side, and making one less thing we need to worry about. The example code that accompanies this book uses this technique to specify the value of Tex1, so take a look there for a more complete example. We'll also use this layout qualifier in the following recipes.

See also

  • For more information about sending data to a shader via vertex attributes refer the Sending data to a shader using vertex attributes and vertex buffer objects recipe in Chapter 1, Getting Started with GLSL
  • The Using per-fragment shading for improved realism recipe in Chapter 3, Lighting, Shading and Optimization
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.222.119.148