• Cesium源码解析一(terrain文件的加载过程)


    1.前言

      目前市场上三维比较火的实现方案主要有两种,b/s 的方案主要是 Cesiumc/s 的方案主要是 ueskylineunity 也占一部分份额)。他们分别对应的是 WebGLOpenGL,其最终都是通过 Shader 来实现的,通过图形学来实现的,所以又回到了代码界永远不变的真理,不论什么语言,修炼内功才是王道,修炼数据结构与算法才是王道。b/s 的好处显而易见,不用客户端怎么配置,但是缺点是对于大数据量的加载,存在性能瓶颈。而 c/s 的方案,主要是为了快,但是需要一堆环境配置。所以就可以解释了,为什么 UE 占主流,因为 UEC++ 写的,它的速度可比用 C# 写的 skylineunity 快多了。

    2.本篇的由来

      本篇博文起源于我们加载 terrain 文件时遇到的一个问题,terrain文件不能正常解析。最开始是加载 terrain 会导致影像图层也出不来了,后来是影像出来了,但是地形一直出不来。因此,我们决定看一看源码,就有了这一篇博文。

    3.terrain文件的加载

      这是我们这一篇的核心,因为这里面的代码量非常大,涉及的细节非常多,所以,最终我们要通过图形化的方式,来对这一过程进行逐步解析。我们首先来看一下terrain 文件的加载,代码非常简单:

     var terrain=new Cesium.CesiumTerrainProvider({
       url:"http://localhost:8090/geoserver/terrain/globe",//有水面
       requestVertexNormals : false,
       requestWaterMask : true,
     });  
     viewer.terrainProvider=terrain;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      接着,一步步的向下跟踪,我们就得到了这样一张总体调用流程图:
    在这里插入图片描述
      由此,我们就得出了这样一个结论:Cesium 中的渲染,是由 startRenderLoop 这个函数来开启的,而 requestAnnimationFrame 的作用就是每一帧都去调用 render 函数,且 requestAnnimationFrame 能够保证渲染刷新的频率和浏览器的频率保持一致,当页面切换到后台时,就会停止渲染以提升性能。而 render 函数最终调用的地方则是 Scene.render ,这是整个渲染机制的控制中枢。我们来看看这个控制中枢都干了些什么:
      1.更新环境

    scene.updateEnvironment();
    
    • 1

      2.更新和执行渲染命令

    scene.updateAndExecuteCommands();
    
    • 1

      3.数据优化

    scene.resolveFramebuffers();
    
    • 1

      4.结束当前帧

    scene.globe.endFrame();
    Context.endFrame();
    
    • 1
    • 2

      看到这里,应该会发现,逻辑还是十分清晰的吧。但是细心的读者,应该会发现,第2步执行渲染命令,为什么会在第3步解析当前帧数据之前?第2步还没有数据呢,要渲染什么数据呢?我在这里先给出结论,后面我们会详细展开,结论就是:Cesium渲染的是上一帧的数据,因为上一帧的数据解析完成后,并没有真正的去执行,只是转为了 Shader 命令并加入到了渲染的队列中去,真正的去执行 Shader 命令,是在下一帧进行的。所以,Cesium 的渲染是具有滞后性的。

    3.1 更新环境

      这一步的代码并不复杂,主要是为了更新天体和地球环境的影响。比如大气、天空、太阳、月亮,以及是否使用 WebVR 等,我们来看一下这个函数。

    Scene.prototype.updateEnvironment = function () {
      var frameState = this._frameState;
      var view = this._view;
    
      // Update celestial and terrestrial environment effects.
      var environmentState = this._environmentState;
      var renderPass = frameState.passes.render;
      var offscreenPass = frameState.passes.offscreen;
      var skyAtmosphere = this.skyAtmosphere;
      var globe = this.globe;
      var globeTranslucencyState = this._globeTranslucencyState;
    
      if (
        !renderPass ||
        (this._mode !== SceneMode.SCENE2D &&
          view.camera.frustum instanceof OrthographicFrustum) ||
        !globeTranslucencyState.environmentVisible
      ) {
        environmentState.skyAtmosphereCommand = undefined;
        environmentState.skyBoxCommand = undefined;
        environmentState.sunDrawCommand = undefined;
        environmentState.sunComputeCommand = undefined;
        environmentState.moonCommand = undefined;
      } else {
        if (defined(skyAtmosphere)) {
          if (defined(globe)) {
            skyAtmosphere.setDynamicAtmosphereColor(
              globe.enableLighting && globe.dynamicAtmosphereLighting,
              globe.dynamicAtmosphereLightingFromSun
            );
            environmentState.isReadyForAtmosphere =
              environmentState.isReadyForAtmosphere ||
              globe._surface._tilesToRender.length > 0;
          }
          environmentState.skyAtmosphereCommand = skyAtmosphere.update(
            frameState,
            globe
          );
          if (defined(environmentState.skyAtmosphereCommand)) {
            this.updateDerivedCommands(environmentState.skyAtmosphereCommand);
          }
        } else {
          environmentState.skyAtmosphereCommand = undefined;
        }
    
        environmentState.skyBoxCommand = defined(this.skyBox)
          ? this.skyBox.update(frameState, this._hdr)
          : undefined;
        var sunCommands = defined(this.sun)
          ? this.sun.update(frameState, view.passState, this._hdr)
          : undefined;
        environmentState.sunDrawCommand = defined(sunCommands)
          ? sunCommands.drawCommand
          : undefined;
        environmentState.sunComputeCommand = defined(sunCommands)
          ? sunCommands.computeCommand
          : undefined;
        environmentState.moonCommand = defined(this.moon)
          ? this.moon.update(frameState)
          : undefined;
      }
    
      var clearGlobeDepth = (environmentState.clearGlobeDepth =
        defined(globe) &&
        globe.show &&
        (!globe.depthTestAgainstTerrain || this.mode === SceneMode.SCENE2D));
      var useDepthPlane = (environmentState.useDepthPlane =
        clearGlobeDepth &&
        this.mode === SceneMode.SCENE3D &&
        globeTranslucencyState.useDepthPlane);
      if (useDepthPlane) {
        // Update the depth plane that is rendered in 3D when the primitives are
        // not depth tested against terrain so primitives on the backface
        // of the globe are not picked.
        this._depthPlane.update(frameState);
      }
    
      environmentState.renderTranslucentDepthForPick = false;
      environmentState.useWebVR =
        this._useWebVR && this.mode !== SceneMode.SCENE2D && !offscreenPass;
    
      var occluder =
        frameState.mode === SceneMode.SCENE3D &&
        !globeTranslucencyState.sunVisibleThroughGlobe
          ? frameState.occluder
          : undefined;
      var cullingVolume = frameState.cullingVolume;
    
      // get user culling volume minus the far plane.
      var planes = scratchCullingVolume.planes;
      for (var k = 0; k < 5; ++k) {
        planes[k] = cullingVolume.planes[k];
      }
      cullingVolume = scratchCullingVolume;
    
      // Determine visibility of celestial and terrestrial environment effects.
      environmentState.isSkyAtmosphereVisible =
        defined(environmentState.skyAtmosphereCommand) &&
        environmentState.isReadyForAtmosphere;
      environmentState.isSunVisible = this.isVisible(
        environmentState.sunDrawCommand,
        cullingVolume,
        occluder
      );
      environmentState.isMoonVisible = this.isVisible(
        environmentState.moonCommand,
        cullingVolume,
        occluder
      );
    
      var envMaps = this.specularEnvironmentMaps;
      var envMapAtlas = this._specularEnvironmentMapAtlas;
      if (
        defined(envMaps) &&
        (!defined(envMapAtlas) || envMapAtlas.url !== envMaps)
      ) {
        envMapAtlas = envMapAtlas && envMapAtlas.destroy();
        this._specularEnvironmentMapAtlas = new OctahedralProjectedCubeMap(envMaps);
      } else if (!defined(envMaps) && defined(envMapAtlas)) {
        envMapAtlas.destroy();
        this._specularEnvironmentMapAtlas = undefined;
      }
    
      if (defined(this._specularEnvironmentMapAtlas)) {
        this._specularEnvironmentMapAtlas.update(frameState);
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127

    3.2 更新和执行渲染命令

      这一步的代码量是很大的,但主要的核心思想就是会去执行当前帧对象 frameStatecommandList 中的多个或一个 DrawCommand。这一步的执行过程我们可以用这样一张图来表示:
    在这里插入图片描述
      通过上图我们可以发现,最终是在 Context.prototype.draw() 函数中去执行 beginDraw()continueDraw() 方法来实现执行 WebGLShader 命令的。
      然后在执行到 updateAndRenderPrimitives(); 时又会去走另外一个分支,此时,上面这样图就变成了这样:
    在这里插入图片描述
      发现了什么?这里会根据当前帧对象 frameState 去选择要渲染哪些切片,然后再去创建命令。这两部分别对应的是图中加黑的两行,即 selectTilesForRendering(this, frameState);createRenderCommandsForSelectedTiles(this, frameState); 然而,这样还没完,在执行 selectTilesForRendering(this, frameState); 时,还会去走一个分支,上面这样图的左边就会变成:
    在这里插入图片描述
      所以,当 tile 可见时,就会被加入到渲染队列中去,等待后面的渲染,否则就会进入加载队列等待加载。

    3.3 数据优化

      这一步主要是判断是否使用 OIT(半透明渲染算法)、全球深度和后期处理,来进行一系列的优化,代码也不复杂,我们来看一下。

    Scene.prototype.resolveFramebuffers = function (passState) {
      var context = this._context;
      var environmentState = this._environmentState;
      var view = this._view;
      var globeDepth = view.globeDepth;
    
      var useOIT = environmentState.useOIT;
      var useGlobeDepthFramebuffer = environmentState.useGlobeDepthFramebuffer;
      var usePostProcess = environmentState.usePostProcess;
    
      var defaultFramebuffer = environmentState.originalFramebuffer;
      var globeFramebuffer = useGlobeDepthFramebuffer
        ? globeDepth.framebuffer
        : undefined;
      var sceneFramebuffer = view.sceneFramebuffer.getFramebuffer();
      var idFramebuffer = view.sceneFramebuffer.getIdFramebuffer();
    
      if (environmentState.separatePrimitiveFramebuffer) {
        // Merge primitive framebuffer into globe framebuffer
        globeDepth.executeMergeColor(context, passState);
      }
    
      if (useOIT) {
        passState.framebuffer = usePostProcess
          ? sceneFramebuffer
          : defaultFramebuffer;
        view.oit.execute(context, passState);
      }
    
      var translucentTileClassification = view.translucentTileClassification;
      if (
        translucentTileClassification.hasTranslucentDepth &&
        translucentTileClassification.isSupported()
      ) {
        translucentTileClassification.execute(this, passState);
      }
    
      if (usePostProcess) {
        var inputFramebuffer = sceneFramebuffer;
        if (useGlobeDepthFramebuffer && !useOIT) {
          inputFramebuffer = globeFramebuffer;
        }
    
        var postProcess = this.postProcessStages;
        var colorTexture = inputFramebuffer.getColorTexture(0);
        var idTexture = idFramebuffer.getColorTexture(0);
        var depthTexture = defaultValue(globeFramebuffer, sceneFramebuffer)
          .depthStencilTexture;
        postProcess.execute(context, colorTexture, depthTexture, idTexture);
        postProcess.copy(context, defaultFramebuffer);
      }
    
      if (!useOIT && !usePostProcess && useGlobeDepthFramebuffer) {
        passState.framebuffer = defaultFramebuffer;
        globeDepth.executeCopyColor(context, passState);
      }
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    这一步让人难以理解的是,干的工作都是优化,但是函数名翻译过来却叫做解析当前帧数据,这就令人费解了,因为真正的解析 terrain 数据,是在下一步干的,真是令人百思不得其解。

    3.4 结束当前帧

      这一步是重点中的重点,因为会在这一步去解析 terrain 文件,在此之前,我们先来看下这一步调用的流程图:
    在这里插入图片描述
      可以看到这一步可以简单概括为三行代码,处理切片加载队列、更新高度、更新切片加载过程。但是图中的第一行代码又进行了其他的一系列操作,其中就有我们非常关心的 terrain 文件的解析,所以,这张图就变成了这样:
    在这里插入图片描述
      可以看到最后一步就是去创建 terrain 数据对象,这种数据类型是 Cesium 定义的,类型就叫做 QuantizedMesh。那么重点就来了,我们就是要看看它到底是怎么解析的,直接上代码:

    function createQuantizedMeshTerrainData(provider, buffer, level, x, y, layer) {
      var littleEndianExtensionSize = layer.littleEndianExtensionSize;
      var pos = 0;
      var cartesian3Elements = 3;
      var boundingSphereElements = cartesian3Elements + 1;
      var cartesian3Length = Float64Array.BYTES_PER_ELEMENT * cartesian3Elements;
      var boundingSphereLength =
        Float64Array.BYTES_PER_ELEMENT * boundingSphereElements;
      var encodedVertexElements = 3;
      var encodedVertexLength =
        Uint16Array.BYTES_PER_ELEMENT * encodedVertexElements;
      var triangleElements = 3;
      var bytesPerIndex = Uint16Array.BYTES_PER_ELEMENT;
      var triangleLength = bytesPerIndex * triangleElements;
    
      var view = new DataView(buffer);
      //中心
      var center = new Cartesian3(
        view.getFloat64(pos, true),
        view.getFloat64(pos + 8, true),
        view.getFloat64(pos + 16, true)
      );
      pos += cartesian3Length;
      //最大高度最小高度
      var minimumHeight = view.getFloat32(pos, true);
      pos += Float32Array.BYTES_PER_ELEMENT;
      var maximumHeight = view.getFloat32(pos, true);
      pos += Float32Array.BYTES_PER_ELEMENT;
    
      //外接球面
      var boundingSphere = new BoundingSphere(
        new Cartesian3(
          view.getFloat64(pos, true),
          view.getFloat64(pos + 8, true),
          view.getFloat64(pos + 16, true)
        ),
        view.getFloat64(pos + cartesian3Length, true)
      );
      pos += boundingSphereLength;
      //水平遮挡点
      var horizonOcclusionPoint = new Cartesian3(
        view.getFloat64(pos, true),
        view.getFloat64(pos + 8, true),
        view.getFloat64(pos + 16, true)
      );
      pos += cartesian3Length;
      //顶点数量
      var vertexCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var encodedVertexBuffer = new Uint16Array(buffer, pos, vertexCount * 3);
      pos += vertexCount * encodedVertexLength;
    
      if (vertexCount > 64 * 1024) {
        // More than 64k vertices, so indices are 32-bit.
        bytesPerIndex = Uint32Array.BYTES_PER_ELEMENT;
        triangleLength = bytesPerIndex * triangleElements;
      }
    
      // Decode the vertex buffer.
      var uBuffer = encodedVertexBuffer.subarray(0, vertexCount);
      var vBuffer = encodedVertexBuffer.subarray(vertexCount, 2 * vertexCount);
      var heightBuffer = encodedVertexBuffer.subarray(
        vertexCount * 2,
        3 * vertexCount
      );
    
      AttributeCompression.zigZagDeltaDecode(uBuffer, vBuffer, heightBuffer);
    
      // skip over any additional padding that was added for 2/4 byte alignment
      if (pos % bytesPerIndex !== 0) {
        pos += bytesPerIndex - (pos % bytesPerIndex);
      }
      //三角形的数量
      var triangleCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var indices = IndexDatatype.createTypedArrayFromArrayBuffer(
        vertexCount,
        buffer,
        pos,
        triangleCount * triangleElements
      );
      pos += triangleCount * triangleLength;
    
      // High water mark decoding based on decompressIndices_ in webgl-loader's loader.js.
      // https://code.google.com/p/webgl-loader/source/browse/trunk/samples/loader.js?r=99#55
      // Copyright 2012 Google Inc., Apache 2.0 license.
      var highest = 0;
      var length = indices.length;
      for (var i = 0; i < length; ++i) {
        var code = indices[i];
        indices[i] = highest - code;
        if (code === 0) {
          ++highest;
        }
      }
      //东南西北顶点的解析
      var westVertexCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var westIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
        vertexCount,
        buffer,
        pos,
        westVertexCount
      );
      pos += westVertexCount * bytesPerIndex;
    
      var southVertexCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var southIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
        vertexCount,
        buffer,
        pos,
        southVertexCount
      );
      pos += southVertexCount * bytesPerIndex;
    
      var eastVertexCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var eastIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
        vertexCount,
        buffer,
        pos,
        eastVertexCount
      );
      pos += eastVertexCount * bytesPerIndex;
    
      var northVertexCount = view.getUint32(pos, true);
      pos += Uint32Array.BYTES_PER_ELEMENT;
      var northIndices = IndexDatatype.createTypedArrayFromArrayBuffer(
        vertexCount,
        buffer,
        pos,
        northVertexCount
      );
      pos += northVertexCount * bytesPerIndex;
    
      var encodedNormalBuffer;
      var waterMaskBuffer;
      
      while (pos < view.byteLength) {
        var extensionId = view.getUint8(pos, true);
        pos += Uint8Array.BYTES_PER_ELEMENT;
        var extensionLength = view.getUint32(pos, littleEndianExtensionSize);
        pos += Uint32Array.BYTES_PER_ELEMENT;
        console.log( level, x, y,"extensionid="+extensionId+",extensionLength="+extensionLength+",byteLength="+view.byteLength);
        if (
          extensionId === QuantizedMeshExtensionIds.OCT_VERTEX_NORMALS &&
          provider._requestVertexNormals
        ) {
          //数据中有光照并且也请求了光照
          encodedNormalBuffer = new Uint8Array(buffer, pos, vertexCount * 2);
        } else if (
          extensionId === QuantizedMeshExtensionIds.WATER_MASK &&
          provider._requestWaterMask
        ) {
          //数据中有水面并且也请求了水面
          waterMaskBuffer = new Uint8Array(buffer, pos, extensionLength);
        } else if (
          extensionId === QuantizedMeshExtensionIds.METADATA &&
          provider._requestMetadata
        ) {
          //数据中有元数据并且也请求了元数据
          var stringLength = view.getUint32(pos, true);
          console.log("metadata_length="+stringLength);
          if (stringLength > 0) {
            var metadata = getJsonFromTypedArray(
              new Uint8Array(buffer),
              pos + Uint32Array.BYTES_PER_ELEMENT,
              stringLength
            );
            var availableTiles = metadata.available;
    
            // console.log("availableTiles="+availableTiles);
    
            if(level==0 &&  x==1 &&  y==0){
              if(sessionStorage.getItem("str010")){
                availableTiles=JSON.parse(sessionStorage.getItem("str010"))
              }else{
                sessionStorage.setItem("str010",JSON.stringify(availableTiles));
              }
            }
            if(level==0 &&  x==0 &&  y==0){
              if(sessionStorage.getItem("str000")){
                availableTiles=JSON.parse(sessionStorage.getItem("str000"))
              }else{
                sessionStorage.setItem("str000",JSON.stringify(availableTiles));
              }
              
            }
    
            // if(level % 10 !=0) availableTiles=undefined;
            if (defined(availableTiles)) {
              for (var offset = 0; offset < availableTiles.length; ++offset) {
                var availableLevel = level + offset + 1;
                var rangesAtLevel = availableTiles[offset];
                var yTiles = provider._tilingScheme.getNumberOfYTilesAtLevel(
                  availableLevel
                );
    
                for (
                  var rangeIndex = 0;
                  rangeIndex < rangesAtLevel.length;
                  ++rangeIndex
                ) {
                  var range = rangesAtLevel[rangeIndex];
                  var yStart = yTiles - range.endY - 1;
                  var yEnd = yTiles - range.startY - 1;
                  provider.availability.addAvailableTileRange(
                    availableLevel,
                    range.startX,
                    yStart,
                    range.endX,
                    yEnd
                  );
                  layer.availability.addAvailableTileRange(
                    availableLevel,
                    range.startX,
                    yStart,
                    range.endX,
                    yEnd
                  );
                }
              }
            }
          }
          layer.availabilityTilesLoaded.addAvailableTileRange(level, x, y, x, y);
        }
        pos += extensionLength;
      }
      //裙摆高度
      var skirtHeight = provider.getLevelMaximumGeometricError(level) * 5.0;
    
      // The skirt is not included in the OBB computation. If this ever
      // causes any rendering artifacts (cracks), they are expected to be
      // minor and in the corners of the screen. It's possible that this
      // might need to be changed - just change to `minimumHeight - skirtHeight`
      // A similar change might also be needed in `upsampleQuantizedTerrainMesh.js`.
      var rectangle = provider._tilingScheme.tileXYToRectangle(x, y, level);
      var orientedBoundingBox = OrientedBoundingBox.fromRectangle(
        rectangle,
        minimumHeight,
        maximumHeight,
        provider._tilingScheme.ellipsoid
      );
    
      return new QuantizedMeshTerrainData({
        center: center,
        minimumHeight: minimumHeight,
        maximumHeight: maximumHeight,
        boundingSphere: boundingSphere,
        orientedBoundingBox: orientedBoundingBox,
        horizonOcclusionPoint: horizonOcclusionPoint,
        quantizedVertices: encodedVertexBuffer,
        encodedNormals: encodedNormalBuffer,
        indices: indices,
        westIndices: westIndices,
        southIndices: southIndices,
        eastIndices: eastIndices,
        northIndices: northIndices,
        westSkirtHeight: skirtHeight,
        southSkirtHeight: skirtHeight,
        eastSkirtHeight: skirtHeight,
        northSkirtHeight: skirtHeight,
        childTileMask: provider.availability.computeChildMaskForTile(level, x, y),
        waterMask: waterMaskBuffer,
        credits: provider._tileCredits,
      });
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268

      重要的地方都加上中文注释了,仔细看上面的代码,可以发现其中包括水面、光照、元数据等的解析,都是在一个 while 循环中完成的。至此,我们的数据也就解析完了。
      最后我们来看看贯穿了所有过程的 frameState 对象,他怎么引用的:
    在这里插入图片描述
      用一句话总结就是 frameState 对象中的 commandList 中的 Command 对象引用了 tile 对象,这样我们的整个从数据解析到渲染的过程就都串联起来了。

    4. 总结

      本文通过梳理 terrain 文件的加载、解析、渲染过程,基本理清了整个 Cesium 的渲染过程,只是最后的 WebGLShader 命令没有进行深入,留待后续完善。这一块需要计算题图形学的知识,现在三维方面,不论是 UE 还是 Cesium ,都要计算机图形学的相关知识储备才能进行深入探讨。本文是作者看了好多天源码整理出来的,由于个人水平有限,其中难免有一些理解不足之处,欢迎读者指正。

  • 相关阅读:
    旧华硕电脑开机非常慢 电脑开机黑屏很久才显示品牌logo导致整体开机速度非常的慢怎么办
    释放潜能!RunnerGo:性能测试的全新视角
    android 多个Bitmap 拼接成一个Bitmap
    vue/react/js 常用的原生获取当前页面的url网址的相关方法
    flutter 嵌套 StatefulWidget 不刷新
    Netty02——Netty 入门
    独立站新手卖家应注意的要点
    巧用 background-clip 实现超强的文字动效
    Java版企业工程项目管理系统源码+java版本+项目模块功能清单+spring cloud +spring boot
    14 幂等生产者和事务生产者
  • 原文地址:https://blog.csdn.net/xiangshangdemayi/article/details/125359881