Texture coordinates are proportions rather than absolute column and row indices. Proportions are usually in [0, 1]. What happens if you let the coordinates wander outside this range? This square's texture coordinates span [-1, 1.5]:
By default, out-of-bounds texture coordinates cause the texture to repeat. This effect is often used to make a single image tile across a floor or wall. The graphics card maps the coordinate back into the [0, 1] range by effectively subtracting away the floor of the coordinate:
$$ c' = c - \lfloor c \rfloor $$
For example, 1.6 maps to 0.6 because \(1.6 - 1 = 0.6\). -2.3 becomes 0.7 because \(-2.3 - -3 = 0.7\). Probably the graphics card implements this not with subtraction, but with some fast hardware instructions.
You can make this default behavior explicit by setting the texture's wrapping parameters to gl.REPEAT
:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
Physical space is often described as having x-, y-, and z-axes. To distinguish texture space from this physical space, some graphics developers say the texture's horizontal axis is the s-axis and the vertical axis is the t-axis. Thus the parameters are named gl.TEXTURE_WRAP_S
and gl.TEXTURE_WRAP_T
. The horizontal and vertical wrapping are independent, which is why two calls are needed.
You should only repeat a texture if it was designed to repeat seamlessly. Otherwise you will see jarring discontinuities between the repetitions. If changing the texture is too much work, you can switch to mirrored repetition:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.MIRRORED_REPEAT);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.MIRRORED_REPEAT);
See this in action by switching the renderer above to use mirrored repetition.
Sometimes you don't want any kind of repetition. Rather you want to clamp the coordinates to [0, 1], with any smaller coordinate forced to 0 and any larger coordinate forced to 1. If you had to implement this clamping in software, you might write this statement:
$$ c' = \min(1, \max(0, c)) $$
However, the graphics card will do this in hardware if you set the wrapping parameters to gl.CLAMP_TO_EDGE
:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
See this in action by switching the renderer above to use clamping.