Skip to content

Latest commit

 

History

History
347 lines (293 loc) · 12.5 KB

File metadata and controls

347 lines (293 loc) · 12.5 KB

Cesium 高性能扩展之DrawCommand(二):OSGB倾斜模型展示应用

DrawCommand 是 Cesium 渲染器的核心类,常用的接口 EntityPrimitiveCesium3DTileSet,以及地形和影像的渲染等等,底层都是一个个 DrawCommand 完成的。在进行扩展开发、视觉特效提升、性能优化、渲染到纹理(RTT),甚至基于 Cesium 封装自己的开发框架,定义独家数据格式等等,都需要开发人员对 DrawCommand 熟练掌握。而这部分接口,Cesium 官方文档没有公开,网上的相关资料也比较少,学习起来比较困难,所以接下来我们用几期文章,由浅入深,实用为主,力求全面地介绍 DrawCommand 及相关类的运用。

drawCommand2

上一期(《Cesium 高性能扩展之DrawCommand(一):入门》)介绍 DrawCommand 基本属性,相信大家已经能够轻松创建一个可用的DrawCommand自定义Primitive了。那么自定义Primitive有什么用呢?还有没有必要深入学习呢?

这一期我们先不增加知识点,综合运用上一期介绍的基本属性,以及前几期介绍的Osgb解析技术(参考文章《如何在Web上直接浏览大规模OSGB格式倾斜模型(一):解析OSGB,附源码 》《如何在Web上直接浏览大规模OSGB格式倾斜模型(二):转换OSGB》),实现在不依赖Three.js的情况下,直接创建DrawCommand来显示Osgb倾斜摄影模型。

很容易总结出关键步骤:

  • Osgb几何体转成Cesium.Geometry
  • Osgb纹理贴图转为Cesium.Texture
  • Shader中读取纹理贴图。

1、将Osgb几何体转成Cesium.Geometry

我们直接基于转换OSGB这篇文章的代码,稍微修改一下就可以实现。

  • Cesium.GeometryAttribute 替代 THREE.BufferAttribute
  • Cesium.Geometry 替代 THREE.BufferGeometry
  • uv属性我们按照Cesium的命名规则,改成st
  • 索引数组,根据最大值来选择相应的数组类型,可以节省内存。
function parseGeometry(osgGeometry) {
    //顶点属性
    var positions = new Float32Array(osgGeometry.VertexArray.flat());
    var uvs = new Float32Array(osgGeometry.TexCoordArray[0].flat());

    //索引
    var primitiveSet = osgGeometry.PrimitiveSetList[0]
    var indices = primitiveSet.data;
    if (primitiveSet.value == 7) {
        let newIndices = [];
        for (let i = 0; i < indices.length; i += 4) {
            let i0 = indices[i], i1 = indices[i + 1], i2 = indices[i + 2], i3 = indices[i + 3];
            newIndices.push(i0, i1, i3, i1, i2, i3);
        }
        indices = newIndices;
    }
    if (indices) {//根据最大值来选择相应的数组类型,可以节省内存
        var max = Math.max.apply(null, indices)
        if (max > 65535) {
            indices = new Uint32Array(indices)
        } else {
            indices = new Uint16Array(indices)
        }
    }

    //构造Cesium几何体
    var geometry = new Cesium.Geometry({
        attributes: {
            position: {
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                componentsPerAttribute: 3,
                values: positions
            },
            st: {
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                componentsPerAttribute: 2,
                values: uvs
            }
        },
        indices: indices
    })

    return geometry
}

2、将Osgb纹理贴图转为Cesium.Texture

我们还是基于转换OSGB这篇文章的代码进行修改,不过我们只要提取纹理贴图。

这里需要注意,Cesium创建纹理贴图对象时会立即将贴图数据绑定到绘图上下文,所以不能必须在获取context之后,也就是渲染循环开始之后才能创建。这点和Three.js不同,THREE.Texture只封装贴图数据和参数,绑定是渲染器层负责的(Three.js的渲染器可有多个实现,比如WebGLRendererWebGPURendererCSSRendererSVGRenderer等等),所以可以在渲染循环开始之前创建。

function parseTexture(osgStateSet, context) {
    var osgImage = osgStateSet.TextureAttributeList[0].value.StateAttribute.Image

    var fileName = osgImage.Name;
    const isJPEG = fileName.search(/\.jpe?g($|\?)/i) > 0
    const isPNG = fileName.search(/\.png($|\?)/i) > 0
    if (!isPNG && !isJPEG) return;

    var mimeType = isPNG ? 'image/png' : 'image/jpeg';
    var imageUri = new Blob([osgImage.Data], { type: mimeType });
    imageUri = URL.createObjectURL(imageUri)
    //加载图片
    return Cesium.Resource.fetchImage(imageUri).then(img => {
        //创建纹理贴图对象
        var texture = new Cesium.Texture({
            context: context,
            source: img,
            width: img.width,
            height: img.height
        });
        return texture
    });
}

3、在Shader中读取纹理贴图

我们改动一下上一期的shader代码,增加st属性,并将stvertexShader传到fragmentShader,当然,这对于老手来说是基本操作了。 vertexShader

attribute vec3 position;
attribute vec2 st;
//向fragmentShader传递
varying vec2 v_st;
void main(){
    //接收js传来的值
    v_st=st;
    gl_Position = czm_projection  * czm_modelView * vec4( position , 1. );
}

fragmentShader

uniform sampler2D map; 
//接收vertexShader传来的值
varying vec2 v_st;
void main(){
    gl_FragColor=texture2D( map , v_st);
}

可以看出,纹理贴图map需要通过uniform传递,所以uniformMap也需要调整。

//处理纹理贴图,先生成一个默认的空白贴图,等模型纹理贴图转换完成,再替换掉
var map = new Cesium.Texture({
    context: context,
    width: 2, height: 2
})
parseTexture(this.osgGeometry.StateSet, context).then(texture => {
    //释放默认纹理贴图
    map.destroy();
    //使用转换好的模型纹理贴图
    map = texture; 
})

var uniformMap = {
    map() {
        return map
    }
}

4、完整代码

构造自定义Primitive时需要传入osg::Geometry类型节点对象,在创建DrawCommand的时调用转换几何体和纹理贴图的函数。

function parseGeometry(osgGeometry) {

    var positions = new Float32Array(osgGeometry.VertexArray.flat());
    var uvs = new Float32Array(osgGeometry.TexCoordArray[0].flat());

    var primitiveSet = osgGeometry.PrimitiveSetList[0]
    var indices = primitiveSet.data;
    if (primitiveSet.value == 7) {

        let newIndices = [];
        for (let i = 0; i < indices.length; i += 4) {
            let i0 = indices[i], i1 = indices[i + 1], i2 = indices[i + 2], i3 = indices[i + 3];
            newIndices.push(i0, i1, i3, i1, i2, i3);
        }
        indices = newIndices;

    }

    if (indices) {

        var max = Math.max.apply(null, indices)
        if (max > 65535) {
            indices = new Uint32Array(indices)
        } else {
            indices = new Uint16Array(indices)
        }

    }

    var geometry = new Cesium.Geometry({
        attributes: {
            position: {
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                componentsPerAttribute: 3,
                values: positions
            },
            st: {
                componentDatatype: Cesium.ComponentDatatype.FLOAT,
                componentsPerAttribute: 2,
                values: uvs
            }
        },
        indices: indices
    })

    return geometry
}

function parseTexture(osgStateSet, context) {
    var osgImage = osgStateSet.TextureAttributeList[0].value.StateAttribute.Image

    var fileName = osgImage.Name;
    const isJPEG = fileName.search(/\.jpe?g($|\?)/i) > 0
    const isPNG = fileName.search(/\.png($|\?)/i) > 0
    if (!isPNG && !isJPEG) return;

    var mimeType = isPNG ? 'image/png' : 'image/jpeg';
    var imageUri = new Blob([osgImage.Data], { type: mimeType });
    imageUri = URL.createObjectURL(imageUri)

    return Cesium.Resource.fetchImage(imageUri).then(img => {
        var texture = new Cesium.Texture({
            context: context,
            source: img,
            width: img.width,
            height: img.height
        });
        return texture
    });
}

class MyPrimitive {

    constructor(modelMatrix, osgGeometry) {

        this.modelMatrix = modelMatrix || Cesium.Matrix4.IDENTITY.clone()
        this.drawCommand = null;
        this.osgGeometry = osgGeometry

    }

    createCommand(context) {

        var modelMatrix = this.modelMatrix;

        //转换几何体
        var geometry = parseGeometry(this.osgGeometry)
         
        var attributeLocations = Cesium.GeometryPipeline.createAttributeLocations(geometry)

        var va = Cesium.VertexArray.fromGeometry({
            context: context,
            geometry: geometry,
            attributeLocations: attributeLocations
        });

        var vs = `
        attribute vec3 position;
        attribute vec2 st;
        varying vec2 v_st;
        void main(){
            v_st=st;
            gl_Position = czm_projection  * czm_modelView * vec4( position , 1. );
        }
        `;
        var fs = `
        uniform sampler2D map; 
        varying vec2 v_st;
        void main(){
            gl_FragColor=texture2D( map , v_st);
        }
        `;
        var shaderProgram = Cesium.ShaderProgram.fromCache({
            context: context,
            vertexShaderSource: vs,
            fragmentShaderSource: fs,
            attributeLocations: attributeLocations
        })

        //处理纹理贴图,先生成一个默认的空白贴图,等模型纹理贴图转换完成,再替换掉
        var map = new Cesium.Texture({
            context: context,
            width: 2, height: 2
        })
        parseTexture(this.osgGeometry.StateSet, context).then(texture => {
            //释放默认纹理贴图
            map.destroy();
            //使用转换好的模型纹理贴图
            map = texture; 
        })

        var uniformMap = {
            map() {
                return map
            }
        }

        var renderState = Cesium.RenderState.fromCache({
            cull: {
                enabled: true,
                face:Cesium.CullFace.BACK
            },
            depthTest: {
                enabled: true
            }
        })

        this.drawCommand = new Cesium.DrawCommand({
            modelMatrix: modelMatrix,
            vertexArray: va,
            shaderProgram: shaderProgram,
            uniformMap: uniformMap,
            renderState: renderState,
            pass: Cesium.Pass.OPAQUE
        })
    }

    /**
     * 
     * @param {Cesium.FrameState} frameState 
     */
    update(frameState) {
        if (!this.drawCommand) {
            this.createCommand(frameState.context)
        }
        frameState.commandList.push(this.drawCommand)
    }

}

5、调用示例及效果

解析,遍历节点,处理类型为osg::Geometry的节点,创建一个自定义Primitive来展示该节点。

import * as osg from 'osg-serializer-browser';

var origin = Cesium.Cartesian3.fromDegrees(106, 26, 0)
var modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(origin)

var url = '../../assets/sampleData/models/tile_0_0_0_tex_children.osgb';
Cesium.Resource.fetchArrayBuffer(url).then(buf => {
    var osgObj = osg.readBuffer(buf)
    osg.TraverseNodes(osgObj, node => {
        if (node.Type == 'osg::Geometry') {
            var primitive = new MyPrimitive(modelMatrix, node);
            viewer.scene.primitives.add(primitive)
        }
    })
})

效果如下: drawCommand2

至此,大家对于文章开头的两个问题应该都有自己的答案了,我们下期继续!

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

微信公众号【三维网格3D】