I trying to port this webgl example Production-ready green screen in the browser to three.js
putting the chroma key shader in a 3d object into the scene, because the idea is interact with camera and lights.This green screen algorithm is derived from the Chroma Key filter in OBS Studio that give us a very good effect result.
Here is my code
I don’t know what is missing or wrong in my code, as the browser inspector is not showing anything
I’ve quickly ported the code to the latest version of three.js
: https://jsfiddle.net/mpkw8yse/
But without the video texture for simplicity.
Hey, I’ve created an OBS inspired webcam greenscreen example as well,
for extra reference in case you need.
Webcam - Three.js Tutorials (sbcode.net)
Hi ,Thanks !
Hi, Seanwasere. It’s exactly what I was trying to do ! Thanks !!
Hello,
First, sorry for my english
I’m trying now that the chroma key object is affected by light. I spent a lot of time studying examples of how to use onBeforeCompile, in this forum and this guide (Extending three.js materials with GLSL), but I still don’t quite understand how the Replace works.
I made this fiddle using a phongMaterial to demonstrate the expected result
chromakey shader from webcam
Can you please share the working code with me which is fetching video from a url instead of webcam
Hey, sorry for delay. I Create an example based on seanwasere (Profile - seanwasere - three.js forum) code using THREE.VideoTexture
Becouse security reason you should use this video in your server or localhost
<!DOCTYPE html>
<html>
<head>
<title>Three.js Chromakey shader</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r123/three.min.js"></script>
<script src="https://unpkg.com/three@0.85.0/examples/js/controls/OrbitControls.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.7/dat.gui.min.js" integrity="sha512-LF8ZB1iTwi4Qvkm4pekHG4a437Y9Af5ZuwbnW4GTbAWQeR2E4KW8WF+xH8b9psevV7wIlDMx1MH9YfPqgKhA/Q==" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stats.js/7/Stats.js" integrity="sha512-+nNqDCQd6FiN/mQKnpfI/UswnfuG/cjHZbv+amV/2PLiuGgN8+D5dKzR4PqnSY5pZBExGavZz+FdjIeI9WgpKQ==" crossorigin="anonymous"></script>
<style>
body {
overflow: hidden;
margin: 0px;
}
</style>
</head>
<body>
<video id="video" width="320" height="240" preload autoplay loop muted src="171003D_002_2K_preview.webm"></video>
<script id="vertexShader" type="glsl">
varying vec2 vUv;
void main( void ) {
vUv = uv;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);
}
</script>
<script id="fragmentShader" type="glsl">
uniform vec3 keyColor;
uniform float similarity;
uniform float smoothness;
varying vec2 vUv;
uniform sampler2D map;
void main() {
vec4 videoColor = texture2D(map, vUv);
float Y1 = 0.299 * keyColor.r + 0.587 * keyColor.g + 0.114 * keyColor.b;
float Cr1 = keyColor.r - Y1;
float Cb1 = keyColor.b - Y1;
float Y2 = 0.299 * videoColor.r + 0.587 * videoColor.g + 0.114 * videoColor.b;
float Cr2 = videoColor.r - Y2;
float Cb2 = videoColor.b - Y2;
float blend = smoothstep(similarity, similarity + smoothness, distance(vec2(Cr2, Cb2), vec2(Cr1, Cb1)));
gl_FragColor = vec4(videoColor.rgb, videoColor.a * blend);
}
</script>
<script>
let camera, controls, gridHelper, scene, renderer, stats;
let container, urlVideo, urlVideoTexture, chromakeyMaterial;
window.onload = init();
function init() {
container = document.createElement( 'div' );
document.body.appendChild( container );
scene = new THREE.Scene();
renderer = new THREE.WebGLRenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
controls = new THREE.OrbitControls( camera, renderer.domElement );
controls.minDistance = 5;
controls.maxDistance = 20;
controls.enableDamping = true;
controls.dampingFactor = 0.5;
gridHelper = new THREE.GridHelper(10, 10);
gridHelper.position.y = -1.5;
scene.add(gridHelper);
camera.position.z = 5;
// urlVideo
urlVideo = document.getElementById("video");
urlVideo.onplaying = function() {
urlVideoTexture = new THREE.VideoTexture(urlVideo);
urlVideoTexture.minFilter = THREE.LinearFilter;
urlVideoTexture.magFilter = THREE.LinearFilter;
const vertexShader = document.getElementById("vertexShader").textContent;
const fragmentShader = document.getElementById("fragmentShader").textContent;
// Cria o material usando a urlVideoTexture
chromakeyMaterial = new THREE.ShaderMaterial({
transparent: true,
uniforms: {
map: { value: urlVideoTexture },
keyColor: { value: [0.0, 1.0, 0.0] },
similarity: { value: 0.74 },
smoothness: { value: 0.0 }
},
vertexShader: vertexShader,
fragmentShader: fragmentShader
});
const geometry = new THREE.PlaneGeometry( 16, 9 );
const mesh = new THREE.Mesh( geometry, chromakeyMaterial );
geometry.scale( 0.4, 0.5, 0.5 );
scene.add(mesh);
}
var data = {
keyColor: [0, 255, 0],
similarity: 0.74,
smoothness: 0.0
};
var gui = new dat.GUI( { width: 300 } );
gui.addColor(data, 'keyColor').onChange(() => updateKeyColor(data.keyColor));
gui.add(data, 'similarity', 0.0, 1.0).onChange(() => updateSimilarity(data.similarity));
gui.add(data, 'smoothness', 0.0, 1.0).onChange(() => updateSmoothness(data.smoothness));
stats = new Stats();
container.appendChild( stats.domElement );
window.addEventListener('resize', onWindowResize, false);
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
render();
}
function updateKeyColor(v) {
chromakeyMaterial.uniforms.keyColor.value = [v[0] / 255, v[1] / 255, v[2] / 255];
}
function updateSimilarity(v) {
chromakeyMaterial.uniforms.similarity.value = v;
}
function updateSmoothness(v) {
chromakeyMaterial.uniforms.smoothness.value = v;
}
function animate() {
requestAnimationFrame(animate);
//if (urlVideo.readyState === urlVideo.HAVE_ENOUGH_DATA) {
if (urlVideoTexture)
urlVideoTexture.needsUpdate = true;
//}
controls.update();
stats.update();
render();
};
function render() {
renderer.render(scene, camera);
}
animate();
Thanks for your help, I will test out the same
Thank you, @Mugen87! I reworked your shader code into a ChromaKeyMaterial module that can take a video or image texture.
I’m unable to get your shader working on a green screen video.
html, body{ background:#323232; outline: none; overflow: hidden; margin:0;}