webgl textures
Introduction
Greetings, WebGL ninjas. The fourth tutorial is upon you, so be prepared: keep your browsers sharp and your scripts quick, and meld into the shadows, ready to strike! The striking will of course involve writing some cool WebGL, rather than infiltrating a shogunβs palace, but I think thatβs ninja enough for us. This week we will turn our attention to importing textures from an image file into the canvas.
This article is a transcript of time 56:50 to 1:03:20 in Erik MΓΆllerβs WebGL 101 tutorial, available on YouTube.
Note: [05-texturing.html You can run the finished texture example] from this article to see what you are aiming towards.
Preparing to use textures in WebGL
Weβre going to build this example on top of the simple shader in our WebGL tutorials. So take a copy of 03 Minimal Shader file and save it as 05-texturing.html.
Weβll also need an image to use as texture, so copy this Opera logo PNG file into a place in your working directory.
Now weβre going to remove the offset as weβre not going to be using it here. To do so, first go into the vertex shader <script> element and remove the following line:
uniform vec2 uOffset;
Next, remove the reference to it in the vTexCoord = aVertexPosition + uOffset; line below it to just leave this:
vTexCoord = aVertexPosition;
And finally, back in the main script delete the offset array line:
var offset = [1, 1];
and the two lines that link the offset to the program:
program.offsetUniform = gl.getUniformLocation(program, 'uOffset');
and
gl.uniform2f(program.offsetUniform, offset[0], offset[1]);
Now weβre ready to start working on our texture.
Creating the texture
First, letβs create a uniform by going into the fragment shader <script> element and putting the following line just above the main() function:
uniform sampler2D uSampler;
sampler2D allows to sample pixel colours from a 2D image.
Now inside the main() function of the fragment shader, we need to change the gl_FragColor line to call the texture lookup function texture2D on the sampler as we want to get the colour information from the texture:
gl_FragColor = texture2D(uSampler, vTexCoord);
Thatβs it for the fragment shader. Now we need to modify the main <script> to pull in the sample texture. So, below
program.vertexPosAttrib = gl.getAttribLocation(program, 'aVertexPosition');
add:
program.samplerUniform = gl.getUniformLocation(program, 'uSampler');
And now, always inside the main <script>, we need to create the texture we are going to use, give it an image, and create our drawing function.
So first, weβll create a new texture and store it in a variable:
var texture = gl.createTexture();
Next, we create a new image variable to store our image in:
var img = new Image();
and specify the location of the image:
img.src = 'textures/opera.png';
The drawing function
Now, right above the last line you added, weβll add our drawing function, which will run when the image has finished loading. Our initial drawing function will look like this:
img.onload = function() {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.uniform1i(program.samplerUniform, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, vertexPosBuffer.numItems);
}
The first line sets TEXTURE0 as the active texture. This line is not strictly necessary because TEXTURE0 is the default active texture anyway, but Iβve included it so you have it there if you need it for reference in the future.
The second line binds the texture we created earlier.
The third line, tells the shader which texture weβre going to be using. WebGL can handle up to 32 textures during any given call to drawing functions (like gl.drawArrays), theyβre called TEXTURE0 to TEXTURE31. 1i means that the uniform will accept a single integer, and that integer is set to zero because weβre going to be using texture 0.
The fourth line is the same youβre already familiar with from our previous lesson.
Now we have a drawing function to play with, letβs add to it.
Right after the second line of our drawing function, create a new line and enter the following to it:
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
This sets how you should read the source data when itβs set with texImage2d (on the next line). Specifically, weβre flipping the Y-axis (or flipping the image around the X-axis) so the texture coordinates will have Ys that increase as you go up the screen when you use images in formats where Y-coordinates increase downwards (like the .png weβre using here).
On the next line, we will actually specify our image data and parameters (using texImage2d). We are specifying that itβs 2D and that it is using RGBA colour and each component in every pixel β r, g, b and a β is specified as un unsigned byte (ranging from 0 to 255). Finally we specify img, the variable where our image is stored:
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img)
Finally, for this function, we want to specify some texture parameters, to use for drawing the image. Put these lines underneath the previous one:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
Mag filter is used when youβre stretching an image over a larger area than the original source image and you need to figure out whatβs "in-between pixels". You can either linearly interpolate between the colours, or use the same value as the nearest pixel to it (resulting in more blockiness.)
Also, change your canvas size to something a bit bigger so you can see what is going on more clearly. I chose 900 x 900. Now try testing your code β you should be presented with something that looks like Figure 1.

Figure 1: A series of Opera company logos rendered inside a <canvas> using WebGL.
Changing the filter
Youβll notice that currently the rendering looks a bit pixellated, because we chose a NEAREST filter. To see what difference the different filters make, try using the LINEAR filter instead:
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
This will give a much smoother result, as shown in Figure 2.

Figure 2: The linear filter gives a much smoother result.
Wrap modes
Right now we are displaying four versions of the Opera logo. This is because we are currently indexing our image with values from -1 to 1, and the wrap mode is by default set to wrap content when it goes outside the range 0 to 1. So weβre drawing one βOβ for -1 to 0 and one for 0 to 1 in each dimension.
Letβs update the code to just display a single copy of the image. First, add a solid border to the canvas so it is easier to visualise where the canvas is, and what is going on:
<canvas id='c' width='900' height='900' style="border: 1px solid black;">
</canvas>
Letβs explore how wrap modes work. Weβre drawing a quad that covers the entire canvas and weβre texturing it with a "O". The quad coordinates range from -1 to 1 along both the x and y axes. To draw only one βOβ you should have coordinates going from 0 to 1. If we change to clamp in one direction youβll only see two Oβs: the clamped axis will not draw a O in the -1 to 0 range. If you clamp both axes, youβll only see one O drawn. Change your texParameter lines to the following:
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);
S and T are the texture coordinate axes. Note that our axes start at (0,0) β the bottom left of a texture β and go to (1,1) β the top right.
This will give us the display shown in Figure 3.

Figure 3: Our four Opera logos have been reduced down to one.
Summary
Thatβs it this for this week, my dear WebGL ninjas. Next time we will start ramping it up again, looking at how we can further improve our shaders with XHR.
This article is licensed under a Creative Commons Attribution 3.0 Unported license.