重读Cesium(七):Primitive自定义几何并设置法线

在上一篇文章中,我们已经大致的了解了Primitive几何类型,我们发现,Cesium自带的Geometry类型已经很丰富了,但有时候也避免不了需要定义自己的Geometry类型。

对于自定义的Geometry类型,我们可以通过

new Cesium.Geoemtry(options) 类去构造实现

重读Cesium(七):Primitive自定义几何并设置法线_第1张图片

1. attributes 类 : 一个 GeometryAttributes对象,每个顶点属性都存储在这个对象中,顶点属性包括但不限于:坐标、法线、颜色值、uv等。

2. primitiveType 类 : 几何图元的类,点、线、三角形等。默认为三角形
Cesium.PrimitiveType.TRIANGLES

3. indices 类 : 顶点的索引,索引数组,使用索引可以减少坐标个数,节约内存。

4. boundingSphere 类 : 可选参数,包围盒。包围整个几何形状的球体,可以辅助剔除来提高性能。

我 们以实现下面的四棱锥为例子,介绍一下如何自定义自带的Geometry类型。

四棱锥可以在Cesium内置的CylinderGeometry
类型中设置边数为4,顶部半径为0实现,这里只是介绍如何去实现自定义Geometry方法

重读Cesium(七):Primitive自定义几何并设置法线_第2张图片

primitive自定义几何

我们可以看看上面四棱锥的构造,四棱锥一共由4个侧面,1个顶面构成。其中4个测面都是三角形,顶面为正方形( 但是在图形渲染概念中没有正方形的概念
),所以我们将正方形拆分成为2个三角形。所以我们得到的结果是 四棱锥 总共有 6个三角形 。每个三角形有3个点,所以 四棱锥 一共有
3*6 = 18 个顶点。

虽然按照上述的分析一个四棱锥有18个顶点,但是你会发现这里面有很多顶点都是重复的,我们可以通过 **_ 索引的方式重建顶点 _ **
的话,那么四棱锥就只会剩下 5 个顶点。我们按照在原点建模,设置四棱锥的中心点位于坐标原点,半径为1,我们对每个点进行标号ABCDE(如下图)。

如此我们得到了这些数据:



A(1,-1,1)  
B(-1,-1,1)
C(-1,1,1)
D(1,1,1)
E(0,0,-1)

重读Cesium(七):Primitive自定义几何并设置法线_第3张图片

重读Cesium(七):Primitive自定义几何并设置法线_第4张图片



A(1,-1,1)  
B(-1,-1,1)
C(-1,1,1)
D(1,1,1)
E(0,0,-1)

1.我们将上面的坐标点存储到一个数组中:


const positions = new Float64Array([
1,-1,1, // 0
-1,-1,1,  //1
-1,1,1,  //2
1,1,1, //3
0,0,-1  //4
])

2.有了这些顶点数据后,我们就可以获取索引了,按照一定的顺序组织索引值



const induces = new Uint16Array([
4,0,1,  // ABE面
4,1,2,//BCE面
4,2,3, //CDE面
4,3,0, // DAE面
0,1,2,  //平面拆分的三角形 ABC
0,3,1//平面拆分的三角形 ABD
)]

3. 然后我们获取包围盒。 BoundingSphere 有一个 fromVertices()
函数来计算紧密包围几何形状所有坐标的包围球。如果包围盒计算不正确的话,我们可能会出现看不到渲染结果的情况,因为大多数渲染引擎中场景渲染会剔除当前视锥体外即看不到的图形。

let boundingSphere = = Cesium.BoundingSphere.fromVertices(positions);

至此,我们获取到了所有需要的数据( 顶点 positions ,索引 induces ,包围盒 boundingSphere
), 接下来我们开始创建Geometry,代码如下:


const positions = new Float64Array([
      1,-1,1, // 0
      -1,-1,1,  //1
      -1,1,1,  //2
      1,1,1, //3
      0,0,-1  //4
      ])
      const indices = new Uint16Array([
      4,0,1,  // ABE面
      4,1,2,//BCE面
      4,2,3, //CDE面
      4,3,0, // DAE面
      0,1,2,  //平面拆分的三角形 ABC
      0,3,1//平面拆分的三角形 ABD
      ])
      let boundingSphere = Cesium.BoundingSphere.fromVertices(positions)
      let m = Cesium.Transforms.eastNorthUpToFixedFrame(
        Cesium.Cartesian3.fromDegrees(120.0, 30.0, 100)
      )
      let geometry = new Cesium.Geometry({
        attributes: {
          position: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.DOUBLE,
            componentsPerAttribute: 3,
            values: positions
          })
        },
        indices: indices,
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        boundingSphere: boundingSphere
      })
      const instance = new Cesium.GeometryInstance({
        geometry: geometry,
        modelMatrix: m,
        attributes: {
          color: Cesium.ColorGeometryInstanceAttribute.fromColor(
            Cesium.Color.RED
          )
        }
      })
      viewer.scene.primitives.add(
        new Cesium.Primitive({
          geometryInstances: instance,
          appearance: new Cesium.PerInstanceColorAppearance({
            translucent: true
          })
        })
      )

重读Cesium(七):Primitive自定义几何并设置法线_第5张图片

因为我们的几何顶点是基于原点的,所以我们需要创建一个矩阵将其转换到地球的表面上。

虽然上面的截图几何体渲染出来了,但是我们看起来有点别扭,因为我们 _没有为Geometry的顶点设置正确的法线 _ ,所以导致菱角不分明。

Primitive自定义几何设置法线

在图形学中, 顶点的法线是所有与它有关的面的法线的加和的单位化向量 ,如图所示:

重读Cesium(七):Primitive自定义几何并设置法线_第6张图片

首先我们需要知道四棱锥是有6个三角面共18个顶点,我们使用的是5个具有法线的顶点通过索引的方式拆分为18个点的。 四棱锥的角比较尖锐_
,每个面的法线差别很大,所以5个点的法线不能作为其在每个点的法线。所以下面我们还是 取消索引的方式共享顶点 ,按照最原始的方法,
每个三角面3个顶点去构造Geometry顶点数据_

1.修改顶点坐标数组:


  const positions = new Float64Array([
        // 前
      0,0,-1, // E点
      1,-1,1, //A点
      -1,-1,1, //B点
      // 后
      0,0,-1, // E点
      -1,1,1, //C点
      1,1,1, //D点
      //左
      0,0,-1, // E点
      -1,-1,1, //B点
      -1,1,1, //C点
      //右
      0,0,-1, // E点
      1,1,1, //D点
      1,-1,1, //A点
      //上
      1,-1,1, //A点
      -1,-1,1, //B点
      1,1,1, //D点
      // 上
      -1,-1,1, //B点
      1,1,1, //D点
      -1,1,1, //C点
      ])

2.计算每个顶点的法线

重读Cesium(七):Primitive自定义几何并设置法线_第7张图片

我们以ABE面为例, **_ 每个顶点的法线就是其所在的三角形的法线。算一个法线,即为这个三角形内向量a与向量b的叉乘所得。 _ ** 代码如下:

**_ _ **


 let c_0 = new Cesium.Cartesian3(0, 0, -1)
 let c_1 = new Cesium.Cartesian3(1, -1, 1)
 let c_2 = new Cesium.Cartesian3(-1, -1, 1)
 let d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
 let d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
 let normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
 normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
 let normals = []
 normals.push(
        normal.x, normal.y, normal.z,
        normal.x, normal.y, normal.z,
        normal.x, normal.y, normal.z
 )

其他顶点也已同样的方法求取,代码如下:


let normals = []
      let c_0 = new Cesium.Cartesian3(0, 0, -1)
      let c_1 = new Cesium.Cartesian3(1, -1, 1)
      let c_2 = new Cesium.Cartesian3(-1, -1, 1)
      let d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
      let d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
      let normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
      normals.push(
        normal.x, normal.y, normal.z,
        normal.x, normal.y, normal.z,
        normal.x, normal.y, normal.z
      )

 c_1 = new Cesium.Cartesian3(1, -1, 1);
 c_2 = new Cesium.Cartesian3(1, 1, 1);
 d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3());
 d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3());
 normals.push(
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z
 );
 c_1 = new Cesium.Cartesian3(1, 1, 1);
 c_2 = new Cesium.Cartesian3(-1, 1, 1);
 d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3());
 d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3());
 normals.push(
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z
 );
 c_1 = new Cesium.Cartesian3(-1, 1, 1);
 c_2 = new Cesium.Cartesian3(-1, -1, 1);
 d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3());
 d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3());
 normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3());
 normals.push(
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z,
     normal.x, normal.y, normal.z
 );
 normals.push(
     0, 0, 1,
     0, 0, 1,
     0, 0, 1,
 );
 normals.push(
    0, 0, 1,
    0, 0, 1,
    0, 0, 1,
 );

这里注意: 因为顶部是正方形切割成两个三角形的,所以顶部的两个三角形的6个顶点的法线其实就是固定的z向量,所以我们直接写成(0,0,1)。

然后我们将法线传入Geometry中,最终完整代码如下:


 const positions = new Float64Array([
        // 前
        0,0,-1, // E点
        1,-1,1, //A点
        -1,-1,1, //B点
        //右
        0, 0,-1, // E点
        1, 1,1, //D点
        1,-1, 1, //A点
        // 后
        0,0,-1, // E点
        -1,1,1, //C点
        1,1,1, //D点
        //左
        0,0,-1, // E点
        -1,-1,1, //B点
        -1, 1,1, //C点
        //上
        1,-1, 1, //A点
        -1, -1, 1, //B点
        1, 1,1, //D点
        // 上
        -1,-1, 1, // B点
        1, 1,1, //D点
        -1, 1,1 //C点
      ])
      let normals = []
      let c_0 = new Cesium.Cartesian3(0, 0, -1)
      let c_1 = new Cesium.Cartesian3(1, -1, 1)
      let c_2 = new Cesium.Cartesian3(-1, -1, 1)
      let d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
      let d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
      let normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
      normals.push(
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z
      )

      c_1 = new Cesium.Cartesian3(1, -1, 1)
      c_2 = new Cesium.Cartesian3(1, 1, 1)
      d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
      d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
      normals.push(
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z
      )
      c_1 = new Cesium.Cartesian3(1, 1, 1)
      c_2 = new Cesium.Cartesian3(-1, 1, 1)
      d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
      d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
      normals.push(
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z
      )
      c_1 = new Cesium.Cartesian3(-1, 1, 1)
      c_2 = new Cesium.Cartesian3(-1, -1, 1)
      d1 = Cesium.Cartesian3.subtract(c_1, c_0, new Cesium.Cartesian3())
      d2 = Cesium.Cartesian3.subtract(c_2, c_0, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.cross(d1, d2, new Cesium.Cartesian3())
      normal = Cesium.Cartesian3.normalize(normal, new Cesium.Cartesian3())
      normals.push(
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z,
        normal.x,
        normal.y,
        normal.z
      )
      normals.push(0, 0, 1, 0, 0, 1, 0, 0, 1)
      normals.push(0, 0, 1, 0, 0, 1, 0, 0, 1)

      let m = Cesium.Transforms.eastNorthUpToFixedFrame(
        Cesium.Cartesian3.fromDegrees(120.0, 30.0, 10)
      )
      let geometry = new Cesium.Geometry({
        attributes: {
          position: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.DOUBLE,
            componentsPerAttribute: 3,
            values: positions
          }),
          normal: new Cesium.GeometryAttribute({
            componentDatatype: Cesium.ComponentDatatype.FLOAT,
            componentsPerAttribute: 3,
            values: new Float64Array(normals)
          })
        },
        primitiveType: Cesium.PrimitiveType.TRIANGLES,
        boundingSphere: Cesium.BoundingSphere.fromVertices(positions)
      })
      const instance = new Cesium.GeometryInstance({
        geometry: geometry,
        modelMatrix: m,
        attributes: {
          color: Cesium.ColorGeometryInstanceAttribute.fromColor(
            Cesium.Color.RED
          )
        }
      })
      viewer.scene.primitives.add(
        new Cesium.Primitive({
          geometryInstances: instance,
          appearance: new Cesium.PerInstanceColorAppearance({
            translucent: false
          })
        })
      )

重读Cesium(七):Primitive自定义几何并设置法线_第8张图片

至此,Primitive大致都结束了,后续咱们开始说说Appearance相关内容。

如果内容对您有帮助,请点点关注!谢谢

你可能感兴趣的:(重读Cesium,Cesium功能集,webgl,javascript,3d)