Skip to content

Instantly share code, notes, and snippets.

@greggman
Created May 2, 2022 17:44
Show Gist options
  • Save greggman/398b5b5505ad1938a8cae6201f9e5d28 to your computer and use it in GitHub Desktop.
Save greggman/398b5b5505ad1938a8cae6201f9e5d28 to your computer and use it in GitHub Desktop.
WebGL - 2 Textures - with a video (transparency) with gui

WebGL - 2 Textures - with a video (transparency) with gui

view on jsgist

@import url("https://webglfundamentals.org/webgl/resources/webgl-tutorials.css");
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
background-image: url(https://mdn.github.io/dom-examples/canvas/chroma-keying/media/foo.png);
}
<button type="button">click to play</button>
<canvas id="canvas"></canvas>
<!-- vertex shader -->
<script id="vertex-shader-2d" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="fragment-shader-2d" type="x-shader/x-fragment">
precision mediump float;
// our textures
uniform sampler2D u_image;
uniform vec3 minColor;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_image, v_texCoord);
if (all(lessThan(color.rgb, minColor))) {
color = vec4(0);
}
gl_FragColor = color;
// premultiply alpha
gl_FragColor.rgb *= gl_FragColor.a;
}
</script><!--
for most samples webgl-utils only provides shader compiling/linking and
canvas resizing because why clutter the examples with code that's the same in every sample.
See https://webglfundamentals.org/webgl/lessons/webgl-boilerplate.html
and https://webglfundamentals.org/webgl/lessons/webgl-resizing-the-canvas.html
for webgl-utils, m3, m4, and webgl-lessons-ui.
-->
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
// WebGL - 2 Textures
// from https://webglfundamentals.org/webgl/webgl-2-textures.html
import GUI from 'https://cdn.jsdelivr.net/npm/lil-gui@0.16/+esm';
function loadVideo(url) {
return new Promise((resolve, reject) => {
var video = document.createElement('video');
video.autoplay = true;
requestCORSIfNotSameOrigin(video, url)
video.src = url;
video.volume = 0;
video.loop = true;
video.addEventListener('playing', waitForFrame(video, resolve));
video.onerror = reject;
video.play();
return video;
});
}
function waitForFrame(video, resolve) {
const id = setInterval(() => {
if (video.currentTime > 0.1 && video.videoWidth > 0) {
clearInterval(id);
resolve(video);
}
});
}
async function loadImage(url) {
const img = new Image();
requestCORSIfNotSameOrigin(img, url)
img.src = url;
await img.decode();
return img;
}
async function main() {
const images = await Promise.all([
loadVideo("https://mdn.github.io/dom-examples/canvas/chroma-keying/media/video.mp4"),
]);
render(images);
}
const b = document.querySelector('button');
b.addEventListener('click', () => {
b.style.display = 'none';
main();
});
function render(images) {
// Get A WebGL context
/** @type {HTMLCanvasElement} */
var canvas = document.querySelector("#canvas");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = webglUtils.createProgramFromScripts(gl, ["vertex-shader-2d", "fragment-shader-2d"]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
// Create a buffer to put three 2d clip space points in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
setRectangle(gl, 0, 0, images[0].videoWidth, images[0].videoHeight);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]), gl.STATIC_DRAW);
const minColor = [10, 10, 10];
const gui = new GUI();
gui.add(minColor, '0', 0, 255, 1).name('red');
gui.add(minColor, '1', 0, 255, 1).name('green');
gui.add(minColor, '2', 0, 255, 1).name('blue');
// create 2 textures
var textures = [];
for (var ii = 0; ii < 1; ++ii) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, images[ii]);
// add the texture to the array of textures.
textures.push(texture);
}
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
var minColorLocation = gl.getUniformLocation(program, "minColor");
// lookup the sampler locations.
var u_imageLocation = gl.getUniformLocation(program, "u_image");
function renderLoop() {
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation, size, type, normalize, stride, offset);
// Turn on the texcoord attribute
gl.enableVertexAttribArray(texcoordLocation);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Tell the texcoord attribute how to get data out of texcoordBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
texcoordLocation, size, type, normalize, stride, offset);
// set the resolution
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
gl.uniform3fv(minColorLocation, minColor.map(v => v / 255));
// set which texture units to render with.
gl.uniform1i(u_imageLocation, 0); // texture unit 0
// upload the latest video frame
gl.bindTexture(gl.TEXTURE_2D, textures[0]);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, images[0]);
// Set each texture unit to use a particular texture.
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textures[0]);
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(renderLoop);
}
requestAnimationFrame(renderLoop);
}
function randomInt(range) {
return Math.floor(Math.random() * range);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2,
]), gl.STATIC_DRAW);
}
// This is needed if the images are not on the same domain
// NOTE: The server providing the images must give CORS permissions
// in order to be able to use the image with WebGL. Most sites
// do NOT give permission.
// See: https://webglfundamentals.org/webgl/lessons/webgl-cors-permission.html
function requestCORSIfNotSameOrigin(img, url) {
if ((new URL(url, window.location.href)).origin !== window.location.origin) {
img.crossOrigin = "";
}
}
{"name":"WebGL - 2 Textures - with a video (transparency) with gui","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment