返回介绍

Cesium 源码剖析-视频投影

发布于 2021-01-09 13:40:56 字数 5630 浏览 1486 评论 0 收藏 0

Cesium中的视频投影是指将视频作为一种物体材质,实现在物体上播放视频的效果。这个功能在Cesium早期版本中就支持了,在Code Example中有一个示例。今天就来分析一下其内部实现原理。

1. 添加视频投影及效果

示例中添加视频投影的代码分为两部分,第一步是添加div控件,控件负责视频播放、暂停等任务,代码如下:

<video id="trailer" muted autoplay loop crossorigin controls>
    <source src="https://cesiumjs.org/videos/Sandcastle/big-buck-bunny_trailer.webm" type="video/webm">
    <source src="https://cesiumjs.org/videos/Sandcastle/big-buck-bunny_trailer.mp4" type="video/mp4">
    <source src="https://cesiumjs.org/videos/Sandcastle/big-buck-bunny_trailer.mov" type="video/quicktime">
    Your browser does not support the <code>video</code> element.
</video>

第二步是添加一个球状物体,并为其指定材质,代码如下:

1 var videoElement = document.getElementById('trailer');//获得video对象
2 var sphere = viewer.entities.add({
3     position : Cesium.Cartesian3.fromDegrees(-79, 39, 1000),
4     ellipsoid : {
5         radii : new Cesium.Cartesian3(1000, 1000, 1000),
6         material : videoElement //指定材质
7     }
8 });

运行程序,得到的效果如下图所示:

2. 内部代码实现

在没有查看Cesium实现视频投影原理之前,我们可以大胆猜测实现的基本思路:视频不就是连续的照片组合在一起播放吗?那通过不断更换照片就可以实现视频投影!好,那我们就按照这个思路去查看一下相关代码。在Material.js中查找到了createTexture2DUpdateFunction这个函数,其中和video材质相关的部分代码如下:

 1 var uniforms = material.uniforms;
 2 var uniformValue = uniforms[uniformId];
 3 var uniformChanged = oldUniformValue !== uniformValue;
 4 oldUniformValue = uniformValue;
 5 var texture = material._textures[uniformId];
 6 
 7 var uniformDimensionsName;
 8 var uniformDimensions;
 9 
10 if (uniformValue instanceof HTMLVideoElement) {
11     // HTMLVideoElement.readyState >=2 means we have enough data for the current frame.
12     // See: https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/readyState
13     if (uniformValue.readyState >= 2) {
14         if (uniformChanged && defined(texture)) {
15             if (texture !== context.defaultTexture) {
16                 texture.destroy();
17             }
18             texture = undefined;
19         }
20 
21         if (!defined(texture) || texture === context.defaultTexture) {
22             texture = new Texture({
23                 context : context,
24                 source : uniformValue
25             });
26             material._textures[uniformId] = texture;
27             return;
28         }
29 
30         texture.copyFrom(uniformValue);
31     } else if (!defined(texture)) {
32         material._textures[uniformId] = context.defaultTexture;
33     }
34     return;
35 }

从上面的代码可以看出,texture的更新可以分为三个状态:

视频未加载完成:此时的texture赋值为context.defaultTexture,用默认图片代替;

视频刚加载完成:通过video构造新的texture对象,并通过texture的copyFrom函数进行更新;

视频加载完成后:只需通过texture的copyFrom函数进行更新即可。

构造texture对象属于常规操作,所以实现视频投影中画面内容更新的重要函数就是copyFrom。截取函数中部分代码如下:

 1 if (arrayBufferView) {
 2     gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
 3     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
 4 
 5     if (flipY) {
 6         arrayBufferView = PixelFormat.flipY(arrayBufferView, pixelFormat, pixelDatatype, width, height);
 7     }
 8     gl.texSubImage2D(target, 0, xOffset, yOffset, width, height, pixelFormat, pixelDatatype, arrayBufferView);
 9 } else {
10     // Only valid for DOM-Element uploads
11     gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, preMultiplyAlpha);
12     gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flipY);
13 
14     gl.texSubImage2D(target, 0, xOffset, yOffset, pixelFormat, pixelDatatype, source); 15 }

其中,红色部分的代码是针对dom元素的,用到的关键函数就是texSubImage2D。这个函数可以动态的更新GPU中绑定的图片内容,相当于每次都从video元素中取当前播放的图片作为材质的当前贴图,达到视频投影的效果。texSubImage2D函数的全部用法如下:

 1 // WebGL 1:
 2 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, ArrayBufferView? pixels);
 3 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, ImageData? pixels);
 4 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLImageElement? pixels);
 5 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLCanvasElement? pixels);
 6 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, HTMLVideoElement? pixels);
 7 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, ImageBitmap? pixels);
 8 
 9 // WebGL 2:
10 void gl.texSubImage2D(target, level, xoffset, yoffset, format, type, GLintptr offset);
11 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, HTMLCanvasElement source);
12 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, HTMLImageElement source);
13 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, HTMLVideoElement source); 
14 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, ImageBitmap source); 
15 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, ImageData source);
16 void gl.texSubImage2D(target, level, xoffset, yoffset, width, height, format, type, ArrayBufferView srcData, srcOffset);

3. 视频投影的应用

这个例子中是将视频投影到一个规则球体上,比较简单。根据这个原理,还可以引申出一些更加有实用价值的功能。比如将视频投影到gltf模型或者3dtiles模型上,将摄像头拍摄的视频投影到真实场景中,使监控更加直观、立体。下面是做的一个将视频投影到3dtiles的效果:

 

Cesium源码剖析系列教程目录

  1. 视频投影
  2. Clipping Plane
  3. Ambient Occlusion(环境光遮蔽)
  4. Post Processing之物体描边(Silhouette)
  5. 添加雨雪天气

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文