Looking Up Texels
With the texture sitting in VRAM and the 3D model equipped with texture coordinates, we are itching to put the texels on the triangles. A good first step, however, is to visually confirm that the texture coordinates are what we think they should be. We want to see bugs when they are babies, not full-grown monsters.
In our vertex shader, we receive the texture coordinates as an attribute and send them along for interpolation:
// ...
in vec2 texPosition;
out vec2 mixTexPosition;
void main() {
// ...
mixTexPosition = texPosition;
}
// ... in vec2 texPosition; out vec2 mixTexPosition; void main() { // ... mixTexPosition = texPosition; }
In the fragment shader, we color the model using the texture coordinates:
in vec2 mixTexPosition;
out vec4 fragmentColor;
void main() {
fragmentColor = vec4(mixTexPosition, 0.0, 1.0);
}
in vec2 mixTexPosition; out vec4 fragmentColor; void main() { fragmentColor = vec4(mixTexPosition, 0.0, 1.0); }
The red intensity comes from the x-coordinate, and the green intensity from the y-coordinate. Since the texture coordinates are in [0, 1] and so are color intensities, the texture coordinates can be directly interpreted as colors. The texture coordinates are a 2-vector, so we need a third intensity to make an RGB color. 0 is a reasonable choice for the blue intensity. The renderer should reveal a mixture of black, green, red, and yellow, as we see here:
Try dragging the unwrapped vertices around to see the fully saturated red, green, yellow, and black corners.
Once the texture coordinates check out, we are ready to use them in the fragment shader to look up the fragment's texel. The fragment needs to know what texture unit has the texture, so we declare a uniform of type sampler2D
:
uniform sampler2D crateTexture;
uniform sampler2D crateTexture;
In our renderer, we set this uniform to the number of the texture unit. If the crate has been bound to texture unit 0, then we write this code:
shaderProgram.bind();
shaderProgram.setUniform1i('crateTexture', 0);
shaderProgram.unbind();
shaderProgram.bind(); shaderProgram.setUniform1i('crateTexture', 0); shaderProgram.unbind();
The texture unit number is an integer, so setUniform1i
is used instead of setUniform1f
as we've used in the past. If the unit numbers do not change as the application runs, consider setting them once in the initialization routine and never thinking about them again.
The final step is to use the texture
function to pull out the color at the given texture coordinates as a 4-vector and assign it to the fragment:
uniform sampler2D crateTexture;
in vec2 mixTexPosition;
out vec4 fragmentColor;
void main() {
fragmentColor = texture(crateTexture, mixTexPosition);
}
uniform sampler2D crateTexture; in vec2 mixTexPosition; out vec4 fragmentColor; void main() { fragmentColor = texture(crateTexture, mixTexPosition); }
Lighting could be added to this shader. That's what's done in this renderer of a rotatable crate:
All six faces of the box are unwrapped so that they map to the entire crate texture. The color from the texture supplies a fragment's albedo in the diffuse shading calculation. As we rotate the crate, we see the faces pointing away from the light darken.