视频在 Three.js 场景中的渲染与播放实现说明
本文档详细记录“视频纹理如何被渲染到 Three.js 场景并实现自动/循环播放”的整体方案、关键代码、资源清理与常见问题。
概述
- 技术路线:
wx.createVideoDecoder解码 → 将帧数据绘制到隐藏的 2D Canvas → 使用THREE.CanvasTexture绑定该 Canvas → 在定时循环中推进帧并标记纹理更新。 - 优先使用
CanvasTexture持续刷新,避免每帧经由 Base64 重新创建纹理的性能瓶颈。 - 自动播放:在素材加载完成后进行首帧预热,并通过视频纹理更新器以约 30fps 驱动播放。
- 循环播放:检测无帧时通过
seek(0)重置到开头,持续循环。
目录与关键文件
index.wxml:定义 WebGL Canvas 与隐藏的 2D Canvasbehavior.js:加载素材、创建视频平面、绑定纹理、启动更新循环video-player.js:封装视频下载压缩、解码、帧绘制到 Canvasvideo-texture-updater.js:以setInterval驱动逐帧播放并刷新纹理
示例(index.wxml)
<view class="container page" data-weui-theme="{{theme}}">
<canvas type="webgl" id="webgl" style="width: {{width}}px; height: {{height}}px" bindtouchend="onTouchEnd"></canvas>
<!-- 用于视频渲染的2D Canvas,隐藏显示 -->
<canvas
type="2d"
id="videoCanvas"
style="position: absolute; left: -9999px; top: -9999px; width: 320px; height: 240px;"
></canvas>
</view>
渲染管线(逐步说明)
- 组件初始化
- 在
onReady中通过this.createSelectorQuery().select('#webgl').node()获取 WebGL Canvas 并初始化 Three.js 与 VKSession。 - 设置画布尺寸,监听窗口尺寸变化。
- 素材批量加载
loadAllModels()遍历data.modelConfigs,对type: 'video'调用loadVideoAsset(...)。
- 视频平面与材质
- 使用
new THREE.PlaneGeometry(16/9, 1)创建 16:9 的几何体(长方形)。 - 使用
new THREE.MeshBasicMaterial({ side: THREE.DoubleSide, transparent: true })创建材质并构建平面Mesh。
- 视频解码与 CanvasTexture
- 在
video-player.js中:- 下载视频:
wx.downloadFile→ 可选压缩:wx.compressVideo({ quality: 'low' }) - 创建解码器:
const decoder = wx.createVideoDecoder()→await decoder.start({ abortAudio: true, source: path }) - 每次播放推进:
decoder.getFrameData()获取当前帧的 RGBA 数据并绘制到 2D Canvas:context.createImageData(width, height)创建ImageDataimageData.data.set(Uint8ClampedArray)填充像素context.putImageData(imageData, 0, 0)绘制
- 下载视频:
- 在
behavior.js中:playVideo('videoCanvas', url, cb).then(videoController => { ... })- 创建
new THREE.CanvasTexture(videoController.canvas)并赋给material.map,保存到assetData.canvasTexture。 - 首帧预热:
videoController.play()。
- 纹理更新循环
video-texture-updater.js启动setInterval(updateLoop, 33)(约 30fps):- 每次调用
videoController.play()推进到下一帧(并绘制到 2D Canvas)。 - 若
play()返回false(无帧),调用videoController.restart()(内部使用decoder.seek(0))实现循环播放。 - 对
CanvasTexture:material.map.needsUpdate = true,确保纹理每帧刷新。
- 每次调用
- 放置到场景
- 加载完成后,可在素材组合/克隆流程中将视频平面设置位置、旋转、缩放并添加到场景或组合中;播放与纹理更新独立运行,无需额外触发。
- 资源清理
onUnload中:- 停止所有视频纹理更新循环:
this.videoTextureUpdater.stopAllVideoTextureUpdates() - 销毁视频播放器与解码器:
this.videoPlayer.destroy() - 清理材质与几何体、渲染器、场景、时钟等,避免内存泄漏。
- 停止所有视频纹理更新循环:
循环播放与自动播放
- 自动播放:在绑定纹理后立即执行一次
videoController.play(),确保首帧显示。 - 循环播放:
play()返回布尔值,表示是否成功渲染一帧。- 当返回
false时,调用restart()以seek(0)回到起点并继续播放。
配置与尺寸
- 几何体比例:当前使用
PlaneGeometry(16/9, 1)。 - 尺寸控制:通过配置中的
scale同步缩放(对长宽等比缩放)。 - 如果希望以“宽度为 1,高度为 9/16”的方式保持宽度不变,可改为
PlaneGeometry(1, 9/16)。
性能与兼容建议
- 基础库要求:
wx.createVideoDecoder从基础库 2.11.0 开始支持(需保证真机/开发者工具环境满足要求)。 - 更新频率:默认 30fps(33ms),如遇性能瓶颈可调大间隔降低帧率(如 24fps、20fps)。
- 纹理更新:
CanvasTexture每帧仅标记needsUpdate,比每帧TextureLoader.load(dataURL)更高效。 - 音频:当前使用
abortAudio: true,不解码音频流以降低开销,如需音频可调整解码参数并自己处理播放。
新增视频素材的步骤
- 在
behavior.js的data.modelConfigs中添加条目:
{
id: 1533,
path: "https://example.com/video.mp4",
positionX: "1.5",
positionY: "1.71",
positionZ: "-3",
rotationX: "0",
rotationY: "0",
rotationZ: "0",
scale: "3",
type: "video"
}
- 进入页面后会自动加载、绑定纹理并开始播放;若视频结束会自动循环。
常见问题排查
- 视频不播放/黑屏:
- 确认
index.wxml中存在id="videoCanvas"的 2D Canvas。 - 检查视频 URL 是否可访问且不跨域受限;当前通过
wx.downloadFile获取本地路径以规避跨域。 - 确认基础库版本 ≥ 2.11.0,真机也需满足。
- 确认
- 画面拉伸:
- 几何体为 16:9,如源视频不是 16:9,建议在几何体或缩放上调整比例,或在 2D Canvas 绘制前做裁剪/留边。
- 卡顿/高耗:
- 将更新频率从 33ms 调大(降低到 24fps/20fps)。
- 避免每帧使用 Base64 新建纹理(已切换为 CanvasTexture)。
参考
- 微信开放社区:使用 Three.js 渲染视频的实践
- 官方文档:视频解码器
wx.createVideoDecoder
