将网络摄像头流直接获取到 WebGL 作为纹理的最有效方法是什么?
标准流程(如 Three.js 中所述)是使用 video 标签,然后在 canvas 标签中操作它,然后从 canvas 到 WebGL。
这会增加 CPU 开销,并且 Chrome 会启动工作线程将图像数据从相机发送到 GPU(例如每秒 30 次)。
有什么方法可以更有效地做到这一点并减少 CPU 上的渲染/处理?
最简单的方法是简单地将流放入视频 HTML 元素中,然后将其转换为纹理。这种方式会导致 fps 较低。幸运的是,有一个库可以快速完成此操作。我不明白到底是怎么回事(可能从流本身中提取纹理,或者优化第一种方法)。
https://p5js.org/examples/dom-video-capture.html https://p5js.org/examples/3d-shader-using-webcam.html#
您可以检查源代码以了解他们是如何做到的。 https://github.com/processing/p5.js/
当我比较这两种方法时,第二种方法的速度为 60fps,而第一种方法的着色器速度为 20fps。
用于更新纹理数据的 WebGL 函数
gl.texImage2D()
将直接接受 <video>...<video/>
页面元素作为其最后一个参数的缓冲区源。
因此,您需要做的就是设置 WebGL 管道,请求用户允许捕获其网络摄像头,然后启动一个动画循环,反复调用
gl.texImage2D()
将数据从相机移动到纹理,然后再渲染您的场景。
我测试了这段代码 Mozilla Firefox
122.0.1
。为了简洁起见,我故意省略了错误检查和浏览器兼容性代码:
<!DOCTYPE html>
<html><head><script>
function main() {
// Capture webcam input using invisible `video` element
// Adapted from p5js.org/examples/3d-shader-using-webcam.html
let camera = document.getElementById("camera");
// Ask user permission to record their camera
navigator.mediaDevices.getUserMedia({video:1,audio:0}).then(
(stream)=>{ try {
if ('srcObject' in camera) camera.srcObject = stream;
else camera.src = window.URL.createObjectURL(stream);
} catch (err) {camera.src = stream;}},console.log);
camera.play();
// 512×512 Canvas with WebGL context
var canvas = document.getElementById("maincanvas");
var gl = canvas.getContext("webgl");
canvas.width = canvas.height = 512;
gl.viewport(0, 0, canvas.width, canvas.height);
// Vertex shader: Identity map
var vshader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vshader,
"attribute vec2 p;"+
"void main(){"+
" gl_Position = vec4(p,0,1);"+
"}");
gl.compileShader(vshader);
// Fragment shader: sample video texture, change colors
var fshader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fshader,
"uniform sampler2D data; "+
"void main() {"+
" gl_FragColor=texture2D(data,gl_FragCoord.xy/vec2(512,512)).zxyw;"+
"}");
gl.compileShader(fshader);
// Create and link program
var program = gl.createProgram();
gl.attachShader(program,vshader);
gl.attachShader(program,fshader);
gl.linkProgram(program);
gl.useProgram(program);
// Vertices: A screen-filling quad made from two triangles
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER,new Float32Array([-1,-1,1,-1,-1,1,-1,1,1,-1,1,1]),gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
// Texture to contain the video data
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);
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);
// Bind texture to the "data" argument to the fragment shader
var param = gl.getActiveUniform(program,0); // data bind point
gl.uniform1i(gl.getUniformLocation(program,"data"),0);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D,texture);
// Repeatedly pull camera data and render
function animate(){
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, camera);
gl.drawArrays(gl.TRIANGLES, 0, 6);
requestAnimationFrame(animate);
}
animate();
}
</script></head>
<body onload="javascript:main()">
<canvas id='maincanvas' style="width:512px;height:512px;"></canvas>
<video id='camera' visible="False" style="width: 512px; height: 512px; display:none;" controls="true" playsinline="" crossorigin="anonymous"></video>
</body>
</html>