A sampler2D uniform names a texture unit, and a lookup function reads a filtered color from it at coordinates between 0 and 1 (Textures loads the images). GLSL ES 1.00 gave each sampler type its own function names; GLSL ES 3.00 overloads one family on the sampler type:
| GLSL ES 1.00 (WebGL 1) | GLSL ES 3.00 (WebGL 2) | Purpose |
|---|---|---|
| texture2D(s, uv), textureCube(s, dir) | texture(s, uv) | filtered sample, automatic mipmap level |
| texture2DLod(s, uv, lod) (vertex only) | textureLod(s, uv, lod) | explicit mipmap level, any stage |
| texture2DProj(s, uvq) | textureProj(s, uvq) | divide by the last component first |
| (none) | textureGrad, textureOffset, texelFetch, textureSize | derivatives, offsets, integer access |
Porting is mechanical: gl_FragColor = texture2D(uCover, vUV) * uTint; becomes outColor = texture(uCover, vUV) * uTint;. In a fragment shader the GPU picks the mipmap level from the screen-space derivatives of the coordinates, and an optional third argument biases it. A vertex shader has no derivatives, so there texture() reads the base level. Derivatives are undefined inside divergent branches, so sample before an if, not inside it.
<!doctype html>
<style>
body { margin: 0; font: 12px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.names { display: flex; max-width: 600px; text-align: center; font-family: monospace; font-size: 11px; }
.names div { flex: 1; padding: 4px 2px; }
</style>
<canvas id="view" width="600" height="220"></canvas>
<div class="names">
<div>texture(s, uv)</div><div>texture(s, uv, 2.5)</div><div>textureLod(s, uv, 4.0)</div><div>textureProj(s, uvq)</div>
</div>
<script>
// A cover drawn on a 2D canvas: fine text makes the mipmap levels easy to tell apart.
const cover = document.createElement('canvas');
cover.width = cover.height = 256;
const g = cover.getContext('2d');
g.fillStyle = '#5b3f99'; g.fillRect(0, 0, 256, 256);
g.fillStyle = '#e09a10'; g.fillRect(0, 170, 256, 12);
g.fillStyle = '#fff'; g.textAlign = 'center';
g.font = 'bold 30px Georgia, serif'; g.fillText("The", 128, 70);
g.fillText("Clockmaker's", 128, 108); g.fillText('Paradox', 128, 146);
g.font = '14px sans-serif';
for (let i = 0; i < 4; i++) g.fillText('BookNest Classics · BookNest Classics', 128, 206 + i * 14);
const gl = document.getElementById('view').getContext('webgl2');
const vs = `#version 300 es
in vec2 aPos; out vec2 vUV;
void main() { vUV = vec2(aPos.x, 1.0 - aPos.y); gl_Position = vec4(aPos * 2.0 - 1.0, 0.0, 1.0); }`;
const fs = `#version 300 es
precision mediump float;
in vec2 vUV;
uniform sampler2D uCover;
uniform int uMode;
out vec4 outColor;
void main() {
if (uMode == 0) outColor = texture(uCover, vUV); // level chosen from derivatives
else if (uMode == 1) outColor = texture(uCover, vUV, 2.5); // same, biased 2.5 levels blurrier
else if (uMode == 2) outColor = textureLod(uCover, vUV, 4.0);// explicit level 4 (16x16 texels)
else {
float q = 0.6 + 0.8 * vUV.y; // divide by the last component first
outColor = textureProj(uCover, vec3(vUV * q, q));
outColor.rgb *= 0.85 + 0.15 * vUV.y;
}
}`;
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vs], [gl.FRAGMENT_SHADER, fs]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) throw new Error(gl.getShaderInfoLog(s));
gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([0,0, 1,0, 1,1, 0,0, 1,1, 0,1]), gl.STATIC_DRAW);
const aPos = gl.getAttribLocation(program, 'aPos');
gl.enableVertexAttribArray(aPos);
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
gl.bindTexture(gl.TEXTURE_2D, gl.createTexture());
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, cover);
gl.generateMipmap(gl.TEXTURE_2D); // levels 256, 128, ... 1
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
gl.clearColor(0.97, 0.96, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
const uMode = gl.getUniformLocation(program, 'uMode');
for (let i = 0; i < 4; i++) {
gl.viewport(i * 150 + 8, 5, 134, 210);
gl.uniform1i(uMode, i);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
</script>