gl_FragCoord holds a fragment's window coordinates: x and y in drawing-buffer pixels from the bottom-left corner at pixel centers, z the depth from 0 to 1, and w as 1/w of clip space; GLH.evaluate() reports 0.5, 0.5, 0.5, 1 for its one pixel. For points, gl_PointCoord runs from 0 to 1 across the sprite, y pointing down:
<canvas id="c" width="480" height="120" style="width:480px;height:120px"></canvas>
<script src="webgl-helper.js"></script>
<script>
const gl = GLH.paint(document.getElementById('c'), `#version 300 es
precision mediump float; out vec4 c;
void main() {
vec2 cell = floor(gl_FragCoord.xy / 20.0); // 20-pixel squares
float shade = mod(cell.x + cell.y, 2.0) == 0.0 ? 0.93 : 0.85;
c = vec4(vec3(shade) * (0.8 + 0.2 * gl_FragCoord.y / 120.0), 1.0); // lighter upward
}`);
GLH.load(gl, `#version 300 es
void main() { gl_Position = vec4(0, 0, 0, 1); gl_PointSize = 100.0; }`, `#version 300 es
precision mediump float; out vec4 c;
void main() {
if (distance(gl_PointCoord, vec2(0.5)) > 0.5) discard; // round off the square
c = vec4(gl_PointCoord, 0.4, 1.0); // red rightward, green downward
}`);
gl.drawArrays(gl.POINTS, 0, 1);
window.__done = true;
</script>
gl_FragCoord counts drawing-buffer pixels, not CSS pixels (scale by devicePixelRatio before comparing with the mouse), relative to the canvas, not the viewport.
<!doctype html>
<style>
body { margin: 0; background: #f7f4ee; }
canvas { display: block; width: 100%; max-width: 480px; }
</style>
<canvas id="c" width="480" height="160"></canvas>
<script>
const gl = document.getElementById('c').getContext('webgl2');
function load(vsSource, fsSource) {
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, vsSource], [gl.FRAGMENT_SHADER, fsSource]]) {
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);
return program;
}
gl.bindVertexArray(gl.createVertexArray());
// Background: every fragment of a full-canvas triangle, coloured by its window position.
load(`#version 300 es
void main() { gl_Position = vec4(gl_VertexID == 1 ? 3.0 : -1.0, gl_VertexID == 2 ? 3.0 : -1.0, 0.0, 1.0); }`,
`#version 300 es
precision mediump float; out vec4 c;
void main() {
vec2 cell = floor(gl_FragCoord.xy / 20.0); // 20-pixel squares from the bottom left
float shade = mod(cell.x + cell.y, 2.0) == 0.0 ? 0.93 : 0.85;
c = vec4(vec3(shade) * (0.8 + 0.2 * gl_FragCoord.y / 160.0), 1.0); // lighter upward
}`);
gl.drawArrays(gl.TRIANGLES, 0, 3);
// Points: gl_PointCoord runs 0..1 across each sprite, y pointing down.
const points = load(`#version 300 es
uniform float uSize;
void main() { gl_Position = vec4(-0.6 + 0.6 * float(gl_VertexID), 0.0, 0.0, 1.0); gl_PointSize = uSize; }`,
`#version 300 es
precision mediump float; out vec4 c;
void main() {
if (distance(gl_PointCoord, vec2(0.5)) > 0.5) discard; // round off the square
c = vec4(gl_PointCoord, 0.4, 1.0); // red rightward, green downward
}`);
gl.uniform1f(gl.getUniformLocation(points, 'uSize'), 110.0);
gl.drawArrays(gl.POINTS, 0, 3);
</script>