Course list http://www.c-jump.com/bcc/
Texture mapping applies an image to the faces of our geometry and adds realism to the scene
This is a lot easier and faster compared to drawing custom geometry and colors, especially when dealing with complicated objects
A texture is an array used as a look-up table for fragment colors
In C++ client:
Load image (e.g. BMP, PNG, JPG, TGA, and so on) from a file into CPU memory, (or generate image programmatically)
Enable texture mapping
Upload texture image to OpenGL
Assign texture coordinates (UV) to vertices
Specify additional texture preferences (image wrapping, etc.)
In fragment shader:
Use texture sampler to access texture object and obtain texel color
A vertex can have:
3 floats specifying (x, y, z) position in 3D space
3 floats specifying a normal vector (x, y, z)
2 floats providing UV coordinates, also known as texture coordinates
Texture coordinates are supplied as part of the vertex attributes:
// Load the UV coordinates: glBufferSubData( GL_ARRAY_BUFFER, 6*NUM_VERTICES*sizeof(GLfloat), 2*NUM_VERTICES*sizeof(GLfloat), UVs );
Texture coordinates are typically normalized, put in range [0,1]
UV coordinates are also known as texel coordinates, or coordinates of the pixels within the texture image.
Thus, a coordinate of 0.5 will always be in the middle of the texture image, regardless of the image size
|
|
// Array of texels, dynamically populated fom an image file: GLubyte* image_data; // Raw color information from a file /* Load image data... (not shown) */ glEnable( GL_TEXTURE_2D ); // Turn on texturing // Create a single texture object: glGenTextures( 1, &tex_buffer_ID ); // Create an ID for a texture buffer glBindTexture( GL_TEXTURE_2D, tex_buffer_ID ); // Bind texture buffer to GL_TEXTURE_2D target glTexImage2D( // allocate storage for the texture and pass data to the texture buffer GL_TEXTURE_2D, 0, GL_RGB, bmpWidth, bmpHeight, 0, GL_BGR, GL_UNSIGNED_BYTE, image_data ); /* | | | | | | | | | | | | | Raw data | | | | | C++ client pixel data type | | | | C++ client pixel data format | | | must be 0, no longer used | | internal format, the number of color components in the texture | mipmapping level: 0 is the base image level target texture type */ tex_coord_ID = glGetAttribLocation( shader_program_ID, "s_vTexCoord" ); glEnableVertexAttribArray( tex_coord_ID ); int texture_coord_offset = 6*NUM_VERTICES*sizeof( GLfloat ); // see example/Lecture17_Texture for details glVertexAttribPointer( tex_coord_ID, 2, GL_FLOAT, GL_FALSE, 0, BUFFER_OFFSET( texture_coord_offset ) ); // Set the preferences: glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST ); texture_ID = glGetUniformLocation( shader_program_ID, "texture" ); const int TEXTURE_UNIT_ZERO = 0; // First, activate texture unit (relative to GL_TEXTURE0) glActiveTexture( GL_TEXTURE0 + TEXTURE_UNIT_ZERO ); // Second, tell "texture" sampler to use the 0th texture unit: glUniform1i( texture_ID, TEXTURE_UNIT_ZERO );
A texture object is configured by the following two calls:
// Create an ID for a texture buffer glGenTextures( 1, &tex_buffer_ID ); // Bind texture buffer to GL_TEXTURE_2D target glBindTexture( GL_TEXTURE_2D, tex_buffer_ID );
Texture unit is a piece of hardware that has access to a texture image
The glActiveTexture() function
const int TEXTURE_UNIT_ZERO = 0; // Turn on texture unit 0: glActiveTexture( GL_TEXTURE0 + TEXTURE_UNIT_ZERO );
changes the current texture unit.
All subsequent texture operations (glBindTexture(), glTexImage2D(), glTexParameterf(), etc.) manipulate the texture bound to the current texture unit.
OpenGL buffer objects are linear arrays of memory, where the GPU keeps a binary-identical copy of our data
The format of a texture image, however, is controlled by the GPU hardware
The image data we provide is transformed into internal OpenGL format
This explains the complexity of glTexImage2D() parameters
Internally, glTexImage2D() executes GPU's pixel transfer operation, which does the conversion
The fragment shader gets texture data by individual texels
How does the shader code accesses the texture object in GPU memory?
GLSL code uses sampler data types to access the texture data
For every OpenGL texture type, there is a corresponding sampler type. For example,
// In fragment shader:
uniform sampler2D texture;
Sampling is a process of fetching data from the texture object. The sampling is done in the fragment shader as part of the lighting computation:
|
|
Note: the return value of GLSL texture() call is a vec4, regardless of the image format of the texture, which may be 1D, 2D, 3D, etc.
Unlike other normal basic types in GLSL,
the sampler can only declared at the global scope as uniform (cannot be a local variable)
the sampler cannot have a value; cannot be used in any arithmetic expressions
the only use of the sampler type is as parameter to a function:
User-defined functions can take sampler as a parameter
A number of built-in functions take samplers as parameters.
OpenGL has an array of texture image units, also known as image units or texture units
One unit represents a single texture
A sampler uniform in the shader has to be connected to a particular image unit
Due to some legacy issues, to associate a texture object with a sampler in the shader requires two steps:
texture_ID = glGetUniformLocation( shader_program_ID, "texture" ); const int TEXTURE_UNIT_ZERO = 0; // First, activate texture unit (relative to GL_TEXTURE0) glActiveTexture( GL_TEXTURE0 + TEXTURE_UNIT_ZERO ); // Second, tell "texture" sampler to use the 0th texture unit: glUniform1i( texture_ID, TEXTURE_UNIT_ZERO );
Also, note that sampler uniforms are considered 1-dimesional (scalar) integer values on the C++ client side, so we call glUniform1i()
Before we made our texture active, we set a number of preferences:
// Set the preferences: glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT );
Here, GL_TEXTURE_WRAP_S and GL_TEXTURE_WRAP_T are texture parameters. They indicate how the texture coordinates should be repeated or clamped to the range of the texture image. The goal is to make sure the shader does not attempt to access texels outside of the actual texture image.
The GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T parameters control the way texture coordinates outside the range [ 0, 1 ] are handled by OpenGL for the S and T texture coordinates.
If UV texture coordinates are out of range [ 0, 1 ], the client program can modify the "wrapping" behavior by calling glTexParameterf(). For example,
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
GL_REPEAT is the texture parameter value. Possible values are
GL_REPEAT // the texture is wrapped and infinitely repeated T ^ GL_MIRRORED_REPEAT // the texture is repeated in a mirrored fashion | GL_CLAMP_TO_EDGE // use pixels on texture edge outside [ 0, 1 ] range | GL_CLAMP_TO_BORDER // use constant border color outside [ 0, 1 ] range +-----> S
Texture parameter values can be illustrated as follows:
GL_CLAMP_TO_EDGE GL_CLAMP_TO_BORDER GL_REPEAT GL_MIRRORED_REPEAT
Note that some texture parameter values can be combined:
glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP | GL_REPEAT );
Ideally, one screen pixel maps to one of the texels in the texture
As the object gets closer to the viewer (magnification),
multiple screen pixels can map to a single texel
Also, what should happen if one screen pixel ends up sampling multiple texels?
If the object moves farther away (minification),
many texels of the image map to a single pixel on the screen
What happens when part of the texture is sampled by a single pixel?
To handle minification and magnification properly, we specify more properties:
// Set the preferences: glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
There are two texture parameter values for GL_TEXTURE_MAG_FILTER and GL_TEXTURE_MAG_FILTER parameters:
|
|
As a textured object moves farther from the viewpoint (minification), the ratio of pixels to texels in the becomes very low.
The texture ends up being sampled at a very low rate, producing low quality rendering
To better handle texture minification mipmapping can be used.
Lower-resolution images can be generated an stored besides the full resolution texture image:
glGenerateMipmap( GL_TEXTURE_2D );
These Lower-resolution images are called mipmaps:
OpenGL automatically picks the most appropriate image to interpolate when texture minification is needed:
Have BMP file extension
Common, and usually aren't compressed
Contain header/meta info followed by pixel data
Stored in "BGR" format
Bottom row of data comes first
Are easy to work with -- many sample loader funtions available online
When loaded into memory the actual data is
GLubyte image[ width ][ height ][ 3 ];
We can open image files and load them into memory
We can upload them from CPU memory into OpenGL texture buffers
We can attach the texture buffer to a variable in the vertex shader
We can access the texels in the fragment shader using a sampler2D