OpenGL Textures
CS 481 Lecture, Dr. Lawlor
Before I forget, here's the little "polka dot" texture we built last class:
vec3 v=clamp(1.0*fract(vec3(10.0*worldCoords)),0.0,+1.0)-0.5; // grid
float c=clamp(1.0e1*(length(v)-0.3),0.0,1.0); // radius=0.3 polka dots!
vec4 polka=c*vec4(1,0,0,0)+(1-c)*vec4(0,0,1,0); // red-blue color blend
And here's the funky grid-of-bullseyes:
vec3 v=clamp(1.0*fract(vec3(10.0*worldCoords)),0.0,+1.0)-0.5; // grid
float c=sin(100.0*length(v)); // oscillating function of radius
vec4 funka=c*vec4(1,0,0,0)+(1-c)*vec4(0,0,1,0); // red-blue blend
I've added these to a new "481_polkadot" example on the CS 481 main page. Try it!
Texture File Formats
Generally, there are two classes of file formats out there: simple but
huge, and complicated but small. The simple formats don't do any
real data compression, so they're really easy for programs to read and
write, but they don't do any data compression, so they take up a lot of
space on disk. The complicated formats do data compression, so
they're really unreasonably difficult to read without a dedicated
library, but on the plus side they take much less space on disk.
Typical compressed image formats:
- JPEG, a "lossy" format, uses discrete cosine transform.
- PNG, a lossless format, uses an encoding similar to zip.
Typical simple image formats:
- BMP, a simple format from Microsoft.
- PPM, a simple format popular on UNIX machines.
- TGA, the ancient "Targa" format, that's still suprisingly useful.
It's pretty easy to write code that reads these simple image formats
from disk, since they're all just some sort of small binary header
followed by RGB pixel data. In the 481_texture example program,
I'm using the "image.cpp" functions from Nigel Stewart's GLT library to read images, although I've slightly modified my versions to work outside of the rest of GLT.
|
Name
|
Stores
|
Compression
|
Advantages
|
Disadvantages
|
jpg
|
JPEG
|
RGB
|
Lossy (DCT)
|
Amazingly tight compression, especially for photos.
|
libjpeg is big, and no alpha channel. Sharp edges may ring or fuzz out.
|
png
|
Portable Network Graphics
|
RGBA
|
Lossless
|
Good compression, can represent alpha channel properly.
|
libpng is big, and not present by default on Windows machines.
|
bmp
|
Windows Bitmap
|
RGB
|
None (usually)
|
Builtin editors on Windows. Simple format. Also known as "pcx", which is the same format. Bottom-up.
|
Files are big. No alpha channel.
|
tga
|
TARGA
|
RGBA
|
None (usually; occasionally simple runlength encoding)
|
Simple format--easy to read. See C++ ltga library and docs. Bottom-up (usually).
|
Not completely
standardized. For example, some targas store un-premultiplied
alpha (normal R, G, B, A); others store premultiplied alpha
(RA,GA,BA,A). Files are big.
|
ppm
|
Portable Pixel Map
|
RGB
|
None
|
Very simple ASCII header followed by binary data.
|
ASCII header can include comments. Files are big. No alpha channel.
|
Any decent image editing program, like the GIMP,
can read or write all these formats, including the alpha channel.
Usually, you just convert your textures to whatever your program
supports.
Uploading Texture Data to OpenGL
Once read in, you pass in the texture's pixel data with glTexImage2d or gluBuild2DMipmaps (you need the latter call if you want to use "mipmaps", described below).
Here's a typical call:
gluBuild2DMipmaps(GL_TEXTURE_2D,
GL_RGBA8, /* internal format (on graphics card) */
wid,ht, /* size of passed-in image, in pixels */
GL_RGB, /* format of passed-in data */
GL_UNSIGNED_BYTE, /* data type in our pixel_data array */
&pixel_data[0]);
This call sets the current OpenGL texture to the given image data.
In the fixed-function (non-GLSL) pipeline, you now just have to
glEnable(GL_TEXTURE_2D), and draw some vertices with "texture
coordinates" (they run from 0 to 1 along the X and Y axes):
glEnable(GL_TEXTURE_2D);
glColor4f(1,1,1,1);
glBegin (GL_TRIANGLE_FAN);
glTexCoord2f(0,0); glVertex2d(0,0);
glTexCoord2f(1,0); glVertex2d(1,0);
glTexCoord2f(1,1); glVertex2d(1,1);
glTexCoord2f(0,1); glVertex2d(0,1);
glEnd();
With GLSL, you can have several texture "units" active at once. By default, texture unit 0 is active:
(C++) glActiveTexture(GL_TEXTURE0);
You declare a texture in GLSL as an ordinary "uniform" variable, of the special type "sampler2D":
(GLSL) uniform sampler2D myTex;
From C++, you set this uniform to the integer texture unit number (by default, 0):
(C++) glUniform1iARB(glGetUniformLocationARB(prog,"myTex"),0);
Now in GLSL, you can call "texture2D" on the sampler to read texture colors from a given texture coordinate:
(GLSL) vec4 c = texture2D(myTex, vec2(worldCoords.x,worldCoords.y));
Recall that in GLSL, you are not
limited to simple fixed texture coordinates! By shifting texture
coordinates, you can get a cool reflective effect (environment
mapping), simulate height on a single quad's surface (parallax
mapping), or even look up arbitrary values by treating a texture as a
look-up table.
Of course, you can also do anything you like with the texture
color! I've seen textures containing normals (a "normal map",
useful for bump mapping), textures containing specularity ("specularity
map"), textures that index into other textures (indirection map, or
look-up table), and so on.
Texture Filtering
You can change how colors interpolate between pixels in the
texture map when blowing up the texture map onscreen ("magnification"
mode):
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_NEAREST);
Return the color of the nearest pixel. Results in ugly boxy shapes, but it's really fast, and never blurry.
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
Take
a bilinear blend of neighboring pixels to create output values.
Gives nice smooth output, but may be strange around the outside edge without
GL_CLAMP_TO_BORDER or GL_REPEAT.
The same options exist when shrinking a texture map down onscreen ("minification" mode), along with extra "mipmap" options:
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_LINEAR);
Linearly
interpolate pixels in the mipmaps, and then linearly interpolate
between mipmaps. The smoothest version by far, and the
recommended one to avoid ugly aliasing.
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
Linearly
interpolate pixels in the mipmaps, but only use the closest mipmap
level. Slightly faster than linear-linear on older hardware. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_NEAREST);
Use
nearest-neighbor pixels in the mipmaps, and only use the closest mipmap
level. The ugliest approach, but good for checking for mipmap
problems. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_NEAREST_MIPMAP_LINEAR);
Use
nearest-neighbor pixels in the mipmaps, but blend between mipmap levels. Rarely useful.
You definitely have to just try these things out. Fire up the
example 'texture' code, crank up the scale factor to put a zillion
copies of the texture onscreen, and tilt and rotate.
Texture Coordinate Wrapping
You can change what happens outside normal texture coordinate bounds,
on both the S (x axis) and T (y axis) texture coordinate axes. With
programmable shaders, this isn't as important as with fixed-function.
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
Works like fract(texCoords). Texture coordinates wrap back around to
0.0 when they exceed 1.0, which causes the texture to repeat. This is
the most common wrap mode.
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
Works like clamp(texCoords,0.0,1.0). Texture coordinates outside the normal range are flattened to 0.0-1.0. - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
Works like clamp(texCoords,0.0+0.5/pixelSize,1.0-0.5/pixelSize), where
"pixelSize" is the corresponding dimension of the texture in *texture*
pixels. This has the effect of extending out the edge
pixels. With GL_NEAREST filtering, it's identical to GL_CLAMP,
but it's different for GL_LINEAR.
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
Works like clamp(texCoords,0.0-0.5/pixelSize,1.0+0.5/pixelSize).
The edge pixels linearly blend down to the "border color" (usually a
transparent black), which then extends outward. This is
probably what GL_CLAMP should have been.