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的效果:
PS:Cesium交流可以掃碼加群,期待你的加入!!!