跳到主要内容

视频纹理与相机

视频/摄像头是 外部图像源,不是场景节点。解码帧进纹理后,用世界矩形 + 相机矩阵画一张四边形。不要每帧 drawImage 进 2D 再 texImage2D——多一次拷,脏性照传。时间轴跟 视频帧,不要跟 rAF 猜。

1. 场景怎么切

CPU:<video> 元素、播放状态、世界四边形、CORS 标记
↓ rvfc / 有新帧
GPU:已分配的 RGBA 纹理 ← texSubImage2D(video)
↓ 一张 textured quad × viewProj

创建时按 videoWidth × videoHeight texStorage2D 一次。每帧只 texSubImage2D,不要 texImage2D 重分配。等到 readyState >= 2,否则上传空帧。图像原点左上,生产几乎总要 UNPACK_FLIP_Y_WEBGL,见 纹理。板子放在世界里:

export type VideoBoard = {
x: number;
y: number;
width: number;
height: number;
};

VideoBoard 乘 viewProj;解码像素不要当成 CSS 尺寸。

export function attachVideoTexture(
gl: WebGL2RenderingContext,
texture: WebGLTexture,
video: HTMLVideoElement,
onFrame: () => void,
): () => void {
let handle = 0;
let cancelled = false;
const tick: VideoFrameRequestCallback = () => {
if (cancelled) return;
if (video.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA) {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, 1);
gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, gl.RGBA, gl.UNSIGNED_BYTE, video);
gl.bindTexture(gl.TEXTURE_2D, null);
onFrame();
}
handle = video.requestVideoFrameCallback(tick);
};
handle = video.requestVideoFrameCallback(tick);
return () => {
cancelled = true;
video.cancelVideoFrameCallback(handle);
};
}

暂停时 rvfc 可能停:在 pause 上补一帧。没有 rvfc 的引擎用 rAF + currentTime,接受相位差。摄像头:getUserMedia 得到的同样是 HTMLVideoElement,上传路径相同;权限失败是产品路径,不要当 GL 错误。世界矩形用场景单位;换源或 videoWidth 变化必须重建不可变纹理,不能对 1×1 存储 texSubImage2D 一张 1080p。

适用:预览、AR 背景、把影片当场景里的一块板。
不适用:烧录字幕/水印成片(先看 Canvas 视频);每帧读像素做 CV——那是 计算着色,且不要热路径全屏 readPixels

2. 哪层必须 CPU,哪层进 GPU

归属
src、crossOrigin、播放、世界矩形CPU
纹理、采样、viewProjGPU
进度条、按钮DOM overlay

crossOrigin = "anonymous" 必须在设 src 之前。CDN 要 ACAO。跨源失败时预览仍可能正常,但 taint 后 readPixels / toBlobSecurityError,见 CORS。不要用 1×1 readPixels 当热路径探针。

WebGPU:探测到 adapter 后可用 importExternalTexture / copyExternalImageToTexture,同一套 origin-clean,不能绕过 taint。没探测就留在 WebGL2 texSubImage2D

同一 canvas 不能再 getContext("2d") 画字幕。字幕走 overlay 或第二张 2D canvas,见 与 2D 叠层preserveDrawingBuffer 仍默认 false:要截当前视频帧,在本帧 draw 之后立刻读 FBO,不要等下一轮 rAF。

3. 失败形态

症状原因
只有第一帧每帧没 texSubImage2D,或以为 pause 还会自己更新
画面上下颠倒忘了或重复了 UNPACK_FLIP_Y_WEBGL
导出 SecurityError跨源无 CORS,或 crossOrigin 设晚
overlay 和画面不同步用 rAF 而视频变速
iOS 黑纹理videoWidth=0 时分配了 1×1 不可变存储

权威资料

核对日期:2026-08-26