Cesium原理篇:7最长的一帧之Entity(下)

上一篇,我们介绍了当我们添加一个Entity时,通过Graphics封装其对应参数,通过EntityCollection.Add方法,将EntityCollection的Entity传递到DataSourceDisplay.Visualizer中。本篇则从Visualizer开始,介绍数据的处理,并最终实现渲染的过程。

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

如上,在渲染阶段,分别调用了clock.tick()和scene.render()。在这两个阶段中都有很多跟Entity相关的方面,我们分别阐述其大概过程

Viewer.prototype._onTick

我们先温习一下上篇的两个知识点:DataSourceDisplay初始化的时候会调用defaultVisualizersCallback,会针对所有Geometry的Type创建对应的Visualizer;EntityCollection.Add每次添加一个Entity,会通过一系列事件传递,将该Entity传递到每一个Visualizer,保存到Visualizer中_addedObjects队列中。

function Viewer(container, options) {
    eventHelper.add(clock.onTick, Viewer.prototype._onTick, this);
}

Viewer.prototype._onTick = function(clock) {
    var time = clock.currentTime;
    var isUpdated = this._dataSourceDisplay.update(time);
}

DataSourceDisplay.prototype.update = function(time) {
    visualizers = this._defaultDataSource._visualizers;
    vLength = visualizers.length;
    for (x = 0; x < vLength; x++) {
        result = visualizers[x].update(time) && result;
    }
}

如上,Viewer初始化时会绑定clock.onTick事件,确保每一帧都会调用。而其内部则调用DataSourceDisplay.update,进而遍历所有的Visualizer,调用其update方法。下面,我们重点看一下Visualizer.update到底干了哪些事情。为了方便,我们还是以Rectangle为例来展开。

GeometryVisualizer.prototype.update = function(time) {
    // 获取添加Entity队列
    var addedObjects = this._addedObjects;
    var added = addedObjects.values;

    for (i = added.length - 1; i > -1; i--) {
        entity = added[i];
        id = entity.id;
        // 每一个GeometryVisualizer都绑定一个具体的Updater,用来解析Entity,以Rectangle为例
        // Rectangle则由对应的RectangleGeometryUpdater来解析
        // 通过new Updater,将Entity对应的RectangleGraphics解析为RectangleGeometryUpdater的GeometryOptions
        updater = new this._type(entity, this._scene);
        this._updaters.set(id, updater);
        // 根据该RectangleGeometryUpdater的材质风格创建对应的GeometryInstance,分到对应的批次队列中
        // 每一个批次队列中的Geometry风格相同,因此可以通过一次DrawCommand渲染该队列中所有Geometry
        // 目的是减少渲染次数,提高渲染效率
        insertUpdaterIntoBatch(this, time, updater);
        this._subscriptions.set(id, updater.geometryChanged.addEventListener(GeometryVisualizer._onGeometryChanged, this));
    }

    // 清空添加Entity队列,下次update中就不用重复处理
    addedObjects.removeAll();

    var isUpdated = true;
    var batches = this._batches;
    var length = batches.length;
    for (i = 0; i < length; i++) {
        // 对所有批次队列进行更新
        // 根据batch的GeometryInstance创建Primitive,并添加到Scene的PrimitiveCollection中
        isUpdated = batches[i].update(time) && isUpdated;
    }

    return isUpdated;
};

如上结合代码和注释,分为三步,下面我们详细介绍一下:

  • new Updater
  • insertUpdaterIntoBatch
  • batch.update

1.new Updater

function RectangleGeometryUpdater(entity, scene) {
    this._options = new GeometryOptions(entity);
    this._onEntityPropertyChanged(entity, ‘rectangle‘, entity.rectangle, undefined);
}

如上,简单说,Updater的构造函数主要就做了一件事情,构建对应的GeometryOptions,并对其赋值。GeometryOptions是Cesium的一个简单的封装,不同的Updater对应不同的Graphics,GeometryOptions的属性也是根据不同的Graphics量身定做,比如RectangleGeometryUpdater中对应的GeometryOptions属性如下:

function GeometryOptions(entity) {
    this.id = entity;
    this.vertexFormat = undefined;
    this.rectangle = undefined;
    this.closeBottom = undefined;
    this.closeTop = undefined;
    this.height = undefined;
    this.extrudedHeight = undefined;
    this.granularity = undefined;
    this.stRotation = undefined;
    this.rotation = undefined;
}

大家可以看看其他的Updater,比如PolygonGeometryUpdater,EllipseGeometryUpdater等等,对应的GeometryOptions也不相同。这样,从设计的角度,将不同Graphics中对应的不同属性,封装成一个标准的GeometryOptions,对外表象一致(都是Updater中的_options属性),内部各自提供解析方法(_onEntityPropertyChanged方法),我们再看一下RectangleGeometryUpdater的_onEntityPropertyChanged实现:

RectangleGeometryUpdater.prototype._onEntityPropertyChanged = function(entity, propertyName, newValue, oldValue) {
    var rectangle = this._entity.rectangle;

    // ……
    var height = rectangle.height;
    // ……

    var options = this._options;
    options.vertexFormat = isColorMaterial ? PerInstanceColorAppearance.VERTEX_FORMAT : MaterialAppearance.MaterialSupport.TEXTURED.vertexFormat;
    options.rectangle = coordinates.getValue(Iso8601.MINIMUM_VALUE, options.rectangle);
    options.height = defined(height) ? height.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.extrudedHeight = defined(extrudedHeight) ? extrudedHeight.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.granularity = defined(granularity) ? granularity.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.stRotation = defined(stRotation) ? stRotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.rotation = defined(rotation) ? rotation.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.closeBottom = defined(closeBottom) ? closeBottom.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    options.closeTop = defined(closeTop) ? closeTop.getValue(Iso8601.MINIMUM_VALUE) : undefined;
    this._isClosed = defined(extrudedHeight) && defined(options.closeTop) && defined(options.closeBottom) && options.closeTop && options.closeBottom;
    this._outlineWidth = defined(outlineWidth) ? outlineWidth.getValue(Iso8601.MINIMUM_VALUE) : 1.0;
    this._dynamic = false;
    this._geometryChanged.raiseEvent(this);
 }

如上是代码片段,this._entity.rectangle为RectangleGraphics类,将其属性赋给_options(GeometryOptions类),属性赋值的过程也是一个自检测的过程,如果存在必要属性缺失的情况则指定一个默认值,最终完成了RectangleGraphics到GeometryOptions的转移。

2.insertUpdaterIntoBatch

function insertUpdaterIntoBatch(that, time, updater) {
    if (updater.outlineEnabled) {
        that._outlineBatches[shadows].add(time, updater);
    }

    if (updater.fillEnabled) {
        if (updater.onTerrain) {
            that._groundColorBatch.add(time, updater);
        } else {
            if (updater.isClosed) {
                if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
                    that._closedColorBatches[shadows].add(time, updater);
                } else {
                    that._closedMaterialBatches[shadows].add(time, updater);
                }
            } else {
                if (updater.fillMaterialProperty instanceof ColorMaterialProperty) {
                    that._openColorBatches[shadows].add(time, updater);
                } else {
                    that._openMaterialBatches[shadows].add(time, updater);
                }
            }
        }
    }
}

不准确的说(但有助于理解),GeometryOptions主要对应RectangleGraphics的几何数值,而在insertUpdaterIntoBatch中则根据RectangleGraphics的材质风格进行分组,只有材质一致的RectangleGeometryUpdater才能分到一起,进行后面的批次。比如学校分班,优等生,中等生分到不同的班级,老师根据不同班级的能力进行适当的区分,就是一种通过分组的方式来优化的思路。打组批次也是同样一个道理。

针对GeometryVisualizer,一共提供了四种Batch类型:

  • StaticGeometryColorBatch
  • StaticGeometryPerMaterialBatch
  • StaticGroundGeometryColorBatch
  • StaticOutlineGeometryBatch

不同的Batch,根据材质属性的不同,会选择Updater的对应方法,创建GeometryInstance。比如RectangleGeometryUpdater提供了createFillGeometryInstance和createOutlineGeometryInstance两个方法来创建其面和边线对应的GeometryInstance。如下是RectangleGeometryUpdater对应的一种逻辑情况:

StaticOutlineGeometryBatch.prototype.add = function(time, updater) {
    // Key 1
    var instance = updater.createOutlineGeometryInstance(time);
    var width = this._scene.clampLineWidth(updater.outlineWidth);
    var batches;
    var batch;
    if (instance.attributes.color.value[3] === 255) {
        batches = this._solidBatches;
        batch = batches.get(width);
        if (!defined(batch)) {
            batch = new Batch(this._primitives, false, width, this._shadows);
            batches.set(width, batch);
        }
        // Key 2
        batch.add(updater, instance);
    }
};
// Key 1
RectangleGeometryUpdater.prototype.createFillGeometryInstance = function(time) {
    return new GeometryInstance({
        id : entity,
        geometry : new RectangleGeometry(this._options),
        attributes : attributes
    });
};
// Key 2
Batch.prototype.add = function(updater, instance) {
    var id = updater.entity.id;
    this.createPrimitive = true;
    this.geometry.set(id, instance);
    this.updaters.set(id, updater);
};

第一是构建GeometryInstance,这里有一个新的对象RectangleGeometry,之前我们对于Rectangle的理解,都是在Graphics这样的一个概念,可以认为这是一个参数化的对象,对用户而言容易理解。比如一个圆对应的参数化信息就是圆心+半径,我们很好理解,但对计算机,或者WebGL则不能理解,WebGL能理解的是三角形,所以我们就需要把这个圆分解成三角形的拼接,比如切成一块块的西瓜状。将参数化的图形分解成非参数的简单三角形。这个过程是在Primitive.update中完成的,但最终是由RectangleGeometry提供的算法来实现。其他Geometry也是同样的一个逻辑。第二,把创建的GeometryInstance放到Batch队列中。

3.batch.update

之前的步骤1和步骤2,我们对当前这一帧中新增的Entity进行解析,构造成对应的GeometryInstance,放到对应的Batch队列中。比如有两个Rectangle类型的Entity,假设他们的风格一样,都是纯色的,当然颜色可能不相同,但最终都是在一个批次队列(StaticOutlineGeometryBatch)。接下来,1每一个批次队列会构建一个Primitive,包括该队列中所有的GeometryInstances,因为显卡强大的并行能力,绘制一个三角面和绘制N个三角面的所需的时间是一样的(N取决于顶点数),2所以尽可能的将多个Geometry封装成一个VBO是提高渲染性能的一个关键思路(批次&实例化)。而这个batch.update完成的前半部分,而Primitive.update则完成了最后,也是最关键的一步。

function Batch(primitives, translucent, appearanceType, closed, shadows) {
    // 每一个Batch中的GeometryInstance队列
    this.geometry = new AssociativeArray();
}

Batch.prototype.add = function(updater, instance) {
    var id = updater.entity.id;
    // 添加新的GeometryInstance,并标识,此时需要创建Primitvie
    this.createPrimitive = true;
    this.geometry.set(id, instance);
};

Batch.prototype.update = function(time) {
    var isUpdated = true;
    var removedCount = 0;
    var primitive = this.primitive;
    var primitives = this.primitives;
    var attributes;
    var i;

    // 检测需要创建Primitive
    if (this.createPrimitive) {
        var geometries = this.geometry.values;
        var geometriesLength = geometries.length;
        if (geometriesLength > 0) {
            // 将队列中所有的GeometryInstances封装成一个primitive对象
            primitive = new Primitive({
                asynchronous : true,
                geometryInstances : geometries,
                appearance : new this.appearanceType({
                    translucent : this.translucent,
                    closed : this.closed
                }),
                shadows : this.shadows
            });
            // 将primitive添加到Scene.primitives中
            // 既然已经绑定到Scene,接下来就要准备渲染
            primitives.add(primitive);
            isUpdated = false;
        } 

        this.attributes.removeAll();
        this.primitive = primitive;
        this.createPrimitive = false;
        this.waitingOnCreate = true;
    }
    return isUpdated;
};

如上是this._clock.tick()的一个大概过程,一步一步摩擦,最终创建了Primitive,并添加到PrimitiveCollection队列中,如果以Rectangle类型的Entity为例,大概的流程如下:

DataSourceDisplay.prototype.update
    GeometryVisualizer.prototype.update
        updater = new this._type(entity, this._scene);
            new GeometryOptions(entity);
            _onEntityPropertyChanged()

        insertUpdaterIntoBatch
            StaticGeometryColorBatch.prototype.add
                RectangleGeometryUpdater.prototype.createFillGeometryInstance
                    new GeometryInstance()
                Batch.prototype.add

        batches[i].update(time)
            StaticGeometryColorBatch.prototype.update
                Batch.prototype.update
                    new Primitive()
                    primitives.add(primitive);

Primitive.prototype.update

看完了tick()后,马不停蹄的则开始了this._scene.render(currentTime),大概经过下面的几层,最终开始了Primitive.prototype.update,也就是接下来我们介绍的中重点。

this._scene.render(currentTime);
    Scene.prototype.render
        function render(scene, time)
            function updateAndExecuteCommands()
                function executeCommandsInViewport()
                    function updatePrimitives()
                        PrimitiveCollection.prototype.update()
                            for (var i = 0; i < primitives.length; ++i) {
                                primitives[i].update(frameState);
                            }

如上的铺垫工作结束,进入正文。Primitive.prototype.update究竟做了哪些重要的事情.

var PrimitiveState = {
    READY : 0,
    CREATING : 1,
    CREATED : 2,
    COMBINING : 3,
    COMBINED : 4,
    COMPLETE : 5,
    FAILED : 6
};

似曾相识的感觉有没有。在设计上,Primitive和Globe相似,也是基于状态的管理:每个状态都有专门的模块来负责,而每一帧主要用来维护和更新状态,并根据当前的状态来调用对应的模块。我们看看PrimitiveState,里面主要有三类:CREATE,COMBINE,COMPLETE。心里大概有个一知半解,下面来解惑。

Primitive.prototype.update = function(frameState) {
    if (this._batchTable.attributes.length > 0) {
        this._batchTable.update(frameState);
    }

    if (this._state !== PrimitiveState.COMPLETE && this._state !== PrimitiveState.COMBINED) {
        if (this.asynchronous) {
            loadAsynchronous(this, frameState);
        } else {
            loadSynchronous(this, frameState);
        }
    }

    if (this._state === PrimitiveState.COMBINED) {
        createVertexArray(this, frameState);
    }

     if (!this.show || this._state !== PrimitiveState.COMPLETE) {
        return;
    }

    if (createRS) {
        var rsFunc = defaultValue(this._createRenderStatesFunction, createRenderStates);
        rsFunc(this, context, appearance, twoPasses);
    }

    if (createSP) {
        var spFunc = defaultValue(this._createShaderProgramFunction, createShaderProgram);
        spFunc(this, frameState, appearance);
    }

    if (createRS || createSP) {
        var commandFunc = defaultValue(this._createCommandsFunction, createCommands);
        commandFunc(this, appearance, material, translucent, twoPasses, this._colorCommands, this._pickCommands, frameState);
    }

    updateAndQueueCommandsFunc();
}

如上是Primitive.update的主要过程,我们以状态的变化为序,介绍一下loadAsynchronous,createVertexArray以及create*这几个内容。

loadAsynchronous

Primitive初始化时,默认为READY状态。Update方法中,首先会进入loadAsynchronous方法。这里主要做了两个事情:Create&Combine。

function loadAsynchronous(primitive, frameState) {
    var instances;
    var geometry;
    var i;
    var j;

    var instanceIds = primitive._instanceIds;

    //开始进入createGeometry
    if (primitive._state === PrimitiveState.READY) {
        instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];
        var length = primitive._numberOfInstances = instances.length;

        var promises = [];
        var subTasks = [];
        for (i = 0; i < length; ++i) {
            geometry = instances[i].geometry;
            instanceIds.push(instances[i].id);

            // 用于处理数据的线程名称
            // 需要进行数据处理的geometry对象
            subTasks.push({
                moduleName : geometry._workerName,
                geometry : geometry
            });
        }

        // 根据当前浏览器允许的最大线程数,创建N个createGeometry线程,方便后续通过Workers线程处理
        if (!defined(createGeometryTaskProcessors)) {
            createGeometryTaskProcessors = new Array(numberOfCreationWorkers);
            for (i = 0; i < numberOfCreationWorkers; i++) {
                createGeometryTaskProcessors[i] = new TaskProcessor(‘createGeometry‘, Number.POSITIVE_INFINITY);
            }
        }

        // 分摊任务,当前Primitive中可能需要对多个Geometry进行处理
        // 平坦任务,均分
        var subTask;
        subTasks = subdivideArray(subTasks, numberOfCreationWorkers);

        for (i = 0; i < subTasks.length; i++) {
            var packedLength = 0;
            var workerSubTasks = subTasks[i];
            var workerSubTasksLength = workerSubTasks.length;
            for (j = 0; j < workerSubTasksLength; ++j) {
                subTask = workerSubTasks[j];
                geometry = subTask.geometry;
                if (defined(geometry.constructor.pack)) {
                    subTask.offset = packedLength;
                    packedLength += defaultValue(geometry.constructor.packedLength, geometry.packedLength);
                }
            }

            var subTaskTransferableObjects;

            // 将Geometry中的参数化信息保存到arraybuffer中
            // 方便后续传入到线程
            if (packedLength > 0) {
                var array = new Float64Array(packedLength);
                subTaskTransferableObjects = [array.buffer];

                for (j = 0; j < workerSubTasksLength; ++j) {
                    subTask = workerSubTasks[j];
                    geometry = subTask.geometry;
                    if (defined(geometry.constructor.pack)) {
                        geometry.constructor.pack(geometry, array, subTask.offset);
                        subTask.geometry = array;
                    }
                }
            }

            // 调用线程,传入参数subTask,subTaskTransferableObjects中以引用方式,非复制
            promises.push(createGeometryTaskProcessors[i].scheduleTask({
                subTasks : subTasks[i]
            }, subTaskTransferableObjects));
        }

        // creating状态,线程中处理
        primitive._state = PrimitiveState.CREATING;

        when.all(promises, function(results) {
            // 成功后更新状态,已经创建成功,返回值results
            primitive._createGeometryResults = results;
            primitive._state = PrimitiveState.CREATED;
        }).otherwise(function(error) {
            setReady(primitive, frameState, PrimitiveState.FAILED, error);
        });
    } else if (primitive._state === PrimitiveState.CREATED) {
        // 如下,同上面的思路一致,通过combine线程,将多个geometry的返回值合并成一个vbo
        var transferableObjects = [];
        instances = (isArray(primitive.geometryInstances)) ? primitive.geometryInstances : [primitive.geometryInstances];

        var scene3DOnly = frameState.scene3DOnly;
        var projection = frameState.mapProjection;

        var promise = combineGeometryTaskProcessor.scheduleTask(PrimitivePipeline.packCombineGeometryParameters({
            createGeometryResults : primitive._createGeometryResults,
            instances : instances,
            ellipsoid : projection.ellipsoid,
            projection : projection,
            elementIndexUintSupported : frameState.context.elementIndexUint,
            scene3DOnly : scene3DOnly,
            vertexCacheOptimize : primitive.vertexCacheOptimize,
            compressVertices : primitive.compressVertices,
            modelMatrix : primitive.modelMatrix,
            createPickOffsets : primitive._createPickOffsets
        }, transferableObjects), transferableObjects);

        primitive._createGeometryResults = undefined;
        primitive._state = PrimitiveState.COMBINING;

        when(promise, function(packedResult) {
            var result = PrimitivePipeline.unpackCombineGeometryResults(packedResult);
            primitive._geometries = result.geometries;
            primitive._attributeLocations = result.attributeLocations;
            primitive.modelMatrix = Matrix4.clone(result.modelMatrix, primitive.modelMatrix);
            primitive._pickOffsets = result.pickOffsets;
            primitive._instanceBoundingSpheres = result.boundingSpheres;
            primitive._instanceBoundingSpheresCV = result.boundingSpheresCV;

            if (defined(primitive._geometries) && primitive._geometries.length > 0) {
                primitive._state = PrimitiveState.COMBINED;
            } else {
                setReady(primitive, frameState, PrimitiveState.FAILED, undefined);
            }
        }).otherwise(function(error) {
            setReady(primitive, frameState, PrimitiveState.FAILED, error);
        });
    }
}

如上是一段代码示意,从中可见,Create和Combine这类计算量比较大的操作,都是放在线程中进行的,避免阻塞主线程。这样,通过loadAsynchronous函数,将参数化的geometry转化为三角形,同时对同类的geometry合并成一个渲染批次,进而优化了渲染效率。可以说,这一块是Cesium对Geometry处理的核心。此时,状态已经更新为PrimitiveState.COMBINED。

备注:如果对Workers不太了解,可以参考之前写的《Cesium原理篇:4Web Workers剖析

create*

  • createVertexArray
    上面的Geometry已经将数据处理为indexBuffer和vertexBuffer,下面则需要将该数据结合attributes创建为vbo&vao,这个过程就是通过createVertexArray完成
  • createRS
    创建RenderState
  • createSP
    创建ShaderProgram

很明显,渲染主要是数据+风格,当我们满足了geometry的数据部分已经符合WebGL渲染的格式后,结合appearance封装的材质,设置对应的RenderState以及Shader和所需要的参数。最后,我们构造出最终的DrawCommand,添加到DrawCommandList中,完成最终的渲染。这块就不对细节展开了,涉及到Renderer模块的,在之前的Renderer系列都有详细介绍,这里主要介绍了大概的流程。

总结

Entity牵扯到的内容很多,从方便用户使用,到Geometry类型以及风格的多样性,到最终构造出DrawCommand,以及渲染Pass的优先级,里面牵扯的内容非常多,同时出于渲染性能的优化,还要打组批次,搞多线程,里面随便一个点都有很多值得学习,借鉴的地方。

自问如果要自己来做这一套Geometry渲染,首先多线程是必须要设计的,不然性能上负担不起,打组也是一个技术要点,但优先级不是最高。个人可能不会把Add,Updater以及Primitive分的这么细,材质上压根就想不出来该如何做。Cesium在设计上确实很优雅,但这在性能上多少也是有代价的。

终于将大概的过程写完了,总觉得欠了一些内容,有点力不从心。希望能把这个流程的大概介绍清楚,后面可以针对某一个局部细节可以细细钻研,学习里面的技巧,理解其中的设计原委。

时间: 2024-09-27 19:56:49

Cesium原理篇:7最长的一帧之Entity(下)的相关文章

Cesium原理篇:3最长的一帧之地形(2:高度图)

       这一篇,接着上一篇,内容集中在高度图方式构建地球网格的细节方面.        此时,Globe对每一个切片(GlobeSurfaceTile)创建对应的TileTerrain类,用来维护地形切片的相关逻辑:接着,在requestTileGeometry中,TileTerrain会请求对应该切片的地形数据.如果读者对这部分有疑问的话,可以阅读<Cesium原理篇:1最长的一帧之渲染调度>:最后,如果你是采用的高度图的地形服务,地形数据对应的是HeightmapTerrainDat

Cesium原理篇:3最长的一帧之地形(3:STK)

有了之前高度图的基础,再介绍STK的地形相对轻松一些.STK的地形是TIN三角网的,基于特征值,坦白说,相比STK而言,高度图属于淘汰技术,但高度图对数据的要求相对简单,而且支持实时构建网格,STK具有诸多好处,但确实有一个不足,计算量比较大,所以必须预先生成.当然,Cesium也提供了一个Online的免费服务,不过因为是国外服务器,所以性能和不稳定因素都不小.好的东西自然得来不易,所以不同的层次,根据具体的情况选择不同的方案,技术并不是唯一决定因素,甚至不是主要因素. CesiumTerra

Cesium原理篇:1最长的一帧之渲染调度

原计划开始着手地形系列,但发现如果想要从逻辑上彻底了解地形相关的细节,那还是需要了解Cesium的数据调度过程,这样才能更好的理解,因此,打算先整体介绍一下Cesium的渲染过程,然后在过渡到其中的两个主要模块:地形数据和影像数据. 简述 设想一下,印度洋的暖流,穿过喜马拉雅山,形成了滴一滴水,落在了青藏高原的唐古拉山,顺势而下,涌入太平洋,长江之水自此经久不息.而Cesium的一切的一切,也是从一个并不起眼的函数开始的: 通过requestAnimationFrame函数,每一帧的结束,就是下

Cesium原理篇:3最长的一帧之地形(4:重采样)

       地形部分的原理介绍的差不多了,但之前还有一个刻意忽略的地方,就是地形的重采样.通俗的讲,如果当前Tile没有地形数据的话,则会从他父类的地形数据中取它所对应的四分之一的地形数据.打个比方,当我们快速缩放影像的时候,下一级的影像还没来得及更新,所以会暂时把当前Level的影像数据放大显示, 一旦对应的影像数据下载到当前客户端后再更新成精细的数据.Cesium中对地形也采用了这样的思路.下面我们具体介绍其中的详细内容.        上图是一个大概流程,在创建Tile的时候(prepa

Cesium原理篇:6 Renderer模块(2: Texture)

Texture也是WebGL中重要的概念,使用起来也很简单.但有句话叫大道至简,如果真的想要用好纹理,里面的水其实也是很深的.下面我们来一探究竟. 下面是WebGL中创建一个纹理的最简过程: var canvas = document.getElementById("canvas"); var gl = canvas.getContext("webgl"); // 创建纹理句柄 var texture = gl.createTexture(); // 填充纹理内容

Cesium原理篇:6 Render模块(3: Shader)

在介绍Renderer的第一篇,我就提到WebGL1.0对应的是OpenGL ES2.0,也就是可编程渲染管线.之所以单独强调这一点,算是为本篇埋下一个伏笔.通过前两篇,我们介绍了VBO和Texture两个比较核心的WebGL概念.假设生产一辆汽车,VBO就相当于这个车的骨架,纹理相当这个车漆,但有了骨架和车漆还不够,还需要一台机器人来加工,最终才能成产出这辆汽车.而Shader模块就是负责这个生产的过程,加工参数(VBO,Texture),执行渲染任务. 这里假设大家对Shader有一个基本的

Cesium原理篇:6 Render模块(4: FBO)

Cesium不仅仅提供了FBO,也就是Framebuffer类,而且整个渲染过程都是在FBO中进行的.FBO,中文就是帧缓冲区,通常都属于高级用法,但其实,如果你了解了它的基本原理后,用起来还是很简单的,关键在于理解.比如你盖楼,地基没打好,盖第一层楼,还可以,盖第二层楼,有点挫了,盖第三层楼,塌了.你会认为第三层楼(FBO)太难了,其实根本原因还是出在地基上. 窗口系统所管理的帧缓存有自己的缓存对象(颜色,深度和模板),它们诞生于窗口创建前,而我们自己创建的帧缓冲,这些缓存对象则需要自己来手动

Cesium原理篇:3D Tiles(1)渲染调度【转】

Cesium在2016年3月份左右推出3D Tiles数据规范,在glTF基础上提供了LOD能力,定位就是Web环境下海量三维模型数据.虽然目前3D Tiles还是Beta阶段,有不少硬伤,但3D Tiles数据规范于2016年9月30日开始了OGC标准化进程,积极成分还是很大. 之前的glTF时分享了个人对二进制格式的一些想法和谨慎的态度.3D Tiles简单说就是具备LOD能力的glTF.有了数据首先是提供API可以渲染,保证用起来,下一步就要了解该数据规范的具体特点,比如倾斜,矢量,点云,

cesium原理篇(二)--网格划分【转】

转自:http://www.cnblogs.com/fuckgiser/p/5772077.html 上一篇我们从宏观上介绍了Cesium的渲染过程,本章延续上一章的内容,详细介绍一下Cesium网格划分的一些细节,包括如下几个方面: 流程 Tile四叉树的构建 LOD 流程 首先,通过上篇的类关系描述,我们可以看到,整个调度主要是update和endFrame两个函数中,前者分工,后者干活. 另外,QuadtreePrimitive类只要来维护整个地球的四叉树,而每一个Tile对应一个Quad