I am learning WebGL and I\'ve drawn a full screen quad with colors for each vertex. No lighting or normals or perspective matrix or depth buffer; I\'m just drawing a gradient ba
The issue is the top right triangle has no knowledge of the bottom left corner so the top right triangle is not including any of the blue from the bottom left (and visa versa)
A couple of ways to fix that.
One is to use a 2x2 texture with linear sampling. You have to do some extra math to get the interpolation correct because a texture only interpolates between pixels
+-------+-------+
| | |
| +-------+ |
| | | | |
+---|---+---|---+
| | | | |
| +-------+ |
| | |
+-------+-------+
Above is a 4 pixel texture stretched to 14 by 6. Sampling happens between pixels so only this center area will get the gradient. Outside that area would be sampled with pixels outside the texture so using CLAMP_TO_EDGE or on the opposite side of the texture using REPEAT.
const gl = document.querySelector('canvas').getContext('webgl');
const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGB, // internal format
2, // width,
2, // height,
0, // border
gl.RGB, // format
gl.UNSIGNED_BYTE, // type
new Uint8Array([...bl, ...br, ...tl, ...tr]));
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
const vec2 texSize = vec2(2, 2); // could pass this in
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex,
(v_texcoord * (texSize - 1.0) + 0.5) / texSize);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');
function createBufferAndSetupAttribute(loc, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(
loc,
2, // 2 elements per iteration
gl.FLOAT, // type of data in buffer
false, // normalize
0, // stride
0, // offset
);
}
createBufferAndSetupAttribute(positionLoc, [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]);
gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
Note: to see what I mean about the extra math needed for the texture coordinates here is the same example without the extra math
const gl = document.querySelector('canvas').getContext('webgl');
const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGB, // internal format
2, // width,
2, // height,
0, // border
gl.RGB, // format
gl.UNSIGNED_BYTE, // type
new Uint8Array([...bl, ...br, ...tl, ...tr]));
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');
function createBufferAndSetupAttribute(loc, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(
loc,
2, // 2 elements per iteration
gl.FLOAT, // type of data in buffer
false, // normalize
0, // stride
0, // offset
);
}
createBufferAndSetupAttribute(positionLoc, [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]);
gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
Also of course, rather than do the math in the fragment shader we could fix the texture coordinates in JavaScript
const gl = document.querySelector('canvas').getContext('webgl');
const tl = [254, 217, 138];
const tr = [252, 252, 252];
const bl = [18, 139, 184];
const br = [203, 79, 121];
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGB, // internal format
2, // width,
2, // height,
0, // border
gl.RGB, // format
gl.UNSIGNED_BYTE, // type
new Uint8Array([...bl, ...br, ...tl, ...tr]));
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);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex;
void main() {
gl_FragColor = texture2D(tex, v_texcoord);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');
function createBufferAndSetupAttribute(loc, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(
loc,
2, // 2 elements per iteration
gl.FLOAT, // type of data in buffer
false, // normalize
0, // stride
0, // offset
);
}
createBufferAndSetupAttribute(positionLoc, [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
0.25, 0.25,
0.75, 0.25,
0.25, 0.75,
0.25, 0.75,
0.75, 0.25,
0.75, 0.75,
]);
gl.useProgram(program);
// note: no need to set sampler uniform as it defaults
// to 0 which is what we'd set it to anyway.
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }
Another way is to do the interpolation yourself based on those corners (which is effectively doing what the texture sampler is doing in the previous example, bi-linear interpolation of the 4 colors).
const gl = document.querySelector('canvas').getContext('webgl');
const tl = [254/255, 217/255, 138/255];
const tr = [252/255, 252/255, 252/255];
const bl = [ 18/255, 139/255, 184/255];
const br = [203/255, 79/255, 121/255];
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = texcoord;
}
`;
const fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform vec3 tl;
uniform vec3 tr;
uniform vec3 bl;
uniform vec3 br;
void main() {
vec3 l = mix(bl, tl, v_texcoord.t);
vec3 r = mix(br, tr, v_texcoord.t);
vec3 c = mix(l, r, v_texcoord.s);
gl_FragColor = vec4(c, 1);
}
`;
const program = twgl.createProgram(gl, [vs, fs]);
const positionLoc = gl.getAttribLocation(program, 'position');
const texcoordLoc = gl.getAttribLocation(program, 'texcoord');
const tlLoc = gl.getUniformLocation(program, 'tl');
const trLoc = gl.getUniformLocation(program, 'tr');
const blLoc = gl.getUniformLocation(program, 'bl');
const brLoc = gl.getUniformLocation(program, 'br');
function createBufferAndSetupAttribute(loc, data) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(data), gl.STATIC_DRAW);
gl.enableVertexAttribArray(loc);
gl.vertexAttribPointer(
loc,
2, // 2 elements per iteration
gl.FLOAT, // type of data in buffer
false, // normalize
0, // stride
0, // offset
);
}
createBufferAndSetupAttribute(positionLoc, [
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]);
createBufferAndSetupAttribute(texcoordLoc, [
0, 0,
1, 0,
0, 1,
0, 1,
1, 0,
1, 1,
]);
gl.useProgram(program);
gl.uniform3fv(tlLoc, tl);
gl.uniform3fv(trLoc, tr);
gl.uniform3fv(blLoc, bl);
gl.uniform3fv(brLoc, br);
gl.drawArrays(gl.TRIANGLES, 0, 6);
canvas { border: 1px solid black; }