- In this final segment on OpenGL,
we will be talking about texture mapping.
We will add a texture of wood to the wooden floor,
in order to make the mytest demo more interesting.
This segment will correspond to the mytest3 program.
Let me first show you a demo.
Here I see that the only change is,
that the floor instead of the being white,
now has a wood texture with it.
And I can toggle the texture on and off using the 't' key.
I've defined a bunch of globals to set up the texture.
woodtexture is 256 x 256 RGB texture map.
I have a texture buffer, which is given by texNames,
istex is a parameter for texturing,
islight similarly for lighting,
and I have these texturing and lighting flags
to turn on and off, texturing and lighting.
In the display routine,
I initially set islight to zero to turn off lighting,
except we'll turn it on later for the teapot.
I set the istex flag to texturing, and I draw texture.
So instead of just drawing an object,
or drawing with color for the cubes,
I draw with texture the floor,
and I provided the texture name 0,
which is the texture buffer.
Finally, I set istex to zero,
to ensure that other items are not textured.
The keyboard has some simple toggles,
most notably 't' for turning on and off texturing.
After this, I have to queue up the display routine
at glutPosRedisplay.
's' turns on and off shading,
which we'll not be talking about here,
but you can experiment with it in the program.
Let me show you the demo again,
and I can just turn 't' on and off.
Notice that I can do everything else.
I can zoom in, zoom out.
Let's back up a level,
and talk about the idea of texture mapping.
This really is one of the great inventions
in computer graphics.
For motivation, consider the geometry
of this dinosaur shown here,
and you want to be able to create an image of it,
with realistic colors on it;
which will be done by taking this texture on the right,
and wrapping it on the dinosaur.
Now, if I had to do this in a geometric way,
I would have to make very small polygons,
each of which is colored with the correct
texture map element,
and I would have to have a huge amount of geometry
to represent the dinosaur.
This would slow down my 3D Graphics rendering.
However, with texture mapping,
I can represent the 3D geometry
at a relatively coarse scale,
but I am now adding detail in an image-based way,
having an image which encodes the fine-scale texture detail.
And in this way I can still get by with coarse geometry.
Texture mapping is a very important topic
in practical computer graphics. In almost any application,
almost all of the objects will end up being texture-mapped.
In the real world, essentially all objects have
some kind of texture to them,
whether it's wood grain, whether it's the face texture,
whether it is bricks.
And this adds a level of image-based visual
detail to scenes, which can make scenes
look much more complicated and interesting,
even if the geometry of the scene is very simple.
It can be added in a fragment shader,
and you can see in the left and right images here,
the greater realism of the scene,
once you've added the appropriate texture maps.
Let's talk about setting up the texture.
So we call this inittexture() function.
This function just reads in
a portable pixel map, or ppm file.
PPM files are not very common nowadays,
so you can replace this with an image read
for your favorite file format if you want.
But let me just explain it.
You take in a pointer to the file name,
and the program corresponding to the shader program.
This is just old style C code for reading a file,
fp is the file pointer,
and the ppm format starts with a line
which usually says P6, then it has the width
and height of the file,
and then it has the maximum character value,
which is 255 usually, and a newline.
So we are just skipping this initial part of it,
and then it just has the colors,
where each of the RGB colors is a single character,
in English reading order from left to right,
and I'm just reading that into this wood texture.
In order to do texture mapping,
each vertex must have the texture coordinate.
We saw this earlier, in fact if you remember
when we defined the floor,
that we had the floor, and we had texture coordinates
from the floor, ranging from (0,0) to (1,1).
But each vertex in the general case,
must have a texture coordinate associated with it.
When you interpolate or rasterize the vertices in OpenGL,
it interpolates these texture coordinates
to get a texture coordinate for each pixel,
which will then be used to look up into the texture.
So the first part of this is setting up
the texture coordinates,
so your glGenTextures just sets up one buffer for texNames.
You bind the Vertex Array corresponding to
the vertex array objects for the floor,
you bind the buffer, and this will be the buffers
numobjects*numperobj+ncolors,
so you're adding a buffer corresponding
to the texture information.
And the information of the buffer detail there,
corresponds to the floortex,
which is the information regarding the floor texture.
The buffer data is stored in the same way
as you've done vertices and colors.
Remember that we used layout 0 for the vertices,
and layout 1 for the colors.
We are now going to use layout location 2 for the texture coordinatess.
So you enable the VertexAttribArray(2).
You set the VertexAttribPointer,
and now notice that you have only two elements,
which are the S and T coordinates for the texture.
You also need to define an ActiveTexture,
which is texture 0 in this case.
You need to enable texturing,
and you need to bind to GL_TEXTURE_2D,
the texNames[0].
All of this setup is needed to be able to use the textures.
The next part of this is Specifying the Texture Image.
And this is a command glTexImage2D,
which takes in a number of different arguments.
The target is just GL_TEXTURE_2D.
In principle you could have more interesting versions
like TEXTURE_3D and 4D,
but we're just sticking with standard TEXTURE_2D.
Level is relevant for mip-mapping,
which we are not really getting to in this course,
so it's almost always going to be zero.
The number of components is three for RGB,
it would be four for RGBA.
Width and height must be a power of 2,
if you want things to work more simply.
Border is usually set to zero.
The format will be GL_RGB or GL_RGBA,
and then the type is what is the type of input data.
In our case it's GL_UNSIGNED_BYTE,
but it could also be GL_FLOAT, etc.
So we define the glTexImage2D command
with GL_TEXTURE_2D, 0, GL_RGB,
the width and height of our texture is 256, 256,
GL_RGB, GL_UNSIGNED_BYTE,
and then you give the texture input,
which is woodtexture.
The following lines or parameters
will say how the texture map is accessed,
which is essentially that you have a texture map
at a certain resolution.
But when you map it onto the object geometry,
you will end up either magnifying
or expanding the texture map,
or minifying or reducing the size of the texture map.
The question is, how do you handle this?
And do you do linear interpolation for magnification,
or do you just take nearest point?
And in this case we've specified GL_LINEAR
for both the magnification and minimication filters.
Now one could also use mipmaps,
which are texture maps at a variety
of different resolutions from the finest resolution
to the coarsest resolution.
And in this case you would specify GL_MIPMAP_LINEAR,
which does bilinear filtering on the texture map,
but also linear filtering of mipmap levels,
and that is the most expensive trilinear filtering.
Finally, we have these two parameters,
which is what happens if you go beyond
the range of 0 to 1 in S and T.
Do you clamp to 1 and 0,
or do you repeat, and do you loop around,
then 1.5 comes back to 0.5?
In order to use texture maps in the shader,
we need to define a sampler.
That's what GLint texsampler is doing.
It is getting the location from the program
of this tex variable.
You initially set it to zero,
which could also be the value of GL_TEXTURE0,
so it's saying which texture map if we have several,
should I look up?
And then we set istex to the glGetUniformLocation
of the istex command in the program,
and that will be set appropriately.
Let's now talk about drawing with texture.
We add a function, draw with texture
which will be used for the floor,
which is similar to draw object,
except now it takes as an input, the texture.
So we first bind the TEXTURE_2D to the texture.
You glBindVertexArray for the object,
and now you just draw it.
So the only thing that's different is
that you're binding this texture
corresponding to the texture variable.
Here are the final steps for drawing.
The vertex shader is just passing on the texture coordinates
to the fragment shader.
Notice that layout location 2,
is where the texture coordinates come in.
Those are a vec2, they're just S and T coordinates.
The output from this will be the texture coordinates
going into the shader,
fragment shader, just like positions and normals.
There is this istex.
And now let's look at the main() routine.
You're doing projection * modelview
as before. You set mynormal and myvertex
for the fragment shader.
You set texcoord initially to (0,0),
which is a default value to prevent errors,
and if texturing is turned on,
if istex not equal to zero,
then you simply set it to the input texture coordinates
that you received.
So it's just passing on the texture coordinates
to the fragment shader.
Notice that the OpenGL rasterization hardware
will interpolate texture coordinates
between the vertices and so each pixel
will get the correct interpolated texture coordinate.
The fragment shader in this case is very simple,
although it can be a more complex blend.
So it takes this uniform 2D sampler tex, this istex command,
and it simply says, if (istex > 0) then the
fragColor will be given by this texture
of (tex, texcoord).
That is the relevant part of the fragment shader,
for texturing. Of course, it will also do
other commands for lighting if we are not texturing.
And of course this can be a more complicated blend;
you could do the standard lighting calculation,
modulate with the texture,
but in this case we are just looking up
and using the texture map directly.
Going back to our program,
I can toggle between the texture,
no texturing and texturing.
I can move back and forth,
and I have my scene now,
with my floor, pillars, teapots, with texturing.