Cesium源码剖析—视频投影

Cesium源码剖析—视频投影

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. 添加雨雪天气