zoukankan      html  css  js  c++  java
  • 1.2 Cesium渲染流程

      

    从前有座山,山里有座庙,庙里有个......”我们喜欢这样讲故事,有头有尾,一个调用接一个,特别因为JS本身的一些特点,往往我们会发现,半路杀出个“程咬金”,一些对象变量临场出现让人迷糊,这里面弄清楚整个流程显得尤为重要,搞清楚这个引擎流水线,我们才能把控这里面的机制。Cesium实时刷新,就是说每一帧都在更新,这就像最原始的动画制作一样,一页翻完翻另一个,只是刷新间隔快了,我们眼皮没发觉(但这也是一般状态下,如果场景完全静悄悄也可请求渲染模式,这时就不是每一帧都更新了,这个我们后面再说,先认为是动起来的)。 

    我们在初始化一个球的时候,比较常用的个方式是创建一个Viewer容器,如下:

    var viewer = new Cesium.Viewer('cesiumContainer', {
        shadows : true
    });
    

    然后可以在里面初始化一堆参数,很多人就误以为这个球的开口就是Viewer,其实不然;Viewer只是一个简单的启动器,帮忙携带了一些球启动的参数,但归根结底它仍然是一个二次封装的产物。Cesium球的大门在CesiumWidget里。在Viewer里我们可以看到,经过一通传递和组合,用户传入的参数,最终还是构建给了CesiumWidget:

             var cesiumWidget = new CesiumWidget(cesiumWidgetContainer, {
                imageryProvider: createBaseLayerPicker || defined(options.imageryProvider) ? false : undefined,           
                skyBox : options.skyBox,            
                scene3DOnly : scene3DOnly,            
                shadows : options.shadows,            
                mapMode2D : options.mapMode2D,
                requestRenderMode : options.requestRenderMode

    ...... });

    options就是在外围Viewer传入进来的,包括最基础的影像、阴影设置等,一个widget包含一个三维场景。我们转入来看CesiumWidget,这里面也洋洋散散为自己也为外围调用封装了一堆东西,可谓是细致入微,但我们最终要看的是startRenderLoop函数

    function render(frameTime) {  if (widget._useDefaultRenderLoop) {
            try {
              ......
                    requestAnimationFrame(render);
                }
            } catch (error) {
               ......
            }
        } 
    }
    requestAnimationFrame(render);

    看到这对WebGL稍熟悉便大彻大悟,requestAnimationFrame()是专门为脚本式的动画而生的,通过requestAnimationFrame()函数,跟传送带一样,高速运转一帧一帧,一个画面又一个画面的输送,不同的三维场景映入眼帘。开篇我们说Cesium是动起来的,就是在这运作的,当然,光到这我们只能说我们看清楚了JS的动画机制,还不能说看清楚了Cesium的动画机制,因为截止目前为止,Cesium还没有创建任何能看到的球上东西。

    再接下来,同样是在CesiumWidget里,场景的渲染在一个自动调用函数里

     CesiumWidget.prototype.render = function() {
            if (this._canRender) {
                this._scene.initializeFrame();
                var currentTime = this._clock.tick();
                this._scene.render(currentTime);
            } else {
                this._clock.tick();
            }
        };
    

    这里调用scene的render(time)函数,才是至关重要,细微化会调用scene里面的私有函数render(scene),它就是这个WebGL三维场景的渲染调度和绘制命令的组织者。但特别说一下,Cesium利用颜色缓冲区来实现拾取,在scene.pick函数里面,将ID当作颜色写入到一个离屏缓冲区,对象与ID唯一对应,然后根窗口坐标(x,y)拾取内容,readPixels读取颜色,并返回拾取的对象,看源码会发现scene.pick与scene.render很相似,但太阳、大气和天空盒没必要做拾取做了不处理。

      function render(scene) {
    var frameState = scene._frameState; var context = scene.context; var us = context.uniformState;

    //Cesium最近几个版本在scene的基础之上,有多加了一层view,方便数据调用和抽象共有层 var view = scene._defaultView; scene._view = view; updateFrameState(scene); frameState.passes.render = true; frameState.passes.postProcess = scene.postProcessStages.hasSelected; frameState.tilesetPassState = renderTilesetPassState; var backgroundColor = defaultValue(scene.backgroundColor, Color.BLACK); if (scene._hdr) { backgroundColor = Color.clone(backgroundColor, scratchBackgroundColor); backgroundColor.red = Math.pow(backgroundColor.red, scene.gamma); backgroundColor.green = Math.pow(backgroundColor.green, scene.gamma); backgroundColor.blue = Math.pow(backgroundColor.blue, scene.gamma); } frameState.backgroundColor = backgroundColor; frameState.creditDisplay.beginFrame(); scene.fog.update(frameState); us.update(frameState); var shadowMap = scene.shadowMap; if (defined(shadowMap) && shadowMap.enabled) { // Update the sun's direction Cartesian3.negate(us.sunDirectionWC, scene._sunCamera.direction); frameState.shadowMaps.push(shadowMap); } scene._computeCommandList.length = 0; scene._overlayCommandList.length = 0; var viewport = view.viewport; viewport.x = 0; viewport.y = 0; viewport.width = context.drawingBufferWidth; viewport.height = context.drawingBufferHeight; var passState = view.passState; passState.framebuffer = undefined; passState.blendingEnabled = undefined; passState.scissorTest = undefined; passState.viewport = BoundingRectangle.clone(viewport, passState.viewport); if (defined(scene.globe)) { scene.globe.beginFrame(frameState); } updateEnvironment(scene); updateAndExecuteCommands(scene, passState, backgroundColor); resolveFramebuffers(scene, passState); passState.framebuffer = undefined; executeOverlayCommands(scene, passState); if (defined(scene.globe)) { scene.globe.endFrame(frameState); if (!scene.globe.tilesLoaded) { scene._renderRequested = true; } } frameState.creditDisplay.endFrame(); context.endFrame(); }

    这里完整地将整个函数列出来,表面不算太长,但设计了整个渲染周期前期准备、完整绘制、后期效果方方面面,可谓保罗万象,有必要仔细跟一下函数里面的细节,Cesium必由之路,不管是自建功能还是调整源码,都会不断地与这段代码打交道。下面我们就来仔细过一下这段代码。

    首先一开始做了一些渲染前的参数传递与准备,frameState.passes.render = true是默认是渲染状态,有人可能问,这个重点函数不就是负责渲染的吗,为什么还有具体的默认状态?其实Cesium由于主要还是基于WebGL1.0来实现,很多的包括深度信息、最终合成帧等都是一帧一帧重新渲染场景的得到的;正常情况下就是很普通的将场景最终的样子推送到绘制缓冲区就OK了,但有些时候,我们并不需要它绘制出来,仅仅是为了获取深度图或者点选对象的ID列表,因此严谨Cesium对渲染不同的需求又做了分流如下几种:

    passes = {
    render : 最常见的渲染,
    pick : 图元拾取渲染,
    depth : 深度渲染,
    postProcess : 后处理阶段渲染,
    offscreen : 离屏渲染
    
    ......
    }

     这下就明了了,比如仅仅为了拿到深度图,frameState.passes.depth= true设为即可,自然往下执行绘制命令,这张深度图就出来了。

    var pickDepth = getPickDepth(this, 0);
    var context = frameState.context;
    var windowCoordinate = SceneTransforms.wgs84ToWindowCoordinates(this._scene,targetPosition,new Cartesian2());
    var drawingBufferPosition = SceneTransforms.transformWindowToDrawingBuffer(this._scene, windowCoordinate, new Cartesian2());
    drawingBufferPosition.y = this._scene.drawingBufferHeight - drawingBufferPosition.y;
    var depth = pickDepth.getDepth(context, drawingBufferPosition.x, drawingBufferPosition.y);

    具体屏幕坐标对应的深度值获取操作就顺理成章了,绘制结束后,直接获取深度图,计算绘制缓冲的坐标,y轴反转,就得到深度值了,封装很到位,看源码其实也就是WebGL坐标反算那套,这点很有用,很多用到深度对比的空间分析手法都可以插入,当然这是最简单粗暴的获取方式,因为中间Cesium又会根据各个绘制命令再次计算分支命令,其实有相当一部分是不需要的......关于这些绘制命令我们下节再讨论,不然本章不知道要写到猴年马月了。总有人问,Cesium深度图如何获取,是的,就是如此简单啊!

    接下来就是一桶更新,每一帧都是不一样的世界。

    视口指定和backgroundColor这些就没必要过多去讨论了。直接来看如下三个更新:

           1)  updateFrameState(scene);

            2) context.uniformState.update(frameState)

            3)  scene.fog.update(frameState)

    第一个函数主要任务是更新帧状态,而FrameState是Cesium里很重要的一个渲染变量,还有context、PassState等,这里面是一些环境基础更新,包块地球mode、相机、投影方式、太阳颜色、对数深度设置等等,后面的计算需要基于这些设置生成;第二函数主要复杂US状态机的更新,对太阳月亮信息做了进一步计算;第三个函数就是对雾的专门更新,包括density、sse、brightness。

    好了,总算是更新完了!

    好比一场大考,前面该装备的装备好了,该上战场了吧,下面三个函数我们习惯性放一起看。

    updateEnvironment(scene);
    updateAndExecuteCommands(scene, passState, backgroundColor);
    resolveFramebuffers(scene, passState);

    这三个方法可谓是整个渲染过程的中流砥柱!重要绘制在updateAndExecuteCommands()函数里一步一步完成实现的,其实地形要特殊一些,它的完成是在scene.globe.endFrame(frameState)基于四叉树逐步分瓦片请求。在updateAndExecuteCommands()函数里,一开始对不同的视图做了分别计算

     if (useWebVR) {
                executeWebVRCommands(scene, passState, backgroundColor);
            } else if (mode !== SceneMode.SCENE2D || scene._mapMode2D === MapMode2D.ROTATE) {
                executeCommandsInViewport(true, scene, passState, backgroundColor);
            } else {
                updateAndClearFramebuffers(scene, passState, backgroundColor);
                execute2DViewportCommands(scene, passState);
            }

    有没有很熟悉,针对VR做VR命令计算,针对3D在视口下计算,针对2D也有对应的计算。我们常见的自然是三维模式走的 executeCommandsInViewport(true, scene, passState, backgroundColor)方法。参数早在前面的的更新中早就准备好了,这个方法也很讲究。

    一开始是对所有的图元进行更新,这就包括我们在上层使用的一堆堆primitives、阴影图的更新,Cesium的机制是这样的,有什么三维数据类型 就有对象的集合类, 有add  就有对应的remove,对象渲染每个都有对应的update()函数,这个函数负责自己图元的渲染调度,这种设计很巧妙,真正意义做到了分而治之,而每个图元的update()就是这里遍历调用的,就像排队面试一样,HR只负责叫你伙计轮到你了,至于你要向我倾诉什么那也是你自己来说。

    if (!renderTranslucentDepthForPick) {
                updateAndRenderPrimitives(scene);
            }

    接下来该推Call了吧,但Cesium不急不忙,基于上面的图元来计算视锥体,这里就有点“多此一举”的感觉,其实不然。首先我们要明白,并不是每一帧都必须运用相同的视锥体,这是一个前提,然后,图形学里,我们知道当场景拉得很大很长的时候,近裁剪面与远裁剪面不得不拉开很大,这个时候有些问题就暴露出来了,最难看的便是精度问题。关于解决精度问题我们也可以将近裁剪面再拉远一点,或者远裁剪面在拉近一些,但这里面的开销以及效果只能说差强人意;多视锥体就此诞生了,它将当前场景分成了多个视锥体,通常2个左右,有效避免 z-fighting,每个frustum都有相同的视野范围和纵横比,只有近裁剪面和远裁剪面的距离不同。

    view.createPotentiallyVisibleSet(scene);

    最后,执行绘制命令,大功告成。

    executeCommands(scene, passState);
    resolveFramebuffers(scene, passState)函数主要是将前一个效果作为输入,计算后期渲染效果,再输出,作为下一个效果的输入,其中就包括我们知道的 


    WebGL如此炫丽,绽放真实世界!

    一阵清雨,躲进满是书香的小楼

    爱技术,爱交流 685834990
  • 相关阅读:
    ECharts概念学习系列之ECharts官网教程之在 webpack 中使用 ECharts(图文详解)
    ECharts概念学习系列之ECharts官网教程之自定义构建 ECharts(图文详解)
    Microsoft Power BI Desktop概念学习系列之Microsoft Power BI Desktop的官网自带示例数据(图文详解)
    Microsoft Power BI Desktop概念学习系列之Microsoft Power BI Desktop的下载和安装(图文详解)
    Microsoft Power BI Desktop概念学习系列之Microsoft Power BI Desktop是什么?
    ECharts概念学习系列之ECharts的下载和安装(图文详解)
    [转]Material使用08 MdDialogModule、MdAutocompleteModule
    [转]Angular 4|5 Material Dialog with Example
    [转] can not find module @angular/animations/browser
    [转]npm、 cnpm、yarn
  • 原文地址:https://www.cnblogs.com/GISCesium/p/10420492.html
Copyright © 2011-2022 走看看