
本文详解如何将浏览器摄像头视频流作为纹理传入自定义 shadermaterial,并在 glsl 片元着色器中实时访问与处理每一帧像素,实现如色彩映射、动态滤镜、uv 变换等 gpu 加速视觉效果。
本文详解如何将浏览器摄像头视频流作为纹理传入自定义 shadermaterial,并在 glsl 片元着色器中实时访问与处理每一帧像素,实现如色彩映射、动态滤镜、uv 变换等 gpu 加速视觉效果。
要在 Three.js 中真正“操控”摄像头画面(而不仅是显示),关键在于:将视频元素封装为 THREE.VideoTexture,并作为 uniform 传递给 ShaderMaterial,再在 GLSL 中用 texture2D() 采样——这才是可编程渲染管线的起点。
✅ 正确接入摄像头纹理的完整流程
首先确保已获取摄像头媒体流并绑定到 <video></video> 元素(需用户授权):
<video id="video" autoplay muted></video>
// 获取摄像头流(注意:仅 HTTPS 或 localhost 下可用)
async function initVideo() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
const video = document.getElementById('video');
video.srcObject = stream;
video.play();
} catch (err) {
console.error("无法访问摄像头:", err);
}
}接着创建 VideoTexture 并注入 ShaderMaterial 的 uniforms:
const video = document.getElementById('video');
const videoTexture = new THREE.VideoTexture(video);
videoTexture.colorSpace = THREE.SRGBColorSpace; // 确保色彩空间一致
videoTexture.minFilter = THREE.LinearFilter;
videoTexture.magFilter = THREE.LinearFilter;
videoTexture.generateMipmaps = false; // 视频通常禁用 Mipmap
const vsh = await fetch('vertex-shader.glsl').then(r => r.text());
const fsh = await fetch('fragment-shader.glsl').then(r => r.text());
const material = new THREE.ShaderMaterial({
uniforms: {
resolution: { value: new THREE.Vector2(window.innerWidth, window.innerHeight) },
time: { value: 0.0 },
uTexture: { value: videoTexture }, // ← 核心:把视频纹理传进去
},
vertexShader: vsh,
fragmentShader: fsh,
transparent: true,
depthTest: false
});
const geometry = new THREE.PlaneGeometry(2, 2); // 覆盖标准 NDC [-1,1]
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);⚙️ 渲染循环中必须手动标记纹理更新
由于视频帧是异步更新的,Three.js 不会自动检测视频内容变化。你必须在每帧检查 video.readyState 并显式设置 needsUpdate = true:
function animate() {
requestAnimationFrame(animate);
// 关键:通知 Three.js 视频帧已更新,需重新上传 GPU 纹理
if (video.readyState === video.HAVE_ENOUGH_DATA) {
videoTexture.needsUpdate = true;
}
material.uniforms.time.value += 0.016; // 简单时间递增(≈60fps)
renderer.render(scene, camera);
}
animate();? 注意:
HAVE_ENOUGH_DATA表示当前帧数据就绪,比HAVE_CONTENTS更稳妥;若跳过此步,着色器将始终读取初始黑帧或旧帧。
? GLSL 片元着色器示例(fragment-shader.glsl)
uniform vec2 resolution;
uniform float time;
uniform sampler2D uTexture;
varying vec2 vUvs; // 由顶点着色器传入的 UV(通常为 position.xy * 0.5 + 0.5)
void main() {
// 直接采样原始视频帧(vUvs 已归一化到 [0,1])
vec4 color = texture2D(uTexture, vUvs);
// 示例:水平翻转 + 色调偏移
vec2 flippedUv = vec2(1.0 - vUvs.x, vUvs.y);
vec4 flipped = texture2D(uTexture, flippedUv);
vec3 adjusted = flipped.rgb * vec3(1.2, 0.9, 1.1); // R/G/B 增益
// 示例:简单时间扰动(正弦波扭曲)
vec2 distortedUv = vUvs + 0.02 * vec2(
sin(vUvs.y * 10.0 + time),
cos(vUvs.x * 10.0 + time * 0.7)
);
vec4 warped = texture2D(uTexture, distortedUv);
gl_FragColor = vec4(warped.rgb, 1.0);
}⚠️ 重要细节提醒:
-
vUvs必须由顶点着色器正确传递(常见做法是vUvs = (position.xy + 1.0) * 0.5); -
uTexture的采样坐标范围是[0,1],请勿直接用gl_FragCoord除以resolution(易因缩放/设备像素比出错); - 若需高精度时间,建议使用
performance.now()或clock.getElapsedTime()替代固定增量; - 移动端需注意
videoTexture在 Safari/iOS 上可能需额外设置video.crossOrigin = 'anonymous'(若跨域)。
至此,你已获得一条完整的 GPU 图像处理流水线:摄像头 → HTMLVideoElement → VideoTexture → ShaderMaterial → GLSL 片元着色器。所有像素级运算均在 GPU 上实时执行,为实时滤镜、光流分析、AR 合成等高级应用奠定坚实基础。


















