「译」如何使用 WebGL 进行实时视频处理
2020/8/30 14:03:36
本文主要是介绍「译」如何使用 WebGL 进行实时视频处理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
- 原文地址:Realtime video processing with WebGL
- 原文作者:Lea Rosema
这是我最近在 CodePen 上制作的 WebGL 演示案例。它可以捕获网络摄像头的数据(或在无法访问网络摄像头时,从 placekitten 获取备用图像),并将其实时转换为 ASCII 图像艺术。
为了获得更多的复古性,我使用了 90 年代 DOS PC 中常见的 8x8 像素光栅字体(您可能会在某些 BIOS 中看到这种字体)。
要将图像内容映射到特定字符,我通过使用亮度图选择最佳匹配。我计算每个 4x4 正方形的像素。在画板内向下滚动以查看亮度图:
我还为这些字体创建了一个编辑器:https://terabaud.github.io/pi...
若干 WebGL 基础知识
我将介绍 WebGL 的一些基础知识,但这里仅涉及部分问题。获取有关详细指导,建议您访问https://webglfundamentals.org
对于 WebGL,一个常见误解是把它当作浏览器中的 3D 引擎。尽管 WebGL 技术能使我们在浏览器中提供 GPU 加速的 3D 内容,但 WebGL 本身不是 3D 引擎。在 WebGL 之上,有专门用于 GPU 加速的 2D 或 3D 内容的图形库(例如用于 2D 的 Pixi,用于 3D 的 ThreeJS)。
WebGL 本身是很基础的绘图标准库,并且是一个以 GPU 加速的方式,将点、线和三角形绘制到 html
<canvas>
元素上的库。
可以通过 getContext
(类似于 2D canvas API
)检索 WebGL 渲染上下文:
const canvas = document.querySelector('canvas'); const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
一个 WebGL 程序包含多个着色器组件,着色器是运行在 GPU 上的代码,它们不是用 JavaScript 编写的,而是具有自己的语言,称为 GLSL(GL 着色器语言)。
GLSL 快速概览
- 类似 C语言,着色器程序包含
void main()
- 变量声明也像在 C 语言中一样
- 原始数据类型:
int
,float
,double
- 向量:
vec2
,vec3
,vec4
, ... - 矩阵:
mat2
,mat3
,mat4
, ... - 访问纹理数据的类型:
sampler2D
- 内置向量、矩阵运算
- 大量内置功能, 例如,求取向量的长度(
length(v)
)
着色器的类型
WebGL 程序中有两种类型的着色器。
- 顶点着色器计算位置。
- 片段着色器处理栅格化。
如果您的 WebGL 程序想要在屏幕上绘制一个三角形,它会把三角形的 3 个坐标传递给顶点着色器。然后,片段着色器的任务是用像素填充该三角形,这种逐像素处理过程非常快,因为它是针对 GPU 上的每个像素并行运行处理的。
在我的演示案例中,我使用 4 个矢量坐标来覆盖适合整个屏幕的矩形,所有工作都在片段着色器中完成。
顶点着色器
顾名思义,顶点着色器存在于顶点。它从 JavaScript 代码提供的缓冲区中获取一堆数据,并根据这些数据计算在画布中的相应位置。
以下代码段将数据从缓冲区拉入一个 attribute
变量,并将其传递给该 gl_Position
变量:
attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); }
片段着色器
precision highp float; void main() { vec2 p = gl_FragCoord.xy; gl_FragColor = vec4( 1.0, .5 + .5 * sin(p.y), .5 + .5 * sin(p.x), 1.0); }
片段着色器针对每个片段(像素)并行运行。在上面的示例中,片段着色器从 gl_FragCoord
变量读取当前像素坐标,并通过 gl_FragColor
中的 sin()
计算运行并输出颜色。
gl_FragColor
是一个 vec4 向量,其中包含(红色,绿色,蓝色,alpha),取值各为 0 .. 1。
GLSL 变量的类型
attribute
: 顶点着色器从缓冲区中提取一个值并将其存储在属性变量中。uniform
: 从 JS 端设置统一变量。例如,您可以统一使用诸如当前鼠标、轻敲位置之类的内容传递给着色器。您还可以使用统一变量来访问从 JavaScript 上传的纹理数据。varying
: 将值从顶点传递到片段着色器并进行插值。
上传图像数据
您可以使用图像数据访问到着色器中的 WebGLRenderingContext,并将其上传到纹理中。(另请参见:WebGL 基础知识:图像处理)
您可以使用 texImage2D
内部方法 WebGLRenderingContext 将图像数据上传到纹理中。
// gl is the WebGLRenderingContext const texture = gl.createTexture() gl.activeTexture(gl.TEXTURE0 + textureIndex); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img); // more info about these parameters in the webglfundamentals 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);
您传递给 texImage2D
的图像数据,可以是 img 元素、视频元素、ImageData 等。
由于视频的图像数据不断变化,因此您必须在 requestAnimationFrame 动画循环内更新纹理。以下是获取完成的 texSubImage2D
。
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video);
读取纹理数据
在着色器代码中读取纹理数据,您可以通过 texture
的 2Dglsl 函数访问纹理的像素数据。
当纹理坐标从(0,0)变为(1,1)时,图像会上下颠倒。同时,我正处于水平镜像图像中(就像用相机自拍一样)。
uniform sampler2D texture0; void main() { vec2 coord = 1.0 - gl_FragCoord.xy / vec2(width, height); gl_FragColor = texture2D(texture1, coord); }
访问网络摄像头
要从网络摄像头获取图像数据,我们可以使用 video
标签,并使用 getUserMediaAPI
:
function accessWebcam(video) { return new Promise((resolve, reject) => { const mediaConstraints = { audio: false, video: { width: 1280, height: 720, brightness: {ideal: 2} } }; navigator.mediaDevices.getUserMedia( mediaConstraints).then(mediaStream => { video.srcObject = mediaStream; video.setAttribute('playsinline', true); video.onloadedmetadata = (e) => { video.play(); resolve(video); } }).catch(err => { reject(err); }); } ); } // 使用说明: // const video = await accessWebcam(document.querySelector('video')); // or via promises: // accessWebcam(document.querySelector('video')).then(video => { ... });
要访问网络摄像头,您可以使用 getUserMedia API 来访问网络摄像头,如上所述。
提供后备图像
如果用户阻止了对网络摄像头的访问,或者没有可用的网络摄像头,则可以提供一个备用图像供您使用。
我也将 new Image()
中的 onload
操作包装成一个 promise
。
function loadImage(url) { return new Promise((resolve, reject) => { const img = new Image(); img.crossOrigin = 'Anonymous'; img.src = url; img.onload = () => { resolve(img); }; img.onerror = () => { reject(img); }; }); }
合并全部操作
为了使事情变得容易一些,我将常用的 WebGL 函数放入了我创建的一个小助手库GLea中。
它初始化 WebGL 应用上下文,编译 WebGL 着色器代码,并为顶点着色器创建属性和缓冲区:
默认情况下,position
为顶点着色器提供一个属性,该属性带有一个缓冲区,该缓冲区包含 4 个 2D 坐标,覆盖整个屏幕上的 2 个三角形。
import GLea from 'glea.js'; const frag = ` ... `; // 片段着色器代码 const vert = ` ... `; // 顶点着色器代码 const glea = new GLea({ shaders: [ GLea.fragmentShader(frag), GLea.vertexShader(vert) ] }).create(); function loop(time = 0) { const { gl, width, height } = glea; glea.clear(); glea.uniV('resolution', [width, height]); glea.uni('time', time * 1e-3); gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4); requestAnimationFrame(loop); } window.addEventListener('resize', () => { glea.resize(); }); loop(0);
结论
基本上就是这样。我希望您喜欢阅读本文,并对自己探索 WebGL 感到好奇。我会在这里放一些资源。
如果我还有没介绍到的内容,请随时发表补充评论 =)。
参考资料
- https://webglfundamentals.org/
- https://thebookofshaders.com/
- https://frontendmasters.com/c...
- https://codame.com/events/wor...
这篇关于「译」如何使用 WebGL 进行实时视频处理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-01前端项目部署入门:新手必读指南
- 2024-11-01富文本编辑器学习:从入门到初步掌握
- 2024-11-01前端项目部署学习:从入门到实践
- 2024-11-01动态主题处理:WordPress新手指南
- 2024-11-01前端项目部署指南:从零开始的部署教程
- 2024-11-01Element-Plus入门指南:轻松开始你的前端项目
- 2024-11-01TagsView标签栏导航入门教程
- 2024-11-01富文本编辑器课程:新手入门教程
- 2024-10-31前端项目部署课程:从入门到实践指南
- 2024-10-31用Angular实现服务器端渲染以提升SEO效果