一、primitive简介
1、概念:[Primitive](https://cesium.com/learn/cesiumjs/ref-doc/Primitive.html) 是Cesium中用于绘制几何图形的另一个重要的接口,相对于[Entity](https://cesium.com/learn/cesiumjs/ref-doc/Primitive.html)来说,它更接近渲染引擎底层,主要面向图形开发人员,意味着使用Primitive来绘制几何图形需要您具备一定的图形开发知识。
2、引用示例
在场景中添加 Entity 是通过viewer.entities.add方法,而添加Primitive则是通过viewer.scene.primitives.add方法。下面创建一个圆几何,通过Primitive添加到场景:
const circle = new Cesium.CircleGeometry({
center: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
radius: 200.0
});
const geometry = Cesium.CircleGeometry.createGeometry(circle);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
3、总结
一个Primitive实例必须设置geometryInstances和appearance两个属性,才能正确地在场景中显示出来。geometryInstances代表要显示的几何形状,而appearance则代码该几何的外观样式。
为什么要有两套绘制图形的接口:
1、Entity是Cesium封装的比较高级的绘图接口,主要面向普通的开发人员,即使您没有任何图形开发知识,您也能使用Entity在场景中快速绘制各种几何形状。
2、Primitive是Cesium封装的更低级绘图接口,绘图方式更接近渲染引擎底层,但又不至于直接使用WebGL底层的绘图接口,其主要面向图形开发人员。
3、使用Primitive的第一大优势是提供了GeometryInstance 即几何实例化的技术,在渲染大量静态数据时很有用,能够减少CPU开销,充分利用GPU的性能。
4、使用Primitive的第二大优势是提供了顶点着色器和片元着色器的编程接口,可以充分发挥您的图形开发技术。
二、primitive的构成
1、Primitive 主要由以下两部分构成:Geometry 几何形状,用于定义Primitive显示的几何形状;Appearance 外观,用于决定Primitive的着色或渲染。
2、通俗来说Geometry就是骨架,而Appearance就是皮肤。一个 Primitive 可以包含多个Geometry,但是只能有一个Appearance,比如下面的代码通过一个Primitive创建10个Geometry:
let p = [110.0, 30.0];
let instances = [];
for (let i = 0; i < 10; i++) {
const instance = new Cesium.GeometryInstance({
geometry: new Cesium.EllipseGeometry({
center: Cesium.Cartesian3.fromDegrees(p[0], p[1]),
semiMinorAxis: 2000.0,
semiMajorAxis: 2000.0,
height:1000*i
})
});
instances.push(instance);
}
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: Cesium.Material.fromType('Color')
})
}));
3、每个Geometry是通过GeometryInstance来进行实例化的,通过GeometryInstance对Geometry进行实例化,通过GeometryInstance对Geometry进行实例化,同一个Geometry可以被实例化多次。这对于展示大量数据时很有用,比如有100万个BoxGeometry需要被显示,我们只需要创建一个BoxGeometry,然后通GeometryInstance来设置每一个的大小、位置、颜色等。代码效果如下:
let p = [110.0, 30.0];
let instances = [];
let boxGeometry = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(100, 100, 100)
})
for (let i = 0; i < 1000000; i++) {
const instance = new Cesium.GeometryInstance({
geometry: boxGeometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(p[0] + Math.random() , p[1] + Math.random() , 200+Math.random()*100))//通过modelMatrix设置不同的位置
});
instances.push(instance);
}
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: Cesium.Material.fromType('Color')
})
}));
4、我们还可以通过对GeometryInstance设置一些属性来进行Geometry的识别,当一个Primitive中装载有多个Geometry时,我们在鼠标交互的时候如果希望知道拾取到的是那个Geometry,那么就可以通GeometryInstance设置一个id,这样我们就知道拾取到的是哪个Geometry。代码效果如下:
let p = [110.0, 30.0];
let instances = [];
let boxGeometry = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(100, 100, 100)
})
for (let i = 0; i < 1000; i++) {
const instance = new Cesium.GeometryInstance({
geometry: boxGeometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(p[0] + Math.random(), p[1] + Math.random(), 200 + Math.random() * 100)),
id: "BoxGeometry" + i
});
instances.push(instance);
}
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.EllipsoidSurfaceAppearance({
material: Cesium.Material.fromType('Color')
})
}));
new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas).setInputAction(e => {
let pick = viewer.scene.pick(e.position);
console.log(pick);
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
5、需要注意的是,因为一个Primitive只能设置一个Appearance,所以当一个Primitive装载有多个Geometry时,这些Geometry只能具有相同的外观。我们可以通过PerInstanceColorAppearance类型的外观为每个Geometry实例设置一个颜色。代码效果如下:
let p = [110.0, 30.0];
let instances = [];
let boxGeometry = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(100, 100, 100)
})
for (let i = 0; i < 100; i++) {
const instance = new Cesium.GeometryInstance({
geometry: boxGeometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(p[0] + Math.random() / 10, p[1] + Math.random() / 10, 200 + Math.random() * 100)),
attributes: {
color: Cesium.ColorGeometryInstanceAttribute.fromColor(Cesium.Color.fromRandom({ alpha: 1 }))
}
});
instances.push(instance);
}
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instances,
appearance: new Cesium.PerInstanceColorAppearance({
flat: true,
translucent: false
})
}));
三、primitive的优缺点
1、优点:性能好,使用Primitive可以将多个Geometry合并为一个大的Geometry减少CPU的开销,更充分利用GPU。合并过程使用WebWorker不影响UI响应。灵活度高,Primitive由Geometry和Appearance构成,我们可以单独修改它们。可编程性强,可以操作顶点着色器和片元着色器,还可以自定义渲染。
2、缺点:使用更为复杂,需要编写更多的代码,需要更多的图形学知识。组合图形对于动态数据不是很友好,比如使用Primitive组合多个Geometry时无法更新Geometry。
3、综合对比:通过Entity和Primitve分别创建100万个Box进行性能测试,帧率相差很大。海量数据的应用必选primitive
Entity创建Box:
viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
box: {
dimensions: new Cesium.Cartesian3(500.0, 500.0, 500.0),
material:Cesium.Color.BLUE
}
})
Primitive创建Box:
const box = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(500.0, 500.0, 500.0)
});
const geometry = Cesium.BoxGeometry.createGeometry(box);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500)),
});
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
四、Primitive的分类
Cesium中除了Primitive类以外,还有一些以Primitive结尾的类,这些类的使用方式和Primitive类似,可以将其视为Primitive的同类。
1、GroundPrimitive:贴地Primitive,将几何图形贴地显示,适用于平面类型的Geometry, 比如CircleGeometry、PolygonGeometry、RectangleGeometry。贴地Primitive即是将图形从上往下贴,可以通过classificationType属性设置贴的目标类型。
2、ClassificationPrimitive:分类Primitive,将几何体贴模型高亮显示,适用于体类型的Geometry 比如BoxGeometry、 CylinderGeometry,也适用于设置拉伸后的平面类型的Geometry。分类Primitive常用于高亮显示模型,比如倾斜模型的分层单体化或者分户单体化就可以使用该类来实现。
3、GroundPolylinePrimitive:贴地线,仅用于GroundPolylineGeometry
4、PointPrimitive:点,不直接new 而是通过下方的PointPrimitiveCollection .add方法 添加
5、PointPrimitiveCollection:点集合
6、VoxelPrimitive
7、DebugCameraPrimitive:相机可视化,常用于调试一个相机的相关参数
五、Primitive的几何类型
官方分类,总共有12种,并且每一种都有对应的边线模式的Geometry类型
1、BoxGeometry 盒子几何,比如长方体、正方体创建一个盒子,我们通常是通BoxGeometry.fromDimensions()方法,而不是new BoxGeometry(),因为fromDimensions可以直接设置盒子的长宽高。BoxGeometry没有提供设置位置的属性,所以我们需要通过GeometryInstance的modelMatrix属性将其定位到地球上正确的位置。
const box = Cesium.BoxGeometry.fromDimensions({
dimensions: new Cesium.Cartesian3(500.0, 500.0, 500.0)
});
const geometry = Cesium.BoxGeometry.createGeometry(box);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500)),
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
2、CircleGeometry 圆几何,CircleGeometry通过center属性设置定位,但是高度需要通过height属性来设置
const circle = new Cesium.CircleGeometry({
center: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
radius: 200.0,
height:500
});
const geometry = Cesium.CircleGeometry.createGeometry(circle);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
CircleGeometry设置拉伸高度后也可以看做是一个圆柱,拉伸高度是指从当前圆的高度拉伸到那个高度,比如当前圆的高度为500,设置拉伸高度为100,那么就是从上往下拉伸。
const circle = new Cesium.CircleGeometry({
center: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
radius: 200.0,
height:500,
extrudedHeight:100
});
3、CorridorGeometry 带宽度的线几何,也有人称为走廊,坐标中的z值会被忽略,如果需要设置高度,需要通过height属性设置。
const circle = new Cesium.CorridorGeometry({
positions: Cesium.Cartesian3.fromDegreesArrayHeights([110.0, 30.0,1000, 110.1, 30.0,1000, 110.1, 30.1,1000]),
width: 1000,
height:1000,
});
const geometry = Cesium.CorridorGeometry.createGeometry(circle);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
可以通过cornerType属性设置转角样式
设置拉伸高度后,更有立体感
const circle = new Cesium.CorridorGeometry({
positions: Cesium.Cartesian3.fromDegreesArrayHeights([110.0, 30.0,1000, 110.1, 30.0,1000, 110.1, 30.1,1000]),
width: 1000,
height:1000,
extrudedHeight:100
});
4、CylinderGeometry 椎体几何,可用于圆柱、圆锥、截头圆锥。
//圆柱:
const cylinder = new Cesium.CylinderGeometry({
length: 1000,
topRadius: 500,
bottomRadius: 500,
});
const geometry = Cesium.CylinderGeometry.createGeometry(cylinder);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500)),
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
//椎体:
const cylinder = new Cesium.CylinderGeometry({
length: 1000,
topRadius: 0,
bottomRadius: 500,
slices:4 //标识四棱锥
});
const geometry = Cesium.CylinderGeometry.createGeometry(cylinder);
5、EllipseGeometry 椭圆几何,EllipseGeometry和CircleGeometry很相似,但是可以设置长半径和短半径。
const ellipse = new Cesium.EllipseGeometry({
center: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
semiMajorAxis: 500.0,
semiMinorAxis: 1500.0,
height: 300,
});
const geometry = Cesium.EllipseGeometry.createGeometry(ellipse);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
椭圆也可以设置拉伸高度
const ellipse = new Cesium.EllipseGeometry({
center: Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500),
semiMajorAxis: 500.0,
semiMinorAxis: 1500.0,
height: 300,
extrudedHeight:800
});
6、EllipsoidGeometry 椭球体几何
const ellipsoid = new Cesium.EllipsoidGeometry({
radii: new Cesium.Cartesian3(1000.0, 800.0, 500.0)
});
const geometry = Cesium.EllipsoidGeometry.createGeometry(ellipsoid);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500)),
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
将三个方向的半径设置为相等的值后得到的就是一个正球体
const ellipsoid = new Cesium.EllipsoidGeometry({
radii: new Cesium.Cartesian3(1000.0, 1000.0, 1000.0)
});
7、RectangleGeometry 矩形几何,我们一般会从一个点集中获取一个Rectangle即这些数据的外包矩形
let positions = [
[108.95959, 34.220223, 435],
[108.95922, 34.220054, 435],
[108.959141, 34.219439, 435],
[108.959754, 34.219573, 435],
[108.959572, 34.21978, 435]
]
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
const rectangle = new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromCartesianArray(positions),
height: 20.0
});
const geometry = Cesium.RectangleGeometry.createGeometry(rectangle);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
RectangleGeometry也可以设置拉伸高度,变成体
const rectangle = new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromCartesianArray(positions),
height: 20.0,
extrudedHeight:100
});
8、PolygonGeometry 多边形几何
let positions = [
[108.95959, 34.220223, 435],
[108.95922, 34.220054, 435],
[108.959141, 34.219439, 435],
[108.959754, 34.219573, 435],
[108.959572, 34.21978, 435]
]
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions)
});
const geometry = Cesium.PolygonGeometry.createGeometry(polygon);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
多边形几何有两种方式设置其高度,第一种是启用顶点数组中的高度值
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions) ,
perPositionHeight:true
});
第二种是设置height属性,这种方式会将所有点的高度设置为统一的值
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions) ,
height:300
});
PolygonGeometry也可以设置拉伸高度
const polygon = new Cesium.PolygonGeometry({
polygonHierarchy: new Cesium.PolygonHierarchy(positions) ,
height:300,
extrudedHeight:200
});
9、PolylineGeometry 线几何
let positions = [
[108.95959, 34.220223, 20],
[108.95922, 34.220054, 33],
[108.959141, 34.219439, 22],
[108.959754, 34.219573, 43],
[108.959572, 34.21978, 23]
]
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
const polyline = new Cesium.PolylineGeometry({
positions: positions,
width: 10.0
});
const geometry = Cesium.PolylineGeometry.createGeometry(polyline);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PolylineMaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
PolylineGeometry需要使用专门的PolylineAppearance外观。PolylineGometry支持传入顶点颜色,实现渐变样式。
let positions = [
[108.95959, 34.220223, 20],
[108.95922, 34.220054, 33],
[108.959141, 34.219439, 22],
[108.959754, 34.219573, 43],
[108.959572, 34.21978, 23]
]
let colors = [
Cesium.Color.RED,
Cesium.Color.GREEN,
Cesium.Color.BLUE,
Cesium.Color.YELLOW,
Cesium.Color.WHITE
];
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
const polyline = new Cesium.PolylineGeometry({
positions: positions,
width: 10.0,
colors: colors,
colorsPerVertex: true
});
const geometry = Cesium.PolylineGeometry.createGeometry(polyline);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PolylineColorAppearance({
translucent: false
})
}));
10、PolylineVolumeGeometry 按2D形状拉伸的折线,也可以称为管道。创建PolylineVolumeGeometry需要先创建它的切面形状,computeCircle方法用于创建一个圆形的切面形状。
let positions = [
[108.95959, 34.220223, 20],
[108.95922, 34.220054, 20],
[108.959141, 34.219439, 25]
]
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
function computeCircle(radius) {
const positions = [];
for (let i = 0; i < 360; i++) {
const radians = Cesium.Math.toRadians(i);
positions.push(new Cesium.Cartesian2(radius * Math.cos(radians), radius * Math.sin(radians)));
}
return positions;
}
const geometry = new Cesium.PolylineVolumeGeometry({
polylinePositions: positions,
shapePositions: computeCircle(2)
});
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
11、SphereGeometry 球体几何,球体几何和椭球体几何差不多
let sphere = new Cesium.SphereGeometry({
radius: 100.0,
});
const geometry = Cesium.SphereGeometry.createGeometry(sphere);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 500)),
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
12、WallGeometry 墙体几何
let positions = [
[108.95959, 34.220223, 50],
[108.95922, 34.220054, 50],
[108.959141, 34.219439, 50],
[108.959754, 34.219573, 50],
[108.959572, 34.21978, 50]
]
positions = Cesium.Cartesian3.fromDegreesArrayHeights([].concat.apply([], positions));
const wall = new Cesium.WallGeometry({
positions:positions
});
const geometry = Cesium.WallGeometry.createGeometry(wall);
const instance = new Cesium.GeometryInstance({
geometry: geometry,
});
viewer.scene.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: "Color",
uniforms: {
color: Cesium.Color.BLUE
}
}
})
})
}));
除了上面12个基础的Geometry类型外,还有下面几种Geometry
a、FrustumGeometry&FrustumOutlineGeometry截椎体几何,常结合相机Camera来使用
b、GroundPolylineGeometry专门用于贴地线的几何,结合GroundPolylinePrimtive使用
c、PlaneGeometry&PlaneOutlineGeometry平面几何
d、CoplanarPolygonGeometry&CoplanarPolygonOutlineGeometry用于共面多边形的几何,PolygonGeometry绘制共面多边形可能会发生崩溃的可能,但是CoplanarPolygonGeometry不会。
六、Primitive自定义几何
Cesium自带的Geometry类型已经很丰富了,但有时也避免不了需要定义自己的Geometry类型。自定义Geometry类型,可以通过Cesium.Geometry类实现,这个类的构造函数如下:
1、attributes Geometry的顶点数据,包括位置、法线、纹理坐标等
2、further/primitiveType 图元类型,点、线、三角形等,默认为三角形
3、indices 顶点的索引 ,
4、boundingSphere 包围盒,包围盒用于剔除当前视椎体外的图形
我们以实现一个四棱锥为例,介绍如何定义自带的Geometry类型,四棱锥在Cesium内置的Geometry中已经有实现,可以通过创建CylinderGeometry设置边数为4,顶部半径为0,这里我们只是介绍如何去实现自定义类型的方法。比如:
第一步、 获取四棱锥的顶点数据
我们先看一下四棱锥的结构,四棱锥共5个面,4个测面,1个顶面。4个测面都是三角形,顶面是一个正方形,因为在图形渲染中没有正方形的概念,所以我们要将其拆分为2个三角形,所以一个四棱锥总共有4+2=6个三角形。每个三角形有3个点,所以一个四棱锥有3x6=18个顶点。虽然按照上面分析的一个四棱锥有18个顶点,但是我们可以看得出来其实很多点是重复的,我们可以通过索引的方式重用顶点,那么此时四棱锥应该只剩下5个顶点,如下图所示:
我们按照在原点建模的方式,设四棱锥的中心点位于坐标原点,半径为1,我们对每个点进行标号,那么此时可以得出下图的结果:
将坐标点存储到一个数组中
const positions = new Float64Array([
0,0,-1,// 0
1,-1,1,// 1
-1,-1,1,//2
-1,1,1,// 3
1,1,1,// 4
])
第二步、 计算索引
有了顶点数据,我们就可以获取索引了,我们按照一定的顺序组织索引值。
const indices = new Uint16Array([
0, 1, 2,//前
0, 2, 3,//左
0, 3, 4,//后
0, 4, 1,//右
1, 3, 2,//上
1, 4, 3,//上
]);
第三步、 获取包围盒
如果包围盒计算不正确,可能会出现看不到渲染结果的情况,因为Cesium场景渲染会剔除掉当前视椎体外即看不到的图形(大多数渲染引擎应该都这样)。Cesium中的BoundingSphere类提供了fromVertices方法可以直接从顶点数组中获取包围盒对象。
let boundingSphere=Cesium.BoundingSphere.fromVertices(positions);
有了顶点、索引、包围盒后我们就可以创建Geometry了,完整代码如下:
const positions = new Float64Array([
0,0,-1,// 0
1,-1,1,// 1
-1,-1,1,//2
-1,1,1,// 3
1,1,1,// 4
]);
const indices = new Uint16Array([
0, 1, 2,//前
0, 2, 3,//左
0, 3, 4,//后
0, 4, 1,//右
1, 3, 2,//上
1, 4, 3,//上
]);
let boundingSphere=Cesium.BoundingSphere.fromVertices(positions);
let m = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.0, 30.0, 10));
let geometry = new Cesium.Geometry({
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE,
componentsPerAttribute: 3,
values: positions
})
},
indices: indices,
further/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.further/primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PerInstanceColorAppearance({
translucent: true
})
}));
因为我们几何的顶点是基于原点的,所以需要创建一个矩阵将其转换到地球的表面上来。虽然几何体的渲染结果是正确的,但是看起来有点别扭,因为菱角不分明,这是因为我们没有为Geometry的顶点设置正确的法线。
第四步、Primitive自定义几何设置法线。我们知道,在图形学中顶点的法线是所有与它有关的面的法线的加和的单位化向量。如下图所示:
红色点的法线是与其相关的三个面的法线的和,那么要计算这个点的法线,首先得先计算这三个面的法线,整个计算过程虽然不难,但是却很麻烦。每个顶点的法线计算之所以很麻烦,是因为计算每个顶点的法线都必须先计算其相关的面的法线。根据作者经验,按照上述方法计算出每个顶点正确的法线后,显示的结果还是与没有法线差别不大。这是因为我们使用索引的方式共享了顶点,四棱锥是有6个三角面共18个顶点,现在我们这5个具有法线的顶点通过索引的方式最后还是拆为了18个点,四棱锥的角比较尖锐,每个面的法线差别很大,所以这5个点的法线不能作为其在每个面上的点的法线。针对四棱锥这种角比较尖锐,每个面法线差别很大的情况,这里正确的做法是取消索引的方式共享顶点,按照每个三角面3个顶点来构造Geometry顶点数据,这样我们求取每个三角面每个顶点的法线,渲染出来的结果才会理想。
接下里修正坐标数组
const positions = new Float64Array([
// //前
0, 0, -1,// 0
1, -1, 1,// 1
-1, -1, 1,// 2
//左
0, 0, -1,// 0
-1, -1, 1,// 2
-1, 1, 1,// 3
// //后
0, 0, -1,// 0
-1, 1, 1,// 3
1, 1, 1,// 4
// //右
0, 0, -1,// 0
1, 1, 1,// 4
1, -1, 1,// 1
//上
1, -1, 1,// 1
1, 1, 1,// 4
-1, 1, 1,// 3
//上
1, -1, 1,// 1
-1, 1, 1,// 3
-1, -1, 1,// 2
]);
然后计算每个顶点的法线,现在每个顶点的法线就是其所在三角形的法线,我们以前一个面为例,如下图:
计算一个三角形的法线n,即为这个三角形内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());
normals = [];
normals.push(
normal.x, normal.y, normal.z,
normal.x, normal.y, normal.z,
normal.x, normal.y, normal.z
);
其他顶点的法线以同样的方式求取,完整代码如下:
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 = [];
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,// 0
1, -1, 1,// 1
-1, -1, 1,// 2
//左
0, 0, -1,// 0
-1, -1, 1,// 2
-1, 1, 1,// 3
// //后
0, 0, -1,// 0
-1, 1, 1,// 3
1, 1, 1,// 4
// //右
0, 0, -1,// 0
1, 1, 1,// 4
1, -1, 1,// 1
//上
1, -1, 1,// 1
1, 1, 1,// 4
-1, 1, 1,// 3
//上
1, -1, 1,// 1
-1, 1, 1,// 3
-1, -1, 1,// 2
]);
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 = [];
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,
);
const indices = new Uint16Array([
0, 1, 2,
3, 4, 5,
6, 7, 8,
9, 10, 11
]);
let m = Cesium.Transforms.eastNorthUpToFixedFrame(Cesium.Cartesian3.fromDegrees(110.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)
})
},
// indices: indices,
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.BLUE)
}
});
viewer.scene.primitives.add(new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.PerInstanceColorAppearance({
translucent: false
})
}));
到这primitive的进阶学习告一段落,能掌握这些再加上primitive的源码就能完全掌握。
标签:Primitive,const,进阶,normal,geometry,Cartesian3,Cesium,new From: https://blog.csdn.net/weixin_43700915/article/details/142751883