精华内容
下载资源
问答
  • 倾斜模型单体化的研究

    千次阅读 2019-03-13 10:52:11
    在这里,我们要和大家讲解倾斜摄影应用的两个重要话题——倾斜摄影自动化建模成果(后文简称“倾斜模型”)的数据组织和单体化。  数据组织:要LOD 更要便捷和安全  倾斜模型的一个突出的特点就是数据量庞大,这是因...

    倾斜摄影三维建模及应用是近年来测绘领域关注的热点,产业链上下游的企业为此都在积极探索,以推动该项技术的健康发展和落地应用。然而,什么样的技术才是真正符合用户实际应用需求的?在这里,我们要和大家讲解倾斜摄影应用的两个重要话题——倾斜摄影自动化建模成果(后文简称“倾斜模型”)的数据组织和单体化。
      数据组织:要LOD 更要便捷和安全
      倾斜模型的一个突出的特点就是数据量庞大,这是因其技术机制、高精度、对地表全覆盖的真实影像所决定的。那么,如何承载海量的倾斜模型数据、保证快速加载和流畅渲染?这是倾斜摄影技术顺利实现应用的第一大“拦路虎”,必须拿下。是直接读取OSGB格式的倾斜摄影模型海量数据,还是需要导入和处理数据格式后读取?这是一个关键问题。
      解决这个问题之前,我们先明确下LOD(Level of Detail)这个概念。LOD是GIS平台提高性能的一个重要法宝,即对同一个数据从清晰到模糊有多份。当屏幕视角距离某个地物近时,软件自动调用最清晰层的数据;当屏幕视角远离该地物时,则自动切换为模糊层的数据。由于人眼本来就无法看清远处的数据,因此这样做并不影响视觉效果。例如影像金字塔、地图分比例尺切图等,都采用此方式。
      对于手工建模的模型,一般是通过三维GIS平台自行计算出多层LOD,并处理其远近距离的切换关系。而对于倾斜模型,由于其技术原理是先计算稠密点云、经过简化后再构建TIN,因此在数据生产的过程中,就能通过不同的简化比例来得到数据LOD,而不再需要GIS平台来进行计算。并且数据生产过程中计算出来的LOD效果也是最佳的。也正因为如此,无论是街景工厂还是Smart3D,其生产出来的倾斜模型都是自带多级LOD的,一般至少带有5-6层,多则10层以上。
      数据本身自带LOD,从技术原理上就决定了其看似庞大,其实完全可以做到非常高的调度和渲染性能(只要不破换原始自带的LOD)。这也是为什么我们使用数据厂家自带的Viewer就可以获得很好的加载和浏览性能。
      分析到这里,相信大家就会明白:采用直接读取倾斜模型原始OSGB格式相比采用导入的方式更好。为什么这么说呢?一方面,导入过程费时费力,处理海量倾斜模型数据往往需要数周。更重要的是,在数据导入过程中,为了和平台内部格式保持一致,往往会破换原始数据的LOD,自行再构建LOD。这样构建的LOD,无论是效果还是性能,都远逊于原始LOD,导致性能不佳。
      此外,在格式选择上,倾斜摄影建模软件有内部私有格式(如Smart3D的s3c),另外还可输出为OSGB和DAE两种外部格式。其中,DAE格式由于是文本格式,直接读取效率太低,因此被排除在外。而OSGB格式是开源的OSG库所自带的二进制格式,直接读取效率高,且格式公开,有免费的开源库可直接使用,并且适合作为后续网络发布与三维服务共享的模型传输格式,因此成为最佳选择。
      那么,有人可能会问:OSGB作为公开格式,若直接使用,如何解决网络发布后的数据安全问题?对于这个问题,其实并不难解决:在服务端,我们虽然是直接读取OSGB文件,但在网络发布缓存到客户端的模型,已经是通过服务端加密,并在客户端压缩打包为一个内部的大文件,如下图所示。因此,直接读取OSGB格式绝对不存在数据泄露等安全问题。

    图 1服务端的OSGB文件

    图 2客户端缓存后的大文件
      解答完上述问题,可能还有人会问:一大堆OSGB的小文件,拷贝和管理都很麻烦。如何解决?针对这个问题,超图软件正在研发采用分布式数据库来存储和管理osgb文件,便于海量OSGB文件的存储、拷贝、管理和服务发布。
      值得注意的是,即便我们采用数据库来进行存储和管理,也是直接把每个OSGB小文件放到数据库中,而不是导入OSGB格式——我们坚决不破破坏数据原有的LOD,也坚决捍卫OSGB格式的开放性。彼时,数据的安全性则可以通过数据库本身的安全体系来予以保证。
      单体化:切割还是矢量化?
      提到单体化,可能很多人说起来都是眼泪。基于倾斜影像,若做人工干预的建模,自然就不存在单体化的问题,生成的模型本来就是单体的。而自动化建模的成果,由于其生成机理,得到的模型是一个连续的TIN加贴图,并没有根据建筑物划分为一个个可以单独选中的对象。而我们都知道,在GIS管理和应用中,若倾斜模型不能进行对象的单独选中和查询,就只能和影像一样作为底图浏览,无法进一步深入应用。单体化成为第二大“拦路虎”,我们必须闯关成功。
      如何实现单体化才是最好的?
      很多技术人员脑海中冒出的第一个直观的想法一定是:对倾斜模型进行切割呀,这样切割之后的模型就和人工建模成果一个样了。
      不过很遗憾地说,这种想法貌似很完美,但其实是一条“死胡同”。只要切割一下数据,做一个小小的实验,就会一目了然了。当看到效果的一刹那,大家一定会心凉半截。

    图 3 倾斜模型切割后的边缘效果
      如上图,这样的边缘效果肯定不是用户所想要的。更重要的是,切割之后,两个主要后续用途都没法实现:
      一是替换人工精细建模的模型。看到这样的锯齿边缘,真不知道人工建模的人员如何才能把锯齿边缘接上去。难以想象建模人员的抓狂。
      第二个用途是隐藏某种类型的地物。这样露出一个锯齿状的空洞,你说评审专家是让项目不过啊,不过啊还是不过啊?
      为什么会有锯齿呢?是切割的技术不够高?完全不是。根源在于倾斜模型数据本身其实就是三角面片加纹理,切割算法无非是根据建筑底面来决定哪些三角面片保留,哪些三角面片被抛弃;边缘的锯齿其实也就是留下来靠边的三角面片而已。说到底是倾斜模型自动化生成算法所决定了的,和GIS平台真没什么关系。
      第二个问题是,这样的切割后,也抛弃了数据自带LOD的优点,导致GIS平台只能按照普通模型的方法来构建LOD。倾斜模型数据量庞大,想想这性能表现也是醉了。
      切割之后存在的第三个问题是:切割必须事先输入对应地物的矢量底面数据。经过漫长的等待,切割出来之后,若发生任何一点变动,对不起,数据还得再返工切一遍。因此,其灵活性几乎为零。
      现在,让我们换一个思路来看待倾斜模型:它事实上就是带有TIN作为高程背景的影像。在二维GIS中,有谁见过根据矢量底面来把影像数据切割为建筑物影像图层、道路影像图层、绿植影像图层的吗?正确的思路是:我们在影像数据上进行矢量化,从而可以在影像+矢量的图层上选中建筑物、道路等地物。若一定需要把影像上的某种类型地物隐藏,也是通过叠加这种地物的矢量图进行显示过滤。
      把GIS的科学原理搞清楚了,我们回过头来再看待如何进行倾斜模型的单体化,就胸中有丘壑了。只要试一下就知道,通过叠加的矢量底面,在渲染层面实现单体化,效果ganggang的:被选中的地物像是被高透膜紧密包裹,底部边缘笔直。

    图 4 矢量化后的倾斜模型单体化边缘效果
      采用矢量化的方式,在保证效果、不破坏原始数据及LOD的同时,最大的好处还在于它打通了基于三维的倾斜模型和基于二维的矢量面之间的关键“关卡”,实现三维和二维GIS的完美一体化。基于此,GIS的一系列功能(如图查属性、属性插图、缓存区分析与查询、专题图制作等等)都可轻松实现。至此,对于倾斜模型的应用,用户可以不用再纠结于选择哪个GIS平台,而是要充分发挥应用的广阔想象力。
      它带来的价值还体现在动态性和灵活性的大大提升。由于叠加的矢量面只需要一个简单的图层设置就可以起作用,这也就意味着:在应用过程中,可以随时更换需要叠加的矢量面。当用户增加数据类型后,不用再大费周章的把数据重新切割一遍,而是点两下鼠标,就可快速搞定。想一想,这是多么惬意的一件事。
      当然,当前支撑倾斜摄影应用的GIS技术还不是很完美,想用户之所想,实现基于倾斜模型的更多能力、追求更优化的性能和效果,永远是我们GIS人共同的追求。在这个技术变革“加速度”的时代,快速响应、快速迭代、快速研发成为关键。

    展开全文
  • 在上一篇《如何在Web上直接浏览大规模OSGB格式倾斜模型(二):转换OSGB》中,我们已经实现单个osgb格式模型的转换和显示,本篇我们来实现PagedLOD,加载大规模的模型。 在开始之前,先来感受一下我们商业版的性能...

    图片

    在上一篇《如何在Web上直接浏览大规模OSGB格式倾斜模型(二):转换OSGB》中,我们已经实现单个osgb格式模型的转换和显示,本篇我们来实现PagedLOD,加载大规模的模型。

    在开始之前,先来感受一下我们商业版的性能优势:

    解析性能对比:开源版vs商业版

    osgb文件大小开源版耗时商业版耗时
    435 kb4.2451171875 ms0.2541503906 ms
    0.60 mb6.1220703125 ms0.5048828125 ms
    1.19 mb13.790283203 ms0.5991210937 ms
    1.57 mb20.443847656 ms0.8320312500 ms
    2.64 mb33.323974609 ms1.1899414062 ms

    两个字:快,稳!
    欢迎咨询,后台回复【商务合作】,获取联系方式!

     欢迎关注微信公众号【三维网格3D】,第一时间获取最新文章

    正文开始了,代码量比较大,3.1.1节之后内容,推荐在电脑上阅读。

    1、认识PagedLOD

    简单说,PagedLOD可以从PagedLOD两方面来认识。

    1.1、LOD

    首先认识LOD,这是一种技术,英文全称是Levels of Detail,意为多细节层次,《地理信息系统名词》第二版,也称为细节层次模型,并给出定义:通过建立原始精细模型的多个近似简化模型,表示原始模型不同程度的细节。三维渲染引擎,根据视点与模型的距离,加载不同简化程度(细节层次)的模型,达到提升渲染效率和浏览体验等目的。

    1.2、Paged

    然后认识Paged,这可以理解“分页的”,强调LOD的组织方式,即各个层次的模型的存储和检索,模型文件之间的链接关系。

    1.3、PagedLOD

    本质上,PagedLOD是一颗调度树,可以是四叉树,也可以是八叉树。

    如基于数字高程的地形图层则可以用四叉树,将一定空间范围的包围盒,按一分为四的方式,逐级细分,这个过程中,可以理解为细分的包围盒高度上始终不变。

    而我们研究的倾斜摄影模型数据集,本质上是一个八叉树集合,但并不是严格的八叉树,生成过程大概可以这样理解:

    • (1)先将包含数据集内所有点的包围盒,等分为 x * y个小包围盒,得到x * y颗八叉树的根节点,这里的xy就是我们后面要用的行列号,对每一个根节点进行(2)至(7)步操作;

    • (2)用根节点包围盒去裁剪原始模型,得到根节点原始模型;

    • (3)对根节点原始模型,进行简化,得到根节点模型;

    • (4)将根节点细分(节点包围盒一分为四或者一分为八)得到子节点,或者仅对原始模型进行简化(节点包围盒不变),这一级的模型比根节点更清晰;

    • (5)对第(4)步中生成的子节点进行第(6)至(7)步操作,直到模型的细节(清晰度)与原始模型一致。

    • (6)用子节点包围盒去裁剪根节点原始模型,简化,得到子节点模型;

    • (7)将子节点细分,或者仅简化节点模型,得到孙节点;

    图片

    图片

    2、OSGB倾斜模型文件结构

    在开始实现之前,我们结合解析结果来加深对PagedLOD的认识和理解。一个倾斜模型osgb文件,可能有两种情况:

    • (1)只包含一个LOD节点,解析结果根节点类型就是PagedLOD,这种情况对应根节点或者第1.3节(4)、(7)步中节点包围盒不变的情况;

    • (2)包含多个LOD节点,对应第1.3节(4)、(7)节点细分的情况。

    2.1、单个PagedLOD

    图片

    2.2、多个PagedLOD

    图片

    2.3、LOD参数

    • CenterRadius 节点包围球,Center为中心,Radius为半径,可用于视锥裁剪过滤,计算决定是否加载下一细节层次的参数range

    • FileNames 文件名,第二个为下一层次模型文件名。

    • RangeList 第一个为当前层次可见的范围,即当range大于范围最大值时,加载下一个细节层次的模型。

    • RangeMode 指示range参数计算模式。

      • 0 表示rangeRangeList中的值是视点到节点中心的距离;

      • 1 表示rangeRangeList中的值是包围球投影后占屏幕的像素(只是一个估算值)。

    3、实现PagedLOD

    关键步骤:

    • (1)从根节点开始,遍历调度树;

    • (2)检查是否有下一层次,如有则继续,否则显示当前层次模型并结束遍历;

    • (3)计算range, 包围球投影,估算占据屏幕像素数;包围球中心到视点的距离;

    • (4)如果range大于RangeList[0].max,检查下一层次是否已经加载;

    • (5)如果未加载,则开始请求下一层次模型文件,并显示当前层次模型,结束遍历;

    • (6)如果已经加载,则从下一层次开始遍历,重复(2)至(6);

    渲染流程:

    • 1.遍历子节点:按其状态分别加入可渲染队列、待加载队列、待转换队列;

    • 2.处理可渲染节点:将节点内容包含的3d对象加入渲染队列;

    • 3.对待加载列表和待转换列表进行排序;

    • 4.处理待转换节点:转换osgb;

    • 5.处理待加载节点:加载、解析osgb;

    • 6.内存管理:当估算的内存占用超过阈值时,卸载过期瓦片。

    实现效果(three.js版):

    ,时长00:23

    3.1、接口定义

    • LOD类 封装基本的计算功能,包括包围球投影,距离,视锥裁剪等。

    • PagedLOD类 封装当前层次的模型创建、显示等功能,并根据RangeListFileNames创建下一层次节点(类型为PagedLODSet)。

    • PagedLODSet类 封装osgb文件读取、解析、创建当前层次的节点(类型为PagedLOD),渲染,射线查询,创建并导出索引等功能。

    • PagedLODQueue类 封装排队功能,包含入队,出队,排序等。

    • PagedLODContent类 负责转换osgb,创建并提供节点模型3D对象。

    3.1.1、PagedLODSet类

    构造函数参数解释:

    • url 数据集路径,可以是:

      • metadata.xml路径

      • 索引文件index.json路径

      • metadata.xml或者index.json所在文件夹路径,优先尝试加载index.json,不存在则自动加载metadata.xml

      • osgb模型文件,单个模型LOD

    • lodScale LOD缩放倍数,同一视野下,倍数越大,显示的瓦片分辨率越低,同时渲染的瓦片越少

    • maxMemeory 当估算的内存占用(单位为byte)超过该值时,卸载过期瓦片。默认为 1024 * 1024 * 500

    • maxRequest 最大并发请求数量,如果当前请求数量超过该值,则当前帧不发出新的请求。默认为 4

    • maxParsing 最大转换任务数量,如果当前未完成转换的任务数量大于等于该值,则当前帧不启动新的转换任务.默认为 1

    • domains 负载均衡数据服务节点列表。url中节点模板标记为{t},即url形如:http://{t}.ds.mesh-3d.com

    • pendingSortFunc 自定义排队函数,用于对待请求和待转换瓦片队列的排序。对排队结果的应用:越靠前越早被处理(请求、解析数据或转换osgb)

    • onInitProgress 初始化进度回调函数,仅数据集没有创建索引文件index.json时有用,因为此情况下需要按给定的行列号范围,逐个尝试猜测的根节点文件是否存在,耗时比较长

    • grid 入口文件为 metadata.xml 时,需指定行列号范围(minX/Y,maxX/Y),数据目录相对路径等信息

      • minX 最小列号,默认为 0

      • maxX 最大列号,默认为 8

      • minY 最小行号,默认为 0

      • maxY 最大行号,默认为 8

      • dataDir 数据目录相对路径,相对于metadata.xml或者index.json所在文件夹,默认为'/Data'

      • tilePattern 瓦片文件名(不包含后缀)模板,默认为'Tile_{x}_{y}'

      • ext 瓦片文件后缀,默认为'osgb'

    • proxy 代理,可选
      debugVolume true则创建并显示调试线框(瓦片包围盒)
      debugVolumeOnly true则仅显示调试线框

    export class PagedLODSet extends LOD { 
        constructor(options: {
            url: string
            manager?: THREE.LoadingManager 
            lodScale?: number 
            maxMemeory?: number 
            maxRequest?: number 
            maxParsing?: number
     
            grid?: { 
                minX: number 
                maxX: number 
                minY: number 
                maxY: number 
                dataDir?:string 
                tilePattern?:string 
                ext?:string
            }
          
            domains?: string[]
            visible?: boolean
            proxy?: { getUrl: (url: string) => string }
            
            pendingSortFunc?: (a: PagedLOD | PagedLODSet, b: PagedLOD | PagedLODSet) => number 
            onInitProgress?: (progress: number) => void
     
            debugVolume?: boolean
            debugVolumeOnly?: boolean
        })
    
        readonly domains?: string[]
        readonly ready: boolean
        readonly readyPromise: Promise<this>
        readonly children: (PagedLOD | PagedLODSet)[]
        readonly url: string
        readonly path: string
        readonly metadata: {
            srs: string,
            enuOrigin: number[]
            srsOrigin: number[]
            upAxis: 'y' | 'z'
            [key: string]: any
        }
        readonly manager: THREE.LoadingManager
        readonly memeory: number
        readonly queue: PagedLODQueue
    
        visible: boolean
        transform: THREE.Matrix4 
        lodScale: number 
        maxMemeory: number 
        maxRequest: number 
        maxParsing: number
    
        grid: {
            minX: number
            maxX: number
            minY: number
            maxY: number
        }
    
        debugVolume: boolean
        debugVolumeOnly: boolean
     
        pendingSortFunc?: (a: PagedLOD | PagedLODSet, b: PagedLOD | PagedLODSet) => number 
        onInitProgress?: (progress: number) => void
    
        /**
         * 请求文件数据,解析,创建子节点。注意:这里尚未进行转换,并不能在完成后获取3d对象
         * @returns {Promise<this>}
         */
        load(): Promise<this>
    
        /**
         * 遍历LOD树,按节点状态加入不同的队列
         * @param {{
         *     camera:THREE.PerspectiveCamera 
         *     renderer:THREE.WebGLRenderer
         *     frustum:THREE.Frustum,
         *     frameNumber: number
         * }} frameState 
         * @param {PagedLODQueue} queue 
         * @returns {boolean}
         */
        traverse(
            frameState: {
                camera: THREE.PerspectiveCamera
                renderer: THREE.WebGLRenderer
                frustum: THREE.Frustum,
                frameNumber: number
            },
            queue: PagedLODQueue
        ): boolean
    
    
        /**
         * 渲染程序入口,每一帧渲染前调用,通知内部遍历获取可渲染节点的3d对象,加入渲染队列`renderList`。调用者需要自行创建、传递、处理渲染队列`renderList`,完成最终的渲染。
         * @param {{
         *     camera:THREE.PerspectiveCamera 
         *     renderer:THREE.WebGLRenderer
         *     frustum:THREE.Frustum,
         *     frameNumber: number
         * }} frameState 
         * @param {THREE.Object3D[]} renderList  
         */
        onRender(
            frameState: {
                camera: THREE.PerspectiveCamera
                renderer: THREE.WebGLRenderer
                frustum: THREE.Frustum,
                frameNumber: number
            },
            renderList: THREE.Object3D[]
        ): void
    
        /**
         * 射线查询,可用于交互拾取对象
         * @param {THREE.Raycaster} raycaster 
         * @param {THREE.Intersection[]} [intersects] 
         */
        raycast(raycaster: THREE.Raycaster, intersects?: THREE.Intersection[]): THREE.Intersection[]
    
        /**
         * 导出索引
         * @returns {object}
         */
        toJSON(): object
    
        /**
         * 卸载所有瓦片,释放资源
         */
        destroy(): void
    
    }
    

    3.1.2、LOD类

    export class LOD {
    
        constructor(options: {
            parent?: LOD
            tileset?: LOD
            boundingSphere?: THREE.Sphere
            boundingBox?: THREE.Box3
            rangeMode?: 0 | 1
        })
        
        readonly range: number
        readonly children: LOD[]
        
        readonly parent?: LOD
        readonly tileset?: LOD
        readonly boundingBox?: THREE.Box3
        readonly boundingSphere?: THREE.Sphere
        readonly distanceToCamera: number
        readonly visibility: number
        readonly isDestroyed: boolean
    
        isExpired: boolean
    
        computeVisibility(frameState: {
            camera: THREE.PerspectiveCamera
            renderer: THREE.WebGLRenderer
            frustum: THREE.Frustum,
            frameNumber: number
        }): void
    
        traverse(frameState: {
            camera: THREE.PerspectiveCamera
            renderer: THREE.WebGLRenderer
            frustum: THREE.Frustum,
            frameNumber: number
        }): void
    
        destroy(): void
    
    }
    

    3.1.3、PagedLOD类

    export class PagedLOD extends LOD {
        constructor(options: {
            content: Object
            url: string
            parent?: OSGLikeLOD
            tileset?: OSGLikeLOD
            boundingSphere?: THREE.Sphere
            boundingBox?: THREE.Box3
            rangeMode?: 0 | 1
        })
    
        readonly url: string
        readonly path: string 
        readonly level: number
    
        readonly rangeDataList: string[]
        readonly rangeList: { min: number, max: number }[] 
        readonly maxRange: number
        readonly rangeMode: 0 | 1
    
        readonly userCenter: {
            center: number[]
            radius: number
        }
    
        readonly content: PagedLODContent
        readonly children: PagedLODSet[]
    
        /**
         * 
         * @param {Osg.PagedLOD|Osg.Geometry|Osg.Geode} content 
         */
        init(content: object): void
    
        /**
         * 
         * @param {{
         *     camera:THREE.PerspectiveCamera 
         *     renderer:THREE.WebGLRenderer
         *     frustum:THREE.Frustum,
         *     frameNumber: number
         * }} frameState 
         * @param {PagedLODQueue} queue 
         * @returns {boolean}
         */
        traverse(
            frameState: {
                camera: THREE.PerspectiveCamera
                renderer: THREE.WebGLRenderer
                frustum: THREE.Frustum,
                frameNumber: number
            },
            queue: PagedLODQueue
        ): boolean
    
        unload(): void
    
        destroy(): void
    }
    

    3.1.4、PagedLODContent类

    /**
     * 负责LOD节点模型转换和渲染
     */
    export class PagedLODContent {
    
        constructor(options: {
            osgObject: osg.PagedLOD | osg.Geode | Osg.Geometry | osg.Group
            manager: THREE.LoadingManager
            debugVolume?: boolean
            debugVolumeOnly?: boolean
        })
        model: THREE.Object3D
        debugVolume: boolean
        debugVolumeOnly: boolean
        memeory: number
        transform: THREE.Matrix4
        readonly isDestroyed: boolean
    
        /**
         * 
         * @param {{
         *     camera:THREE.PerspectiveCamera 
         *     renderer:THREE.WebGLRenderer
         *     frustum:THREE.Frustum,
         *     frameNumber: number
         * }} frameState 
         * @param {THREE.Object3D[]} renderList  
         */
        onRender(
            frameState: {
                camera: THREE.PerspectiveCamera
                renderer: THREE.WebGLRenderer
                frustum: THREE.Frustum,
                frameNumber: number
            },
            renderList: THREE.Object3D[]
        ): void
    
        parse(): Promise<this>
    
        destroy(): void
    }
    
    

    下面贴出关键代码,都是从可运行的源码中直接复制过来的,可以说比较详尽了,希望对你动手实现有帮助。

    3.2、包围球投影

    var _vector3=new THREE.Vector3()
    /**
     *  from http://www.iquilezles.org/www/articles/sphereproj/sphereproj.htm
     *  Sample code at http://www.shadertoy.com/view/XdBGzd?
     * @param {THREE.Sphere} sph 
     * @param {THREE.Matrix4} camMatrix 
     * @param {number} fle 
     * @returns 
     */
    function projectBoundingSphere(sph, camMatrix, fle) {
        var o = _vector3.copy(sph.center).applyMatrix4(camMatrix)
        var r = sph.radius
        var r2 = r * r
        var z2 = o.z * o.z;
        var l2 = o.dot(o);
        var area =
            -Math.PI *
            fle *
            fle *
            r2 *
            Math.sqrt(Math.abs((l2 - r2) / (r2 - z2))) /
            (r2 - z2);
        return area;
    }
    

    3.3、LOD计算range

      computeVisibility(frameState) {
            const { camera, frustum } = frameState;
            const boundingSphere = this.boundingSphere;
            if (boundingSphere) {
                const visible = frustum.intersectsSphere(boundingSphere)
                this.visibility = visible ? 1 : 0;
                var distanceToCamera = boundingSphere.distanceToPoint(camera.position)
                if (distanceToCamera < 0) {//相机位置在包围球里面
                    distanceToCamera = 0;
                }
                this.distanceToCamera = distanceToCamera;
            } else {
                this.visibility = 1;
                this.distanceToCamera = 0
            }
        }
    
        traverse(frameState) {
            const { camera, renderer } = frameState;
            const { tileset, boundingSphere, rangeMode } = this;
            const { lodScale, transform } = tileset
            const { Matrix4, Vector3, Vector4 } = THREE
    
            if (!_vector3) _vector3 = new Vector3()
            if (!_viewport) _viewport = new Vector4()
            if (!_matrix4) _matrix4 = new Matrix4();
    
            this.computeVisibility(frameState);
    
            // Calculate distance from viewpoint 
            if (rangeMode == 0) {
                this.range = this.distanceToCamera * lodScale
            }
            else if (this.visibility) {
                // Calculate pixels on screen
                var modelMatrix = transform;
                var viewMatrix = camera.matrixWorldInverse
                var projmatrix = camera.projectionMatrix
                var modelViewMatrix = _matrix4.multiplyMatrices(modelMatrix, viewMatrix)
                var requiredRange = projectBoundingSphere(boundingSphere, modelViewMatrix, projmatrix.elements[0])
                // Get the real area value and apply LODScale
                var viewport = renderer.getViewport(_viewport)
                requiredRange = requiredRange *
                    viewport.width *
                    // viewport.width *
                    viewport.height *
                    0.25 /
                    lodScale;
    
                this.range = requiredRange;
            }
    
        }
    

    3.4、PagedLOD初始化

    init(content) {
        const { rangeDataList, rangeList, userCenter, path, tileset } = this;
        const { manager, debugVolume, debugVolumeOnly, transform } = tileset;
        const { Euler, Quaternion, Sphere } = THREE;
    
        if (rangeDataList && rangeDataList.length > 0
            && !this.children.length//过期节点重新加载时,不创建子节点
        ) {
      
            if (!_zUp2YupQuat) {
                const zUp2YupEuler = new Euler(-Math.PI / 2)
                _zUp2YupQuat = new Quaternion().setFromEuler(zUp2YupEuler)
            }
    
            var boundingSphere = this.boundingSphere || new Sphere();
            boundingSphere.center.fromArray(userCenter.center)
            boundingSphere.radius = userCenter.radius
            boundingSphere.center.applyQuaternion(_zUp2YupQuat)
            this.boundingSphere = boundingSphere
    
            this.maxRange = rangeList[0].max
    
            for (let i = 1; i < rangeDataList.length; i++) {
                const rangeData = rangeDataList[i];
                var childLODSet = new PagedLODSet({
                    parent: this,
                    url: path + '/' + rangeData,
                    tileset,
                    level: this.level + 1
                })
                this.children.push(childLODSet)
            }
        }
    
        this.content = new PagedLODContent({
            osgObject: content,
            manager,
            transform,
            debugVolume,
            debugVolumeOnly
        });
        if (debugVolume) {
            this.boundingBox = this.content.boundingBox
        }
    
    }
    

    3.4、PagedLOD遍历

    traverse(frameState, queue) {
    
        const { rangeDataList, content, maxRange } = this;
        /**
         * 1.视锥裁剪过滤
         * 2.根据相机、包围球以及范围计算模式,估算LOD范围(range)
         * 注意:叶子节点没有包围球,无需进行裁剪过滤
         */
        if (this.boundingSphere) {
            super.traverse(frameState)
            if (!this.visibility) {
                return true;
            }
        }
    
        //记录最新访问的帧数,用于判断是否过期(可卸载)
        this.frameNumber = frameState.frameNumber;
    
        //根据LOD范围(range),遍历下一细节层级
        if (rangeDataList && rangeDataList.length > 1) {
    
            let requiredRange = this.range;
            if (requiredRange < 0) {
                requiredRange = maxRange
            }
    
            if (requiredRange >= maxRange) {
                var child = this.children[0]
                var ready = child.traverse(frameState, queue);
                if (ready) {
                    return true
                }
            }
        }
    
        //将当前节点加入渲染队列(queue.rendering)或者待转换队列(queue.parsing)。
        //注意:不处理过期节点
        if (!this.isExpired) {
            if (content && content.ready) {
                queue.pushRendering(this)
                return true
            } else {
                queue.pushParsing(this)
            }
        }
    
    }
    

    3.5、PagedLODSet初始化

    创建或者更新子节点

    function initLODGroup(lodset, content) {
        const url = lodset.url;
        var osgPagedLODs = content.Children
    
        if (content.Type == 'osg::PagedLOD') {
            osgPagedLODs = [content]
        } else if (!content.Children) {
            lodset.isEmpty = true;
            return;
        }
    
        if (lodset.children.length) {//卸载过的瓦片,重新加载时不需要创建新子节点
    
            if(osgPagedLODs.length>1){
                console.log(content);
            }
            lodset.isExpired = false
            for (let i = 0; i < osgPagedLODs.length; i++) {
                const osgPagedLOD = osgPagedLODs[i];
                var childLOD = lodset.children[i]
                childLOD.init(osgPagedLOD)
                childLOD.isExpired = false
            }
    
        } else {//瓦片首次加载,创建子节点
    
            for (let i = 0; i < osgPagedLODs.length; i++) {
                const osgPagedLOD = osgPagedLODs[i];
                var childLOD, childUri = [url, i].join('.');
    
                childLOD = new PagedLOD({
                    parent: lodset,
                    url: childUri,
                    content: osgPagedLOD,
                    tileset: lodset.tileset || lodset,
                    level: lodset.level + 1
                })
    
                lodset.children.push(childLOD)
            }
        }
    
    }
    
    

    3.6、PagedLODSet遍历

    traverse(frameState, queue) {
        const { frustum } = frameState;
        const { isLoading, level } = this;
    
        //计算瓦片可见性,以及瓦片到相机位置的距离
        this.computeVisibility(frameState)
        if (level == 0 && !this.visibility) {
            return true
        }
    
        //记录最新访问的帧数,用于判断是否过期(可卸载)
        this.frameNumber = frameState.frameNumber;
    
        if (isLoading) {
            return false;
        }
    
        if (!this.loaded) {//加载当前节点
            queue.pushLoading(this);
            return false;
    
        } else if (this.isExpired) {  //判断是否需要重新加载过期的节点
            var needReload = false
            for (const lod of this.children) {
                if (lod.boundingSphere) {
                    needReload = frustum.intersectsSphere(lod.boundingSphere)
                } else {
                    needReload = true
                }
                if (needReload) {
                    break;
                }
            }
    
            if (needReload) {
                for (const lod of this.children) {
                    lod.frameNumber = this.frameNumber
                }
                queue.pushLoading(this);
                return false;
            } else {
                return true
            }
    
        } else {//遍历子节点
    
            var childrenLoaded = true
            const children = this.children;
            for (let i = 0, l = children.length; i < l; i++) {
                const childLOD = children[i];
                var childReady = childLOD.traverse(frameState, queue)
                if (!childReady) {
                    childrenLoaded = false
                    //注意:这里不要break,break则部分子节点没有检查状态就被丢弃了,永远访问不了下一层级
                }
            }
    
            return childrenLoaded
        }
    
    }
    

    3.7、PagedLODSet渲染程序入口

    onRender(frameState, renderList) {
        if (this.isDestroyed || !this.visible) return;
    
        /**
         * @type {PagedLODQueue}
         */
        const queue = this.queue;
        queue.init(frameState.frameNumber)
    
        //1.遍历子节点,按其状态分别加入可渲染队列、待加载队列、待转换队列
        this.traverse(frameState, queue);
    
        //2.处理可渲染节点:将节点内容包含的3d对象加入渲染队列
        for (const lodRendering of queue.rendering) {
            if (lodRendering.content) {
                lodRendering.content.onRender(frameState, renderList)
            }
        }
    
        //3.对待加载列表和待转换列表进行排序
        queue.sortPending(this.pendingSortFunc)
    
        //4.处理待转换节点:转换osgb 
        while (queue.numParsing
            && this._numParsing < this.maxParsing
        ) {
            var lodParsing = queue.shiftParsing()
            lodParsing.content.parse().then(() => {
                if (!lodParsing.isExpired) {
                    this.memeory += lodParsing.content.memeory;
                }
                this._numParsing--
            }).catch(err => {
                this._numParsing--
            })
            this._numParsing++
        }
    
        //5.处理待加载节点:加载、解析osgb  
        while (queue.numLoading
            && this._numLoading < this.maxRequest
        ) {
            var lodLoading = queue.shiftLoading()
            lodLoading.load().then(() => {
                this._numLoading--
            }).catch(err => {
                this._numLoading--
            })
            this._numLoading++
        }
    
        //6.内存管理:当估算的内存占用超过阈值时,卸载过期瓦片
        if (this.memeory >= this.maxMemeory) {
    
            var expiredList = queue.getExpiredList();
            var minMemeory = this.maxMemeory * 0.25;
    
            while (expiredList.length && this.memeory >= minMemeory) {
                var expiredLodset = expiredList.shift()
                var expiredLods = expiredLodset.children
                expiredLodset.isExpired = true
    
                for (let i = 0, l = expiredLods.length; i < l; i++) {
                    const expiredLod = expiredLods[i];
                    if (expiredLod.content) {
                        var mem = expiredLod.content.memeory
                        expiredLod.unload()
                        expiredLod.isExpired = true
                        this.memeory -= mem
                    }
                }
            }
        }
    
    }
    

    至此,《如何在Web上直接浏览大规模OSGB格式倾斜模型》系列完结。

    展开全文
  • Cesium倾斜模型单体化

    千次阅读 2020-10-31 14:19:59
    Cesium倾斜模型单体化 前言 目前Cesium三维项目很多是使用倾斜摄影模型,但是倾斜模型只是一张好看的皮,不能进行交互操作,所以需要后期实现单体化功能,单体化有很多种,比如楼栋单体化、分层单体化、更细的有分户...

    Cesium倾斜模型单体化

    前言

    目前Cesium三维项目很多是使用倾斜摄影模型,但是倾斜模型只是一张好看的皮,不能进行交互操作,所以需要后期实现单体化功能,单体化有很多种,比如楼栋单体化、分层单体化、更细的有分户单体化。

    实现效果

    在这里插入图片描述

    实现思路

    倾斜模型单体化需要数据结合代码来实现,数据生产需要采集每层的面坐标串等信息。有了数据后在Cesium中通过Entity的方式渲染出来。

    关键代码

     //处理查询结果
        handleQueryResult(result) {
            //清除上一次结果
            this.clearQueryResult();
            //如果查询成功 那么返回的结果应该是一个geojson对象 类型为FeatureCollection
            let feature = result.features[0]; //取第一个要素
            if (!feature) return;
            let geometry = feature.geometry; //取要素的几何对象
            let properties = feature.properties; //取要素的属性信息
            let coordinates;
            let pointArr = [];
            if (geometry.type == "MultiPolygon") { //多面 房屋面一般不会出现空洞等现象 如果有需要另做处理
                coordinates = geometry.coordinates[0][0];
            } else if (geometry.type == "Polygon") {
                coordinates = geometry.coordinates[0];
            }
    
            for (let i = 0; i < coordinates.length; i++) {
                const element = coordinates[i];
                pointArr.push(element[0]);
                pointArr.push(element[1]);
                pointArr.push(0);
            }
            this.addClampFeature(pointArr);
            this.showBuildInfo(properties)
        },
    
        //添加贴地对象
        addClampFeature(pointArr) {
            this.clampFeature = this.viewer.entities.add({
                polygon: {
                    hierarchy: new Cesium.PolygonHierarchy(Cesium.Cartesian3.fromDegreesArrayHeights(pointArr)),
                    classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
                    material: Cesium.Color.RED.withAlpha(0.5)
                }
            })
        },
    

    详情参见 Cesium实战专栏

    展开全文
  • Cesium倾斜模型分层单体化

    千次阅读 2020-10-17 14:44:49
    Cesium倾斜模型分层单体化前言实现效果实现思路 前言 目前Cesium三维项目很多是使用倾斜摄影模型,但是倾斜模型只是一张好看的皮,不能进行交互操作,所以需要后期实现单体化功能,单体化有很多种,比如楼栋单体化、...

    Cesium倾斜模型分层单体化

    前言

    目前Cesium三维项目很多是使用倾斜摄影模型,但是倾斜模型只是一张好看的皮,不能进行交互操作,所以需要后期实现单体化功能,单体化有很多种,比如楼栋单体化、分层单体化、更细的有分户单体化。

    实现效果

    在这里插入图片描述

    实现思路

    倾斜模型分层单体化需要数据结合代码来实现,数据生产需要采集每层的面坐标串、底部高程、层高、顶部高度等信息。有了数据后在Cesium中通过ClassificationPrimitive渲染出来。

    关键代码

     //创建拉伸的多边形对象
        createExtrudedPolygon(id, polygonGeometry) {
            return this.viewer.scene.primitives.add(
                new Cesium.ClassificationPrimitive({
                    geometryInstances: new Cesium.GeometryInstance({
                        geometry: Cesium.PolygonGeometry.createGeometry(polygonGeometry),
                        attributes: {
                            color: Cesium.ColorGeometryInstanceAttribute.fromColor(
                                Cesium.Color.fromRandom({ alpha: 0.8 })
                            ),
                            show: new Cesium.ShowGeometryInstanceAttribute(true),
                        },
                        id: id, //设置id有效 其他属性无效
                    }),
                    classificationType: Cesium.ClassificationType.CESIUM_3D_TILE,
                })
            );
        },
    

    详情参见 Cesium实战专栏

    展开全文
  • 数据瘦身有几招之倾斜模型

    千次阅读 2018-05-17 15:02:26
    然而,越精细、越广阔的倾斜模型就意味着加载和浏览时的压力越大。这该肿么办?且看SuperMap祭出的倾斜数据瘦身三大法宝。 SuperMap三维提供了数种对倾斜数据的优化方案,其中最为常用者有三:一是从倾斜数据的数...
  • BIM模型倾斜摄影模型、3D模型对比

    千次阅读 2020-04-27 21:10:51
    开发工具与关键技术:SuperMap iDesktop 10...BIM模型和属性信息丰富(模型+属性一次性导出) BIM模型精细度非常高(导出时对模型进行优化) BIM软件中分层管理模型(以数据集和图层来管理) BIM与GIS对接的...
  • 倾斜摄影模型通常被称作“一张皮”的模型,为发挥倾斜摄影模型应用价值,必须对倾斜摄影模型进行单体化处理。超图三维率先采用了模型叠加矢量面的方式对倾斜摄影模型进行单体化,此方式操作简单,并且在矢量面数据...
  • Cesium倾斜模型分户单体化前言实现效果实现思路 前言 目前Cesium三维项目很多是使用倾斜摄影模型,但是倾斜模型只是一张好看的皮,不能进行交互操作,所以需要后期实现单体化功能,单体化有很多种,比如楼栋单体化、...
  • 在WebGIS直接浏览大规模Osgb格式倾斜模型(100GB+)中展示了实现成果,并且文末说了要写一个《如何在Web上直接浏览大规模OSGB格式倾斜模型》专门介绍实现流程。 今天是2021年国庆假期第二天,也是留守公司看大门...
  • 上一篇(如何在Web上直接浏览大规模OSGB格式倾斜模型(一):解析OSGB)已经贴出了解析结果,让我们对OSGB倾斜模型文件内部结构有了比较清晰的认识,本篇我们将解析结果转成three.js对象,并实现单个模型的显示。...
  • SuperMaSuperMap iMobile 8C 技术文档 ——加载倾斜摄影模型 作者:博哥 北京超图软件股份有限公司 中国•北京
  • 原文发布时间:2017/8/4 14:39:06 作者:土豆(天下图) ... 1 序言 众所周知,我们利用Smart3D除了生成各种数据格式的三维模型以外,还可以在...但有时我们手头仅有倾斜模型数据成果(如osgb格式模型),或者在...
  • 问题 当在地形上移动一个汽车模型时,使用... 你想正确地放置和倾斜汽车模型使之可以匹配地形的起伏。 解决方案 这个问题可以分成四个部分: 首先,你想找到模型四个轮胎的最低顶点的位置。 其次,你想获取这四个顶...
  • 倾斜摄影3D建模

    千次阅读 2020-05-04 21:33:26
    根据航拍的照片、视频或者结合点云,自动化生成正摄影像(DOM)、地形(DSM)倾斜模型;对于不满意的地方可以人工介入进行少量修改;自带模型浏览工具,包含简单的量测、分析。 通俗、简单的讲,即我们可以通过...
  • 三维城市精细城市模型的获取大体有两种方式:一种是通过现场拍摄建筑照片手工建模完成,另一种是通过无人机倾斜摄影测量获取。这两种方式构建的代价都非常昂贵,而且数据量庞大。对于一些对城市模型精度要求没有那么...
  • 无人机航拍的影像经过建模软件处理产出之时,有很多成果的数据需要我们去选择输出,对于新手而言,如何选择数据格式呢?他们之间有什么区别?...目前市面上生产的倾斜模型,尤其Smart3D处理的倾斜摄影三...
  • 前端面试题(持续更新中)

    万次阅读 多人点赞 2019-11-06 17:16:33
    // 旋转,缩放,定位,倾斜 增加了更多的CSS选择器 多背景 rgba 在CSS3中唯一引入的伪类是 ::selection. 媒体查询,多栏布局 border-image 15、为什么要初始化CSS样式。 因为浏览器的兼容问题,不同浏览器对有些标签的...
  • 前端面试题

    万次阅读 多人点赞 2019-08-08 11:49:01
    列举浏览器对象模型BOM里常用的至少4个对象,并列举window对象的常用方法至少5个 72 简述列举文档对象模型DOM里document的常用的查找访问节点的方法并做简单说明 72 希望获取到页面中所有的checkbox怎么做?(不...
  • 在这 里,我们要和大家讲解倾斜摄影应用的两个重要话题——倾斜摄影自动化建模成果(后文简称“倾斜模型”)的数据组织和单体化。  数据组织:要LOD 更要便捷和安全  倾斜模型的一个突出的特点就是数据量庞大,这...
  • 初探AR技术

    千次阅读 多人点赞 2019-10-06 15:36:58
    3D增强:Wikitude SDK可以在增强现实场景中加载和渲染3D模型。从行业工具(比如Autodesk Maya 3D或Blender等工具)导入3D模型,每个3D模型基于新的Native API,Wikitude提供了一个用于Unity3D框架的插件,因此开发者...
  • 本文的重点是,新的建模软件结合无人机倾斜摄影有巨大优势,相比较传统的建模方法,效率提高,成本降低,新...借助倾斜摄影测量技术和Smart3D 软件实现精细三维模型的快速构建,规范和分析了数据获取的主要流程和数据.
  • 使用英特尔流式SIMD扩展优化动画模型渲染流水线 作者JMP van Waveren, Id Software,Inc. 原文名称 Optimizing the Rendering Pipeline of Animated Models Using the Intel Streaming SIMD Extensions ...
  • 图4-9 09 单击“猜测垂直倾斜”按钮,使得“VR-物理摄影机”渲染的墙线垂直画面,如图4-10所示。 图4-10 10 设置完成后,摄影机的构图如图4-11所示。 图4-11 4.3 材质制作 本案例中所涉及到的主要材质分别为水泥...
  • 图9-9 09 单击“猜测垂直倾斜”按钮,使得“VR-物理摄影机”渲染的墙线垂直画面,如图9-10所示。 图9-10 10 设置完成后,摄影机的构图如图9-11所示。 图9-11 9.3 材质制作 本案例中所涉及到的主要材质分别为制作...
  • 图7-9 09 单击“猜测垂直倾斜”按钮,使得“VR-物理摄影机”渲染的墙线垂直画面,如图7-10所示。 图7-10 10 设置完成后,摄影机的构图如图7-11所示。 图7-11 7.3 材质制作 本案例中所涉及到的主要材质分别为墙体...
  • 图10-9 09 展开“透视控制”卷展栏,勾选“自动垂直倾斜校正”选项,使得物理摄影机渲染的墙线垂直画面,如图10-10所示。 图10-10 10 设置完成后,摄影机的构图如图11-11所示。 图11-11 10.3 材质制作 本案例中...
  • 图6-9 09 单击“猜测垂直倾斜”按钮,使得“VR-物理摄影机”渲染的墙线垂直画面,如图6-10所示。 图6-10 10 设置完成后,摄影机的构图如图6-11所示。 图6-11 6.3 材质制作 本案例中所涉及到的主要材质分别为制作...
  • 图3-9 09 单击“猜测垂直倾斜”按钮,使得“VR-物理摄影机”渲染的墙线垂直画面,如图3-10所示。 图3-10 10 设置完成后,摄影机的构图如图3-11所示。 图3-11 3.3 材质制作 本案例中所涉及到的主要材质分别为墙体...
  • 虽然软件名称直译过来为“专业森林”,但是这一款插件所自带的模型库中也包含有草地、花、落叶、石头等其他类型的模型,这意味着我们不仅仅可以通过这一插件制作出森林,也可以制作出高精度成片的花草、园林景观中的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 4,723
精华内容 1,889
关键字:

倾斜模型渲染