7. Texturing in GLSL

GLSL Texturing

Textures bring life and detail to your 3D objects, allowing you to map 2D images onto surfaces. In this tutorial, we’ll explore how to integrate textures into GLSL shaders, covering key concepts like the use of sampler2D and texture coordinates.

Introduction to sampler2D

In GLSL, textures are handled using samplers, which act as handles for accessing texture data in shaders. A sampler2D is specifically designed for 2D textures, allowing you to sample the color of a texture at a specific coordinate.

It’s worth noting that GLSL also supports sampler1D, sampler3D, and specialized samplers for shadow maps and cube maps, but in this tutorial, we’ll focus on sampler2D to keep things simple.

Main Program

Our main program will:
1. Load a texture.
2. Bind the texture to the shaders.
3. Pass texture coordinates to the vertex and fragment shaders.

Here’s the complete implementation:

#if (defined(__MACH__) && defined(__APPLE__))
#include <cstdlib>
#include <OpenGL/gl.h>
#include <GLUT/glut.h>
#include <OpenGL/glext.h>
#else
#include <cstdlib>
#include <GL/glew.h>
#include <GL/gl.h>
#include <GL/glut.h>
#include <GL/glext.h>
#endif

#include "shader.h"

Shader shader;    // Shader program
GLuint texture;   // Texture ID

GLfloat angle = 0.0;  // Rotation angle for teapot

GLuint LoadTexture(const char *filename, int width, int height) {
    GLuint texture;
    unsigned char *data;
    FILE *file;

    // Load RAW image data
    file = fopen(filename, "rb");
    if (file == NULL) return 0;
    data = (unsigned char *)malloc(width * height * 3);
    fread(data, width * height * 3, 1, file);
    fclose(file);

    // Generate and bind texture
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

    // Set texture parameters
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);

    // Generate texture
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    free(data);

    return texture;
}

void init(void) {
    glEnable(GL_DEPTH_TEST);  // Enable depth testing
    glEnable(GL_LIGHTING);    // Enable lighting
    glEnable(GL_LIGHT0);      // Enable light source 0

    shader.init("shader.vert", "shader.frag"); // Initialize shaders
    texture = LoadTexture("texture.raw", 256, 256); // Load texture
}

void cube(void) {
    glRotatef(angle, 1.0, 0.0, 0.0); // Rotate on the X-axis
    glRotatef(angle, 0.0, 1.0, 0.0); // Rotate on the Y-axis
    glRotatef(angle, 0.0, 0.0, 1.0); // Rotate on the Z-axis

    // Set active texture
    glActiveTexture(GL_TEXTURE0);

    // Get and set uniform texture location in shader
    int texture_location = glGetUniformLocation(shader.id(), "color_texture");
    glUniform1i(texture_location, 0);

    // Bind the texture
    glBindTexture(GL_TEXTURE_2D, texture);

    glutSolidTeapot(2.0); // Render teapot
}

void display(void) {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // Clear buffers
    glLoadIdentity();
    gluLookAt(0.0, 0.0, 5.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0); // Set camera

    shader.bind();
    cube();
    shader.unbind();

    glutSwapBuffers();
    angle += 0.01f; // Increment rotation angle
}

void reshape(int w, int h) {
    glViewport(0, 0, (GLsizei)w, (GLsizei)h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(60, (GLfloat)w / (GLfloat)h, 1.0, 100.0);
    glMatrixMode(GL_MODELVIEW);
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH); // Enable double buffering and depth
    glutInitWindowSize(500, 500);
    glutCreateWindow("GLSL Texturing");

    glewInit();  // Initialize GLEW
    init();

    glutDisplayFunc(display);
    glutIdleFunc(display);
    glutReshapeFunc(reshape);

    glutMainLoop();
    return 0;
}

Vertex Shader

The vertex shader’s role is to pass texture coordinates from OpenGL to the fragment shader. To do this, it writes the texture coordinates into gl_TexCoord[0].

How It Works:

1. Reading Texture Coordinates:
The variable gl_MultiTexCoord0 holds the texture coordinates supplied by OpenGL for the first texture unit (active texture 0).

2. Writing to gl_TexCoord:
The vertex shader writes the texture coordinates to gl_TexCoord[0], making them available to the fragment shader.

3. Result:
By passing the coordinates from gl_MultiTexCoord0 to gl_TexCoord[0], we allow GLSL to handle multitexturing setups seamlessly.

Here’s the full vertex shader:

void main() {
    // Pass texture coordinates to the fragment shader
    gl_TexCoord[0] = gl_MultiTexCoord0;

    // Transform vertex position to clip space
    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
}

Fragment Shader

The fragment shader uses the sampler2D uniform variable to sample the texture color and applies it to the object.

Key Points:

1. Defining the Texture Variable:
The line uniform sampler2D color_texture; declares a handle to the texture, allowing us to sample its color in the shader.

2. Sampling the Texture:
The function texture2D(color_texture, gl_TexCoord[0].st) fetches the color of the texture at the given texture coordinates.

3. Returning the Final Color:
The sampled texture color, a vec4 containing RGBA values, is assigned to gl_FragColor for rendering.

Here’s the complete fragment shader:

uniform sampler2D color_texture; // 2D texture uniform

void main() {
    // Sample texture and assign it as the fragment color
    gl_FragColor = texture2D(color_texture, gl_TexCoord[0].st);
}

Download Links

If you have any questions, feel free to email me at swiftless@gmail.com.

  • March 25, 2010
  • 19