The code in this paper is based on VUE-CLI4 and Cesium, the map engine using WebGL. The main content is the dynamic material construction of different objects in 3d scenes.

References are made to many articles, links are attached at the end.

Add dynamic materials for different geometry objects

I don’t know if the name overview of this section is accurate, but as I understand it, set entities in Cesium fall into two categories: Primitives are closer to the primitives, and entities are encapsulated advanced objects that are easier to use. We won’t analyze the use scenarios, but we will show you how to add materials to both sets of objects.

Use Primitive to generate flood walls

Generally speaking, Primitive is a bit more complicated to use and requires the user to initialize more objects than Entity, including appearance, geography, etc. But because of this, it is much easier to add materials to Primitive because you can directly manipulate the appearance of the geometry. First we initialize a wall geometry:

var wallInstance = new Cesium.GeometryInstance({
  geometry: Cesium.WallGeometry.fromConstantHeights({
    positions: Cesium.Cartesian3.fromDegreesArray([
      97.0.43.0.107.0.43.0.107.0.40.0.97.0.40.0.97.0.43.0,]),maximumHeight: 100000.0.vertexFormat: Cesium.MaterialAppearance.VERTEX_FORMAT,
  }),
})

viewer.scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: [wallInstance],
    appearance: new Cesium.MaterialAppearance(),
  })
)
Copy the code

The code above is only a foundation created the appearance of “wall”, instantiate the Cesium. MaterialAppearance () this class does not pass parameters obtained is shown below:

We can check the following parameters in the official document:

Here we focus on material, which is called “material”, and add the code for material:

let image = '/static/images/colors1.png'.// Select your own dynamic material image
  color = new Cesium.Color.fromCssColorString('rgba(0, 255, 255, 1)'),
  speed = 0,
  source =
  'czm_material czm_getMaterial(czm_materialInput materialInput)\n\ {\n\ czm_material material = czm_getDefaultMaterial(materialInput); \n\ vec2 st = materialInput.st; \n\ vec4 colorImage = texture2D(image, vec2(fract((st.t - speed*czm_frameNumber*0.005)), st.t)); \n\ vec4 fragColor; \n\ fragcolor. RGB = color.rgb / 1.0; \n\ fragColor = czm_gammaCorrect(fragColor); \n\ material.alpha = colorImage.a * color.a; . \ n \ material diffuse = (colorImage. RGB + color. RGB) / 2.0; \n\ material.emission = fragColor.rgb; \n\ return material; \n\ }'

let material = new Cesium.Material({
  fabric: {
    type: 'PolylinePulseLink'.uniforms: {
      color: color,
      image: image,
      speed: speed,
    },
    source: source,
  },
  translucent: function () {
    return true
  },
})

viewer.scene.primitives.add(
  new Cesium.Primitive({
    geometryInstances: [greenWallInstance],
    appearance: new Cesium.MaterialAppearance({
      material: material,
    }),
  })
)
Copy the code

Renderings of applied materials:

Texture file colros1.png:

As you can see from the code and screenshots above, the material file is quite different from the final result, and the code in the variable source may seem difficult to understand. This will be explained below, but in this section, how materials are applied.

Use Entity to generate flow lines

Refer to the process of Primitive and create a solid object. This time we will change the scene and create a flow line. Refer to the flow line texture

create(viewer) {
  // Create a ray
  var data = {
    center: {
      id: 0.lon: 114.302312702.lat: 30.598026044.size: 20.color: Cesium.Color.PURPLE,
    },
    points: [{id: 1.lon: 115.028495718.lat: 30.200814617.color: Cesium.Color.YELLOW,
        size: 15}, {id: 2.lon: 110.795000473.lat: 32.638540762.color: Cesium.Color.RED,
        size: 15}, {id: 3.lon: 111.267729446.lat: 30.698151246.color: Cesium.Color.BLUE,
        size: 15}, {id: 4.lon: 112.126643144.lat: 32.058588576.color: Cesium.Color.GREEN,
        size: 15}, {id: 5.lon: 114.885884938.lat: 30.395401912.color: Cesium.Color.BLUE,
        size: 15}, {id: 6.lon: 112.190419415.lat: 31.043949588.color: Cesium.Color.BLUE,
        size: 15}, {id: 7.lon: 113.903569642.lat: 30.93205405.color: Cesium.Color.BLUE,
        size: 15}, {id: 8.lon: 112.226648859.lat: 30.367904255.color: Cesium.Color.BLUE,
        size: 15}, {id: 9.lon: 114.86171677.lat: 30.468634833.color: Cesium.Color.BLUE,
        size: 15}, {id: 10.lon: 114.317846048.lat: 29.848946148.color: Cesium.Color.BLUE,
        size: 15}, {id: 11.lon: 113.371985426.lat: 31.70498833.color: Cesium.Color.BLUE,
        size: 15}, {id: 12.lon: 109.468884533.lat: 30.289012191.color: Cesium.Color.BLUE,
        size: 15}, {id: 13.lon: 113.414585069.lat: 30.368350431.color: Cesium.Color.SALMON,
        size: 15}, {id: 14.lon: 112.892742589.lat: 30.409306203.color: Cesium.Color.WHITE,
        size: 15}, {id: 15.lon: 113.16085371.lat: 30.667483468.color: Cesium.Color.SALMON,
        size: 15}, {id: 16.lon: 110.670643354.lat: 31.74854078.color: Cesium.Color.PINK,
        size: 15],},options: {
      name: ' '.polyline: {
        width: 2.material: [Cesium.Color.GREEN, 3000],}}},this.createFlyLines(data, viewer)
},

createFlyLines(data, viewer) {
  const center = data.center
  const cities = data.points
  const startPoint = Cesium.Cartesian3.fromDegrees(
    center.lon,
    center.lat,
    0
  )
  / / center
  viewer.entities.add({
    position: startPoint,
    point: {
      pixelSize: center.size,
      color: center.color,
    },
  })
  // Temporarily disabling events can improve performance for bulk operations
  viewer.entities.suspendEvents() / / scatter
  cities.map((city) = > {
    let material = new Cesium.PolylineTrailMaterialProperty({
      color: Cesium.Color.RED,
      duration: 3000.trailImage: '/static/images/colors1.png',})const endPoint = Cesium.Cartesian3.fromDegrees(city.lon, city.lat, 0)
    viewer.entities.add({
      position: endPoint,
      point: {
        pixelSize: city.size - 10.color: city.color,
      },
    })

    viewer.entities.add({
      polyline: {
        positions: this.generateCurve(startPoint, endPoint),
        width: 2.material: material,
      },
    })
  })
  viewer.entities.resumeEvents()
  viewer.flyTo(viewer.entities)
},

/** * generate flow curve *@param The startPoint starting point *@param The endPoint end *@returns {Array}* /
generateCurve(startPoint, endPoint) {
  let addPointCartesian = new Cesium.Cartesian3()
  Cesium.Cartesian3.add(startPoint, endPoint, addPointCartesian)
  let midPointCartesian = new Cesium.Cartesian3()
  Cesium.Cartesian3.divideByScalar(addPointCartesian, 2, midPointCartesian)
  let midPointCartographic = Cesium.Cartographic.fromCartesian(
    midPointCartesian
  )
  midPointCartographic.height =
    Cesium.Cartesian3.distance(startPoint, endPoint) / 5
  let midPoint = new Cesium.Cartesian3()
  Cesium.Ellipsoid.WGS84.cartographicToCartesian(
    midPointCartographic,
    midPoint
  )
  let spline = new Cesium.CatmullRomSpline({
    times: [0.0.0.5.1.0].points: [startPoint, midPoint, endPoint],
  })
  let curvePoints = []
  for (let i = 0, len = 200; i < len; i++) {
    curvePoints.push(spline.evaluate(i / len))
  }

  return curvePoints
},
Copy the code

It’s a lot more code, but it’s mostly point information, and it’s a lot more complicated to create curved entities. Let’s just focus on createFlyLines. You can see that the material creation part is a little bit different from Primitive. Instantiation is a custom class PolylineTrailMaterialProperty.

let material = new Cesium.PolylineTrailMaterialProperty({
  color: Cesium.Color.WHITE,
  duration: 3000.trailImage: '/static/images/colors1.png',})Copy the code

We can see from the official document that, unlike Primitive, material is not passed in as material but as MaterialProperty.

Cesium provides some MaterialProperty, which respectively represent different effects, but obviously cannot meet our diverse business needs, so we need to customize this MaterialProperty at this time.

MaterialProperty

Create PolylineTrailMaterialProperty. Js file and import the:

import PolylineTrailMaterialProperty from '@/utils/cesium/PolylineTrailMaterialProperty'
Copy the code
import * as Cesium from 'cesium/Cesium'

export class PolylineTrailMaterialProperty {
  constructor(options) {
    options = Cesium.defaultValue(options, Cesium.defaultValue.EMPTY_OBJECT)

    this._definitionChanged = new Cesium.Event()
    this._color = undefined
    this._colorSubscription = undefined
    this._time = performance.now()

    this.color = options.color
    this.duration = options.duration
    this.trailImage = options.trailImage
  }
}

Object.defineProperties(PolylineTrailMaterialProperty.prototype, {
  isConstant: {
    get: function () {
      return false}},definitionChanged: {
    get: function () {
      return this._definitionChanged
    },
  },

  color: Cesium.createPropertyDescriptor('color'),
})

PolylineTrailMaterialProperty.prototype.getType = function () {
  return 'PolylineTrail'
}

PolylineTrailMaterialProperty.prototype.getValue = function (time, result) {
  if(! Cesium.defined(result)) { result = {} } result.color = Cesium.Property.getValueOrClonedDefault(this._color,
    time,
    Cesium.Color.WHITE,
    result.color
  )
  result.image = this.trailImage
  result.time =
    ((performance.now() - this._time) % this.duration) / this.duration

  return result
}

PolylineTrailMaterialProperty.prototype.equals = function (other) {
  return (
    this === other ||
    (other instanceof PolylineTrailMaterialProperty &&
      Cesium.Property.equals(this._color, other._color))
  )
}

Cesium.Material.PolylineTrailType = 'PolylineTrail'
Cesium.Material.PolylineTrailImage = '/static/images/colors1.png'
Cesium.Material.PolylineTrailSource =
  'czm_material czm_getMaterial(czm_materialInput materialInput)\n\ {\n\ czm_material material = czm_getDefaultMaterial(materialInput); \n\ vec2 st = materialInput.st; \n\ vec4 colorImage = texture2D(image, vec2(fract(st.s - time), st.t)); \n\ material.alpha = colorImage.a * color.a; . \ n \ material diffuse = (colorImage. RGB + color. RGB) / 2.0; \n\ return material; \n\ }'

Cesium.Material._materialCache.addMaterial(Cesium.Material.PolylineTrailType, {
  fabric: {
    type: Cesium.Material.PolylineTrailType,
    uniforms: {
      color: new Cesium.Color(1.0.0.0.0.0.0.5),
      image: Cesium.Material.PolylineTrailImage,
      time: 0,},source: Cesium.Material.PolylineTrailSource,
  },
  translucent: function () {
    return true
  },
})

Cesium.PolylineTrailMaterialProperty = PolylineTrailMaterialProperty
Copy the code

When defining your MaterialProperty, you need to set several common methods: getValue, isConstant, definitionChanged, and equals

  1. GetValue: Used to get the value of a specific property at a point in time, including two arguments: type and result, which are used to pass the point in time and store the value of the property, respectively.
  2. IsConstant: indicates whether the property changes over time. It is a bool. Cesium will use this variable to determine whether the value of this attribute needs to be obtained in every frame of the scene update to update objects in the 3D scene. ifisConstantIf true, the value is retrieved only once, unlessdefinitionChangedThe event is triggered. Procedure
  3. DefinitionChanged: An event that listens for changes to the Property itself, such as numeric changes.
  4. Equals: Checks whether attribute values are equal.

For details, see Cesium custom materials

The actual effect is shown below:

How is the material “refined”

Fabric

The code prior to MaterialProperty is mainly about building arcs and then adding dynamic effects to the arcs. It all depends on how the MaterialProperty class works. As you can see from the Material added for Primitive above, They all contain a Fabric object, which literally translates to “fabric,” meaning it’s a layer of clothing that covers the model. It has color and image, and if you want this effect to work, you need a time variable, like speed in the floodlight wall example, “Time” in the flow line example. Cesium’s official documentation defines it as a JSON object used to generate materials.

Shader for WebGL

So how do we turn a few variables we defined into imaginary materials? Here are the two most obscure strings in the code above, which is a separate syntax from WebGL technology called shader, and it’s a step into graphics that I’m interested in but not ready to go into just yet, You can take a look at this article [Cesium Yandu preliminary] fabric material definition and custom shader practice, more easy to understand, there are examples.

From my humble understanding, WebGL as an HTML5 API provides us with a better interaction with the graphics part of the client hardware for better visual effects.

A brief description of dynamic wall and flow line shader

If you don’t want to go through a long article or want to implement the effect directly, you can refer to the above code. If you just want to fine-tune the effect, you don’t need to learn how to use the shader. We mentioned the time variable in the Fabric section, but the floodlight wall example seems to have nothing to do with time. In fact, this is also a dynamic material. Setting speed to a positive number will give the wall the effect of moving up and down.

If you look at the parameters, they all appear in the shader:

uniforms: {
  color: color,
  image: image,
  speed: speed,
},
Copy the code

It’s just that shader has its own unique way of parsing the graphics and parameters that we pass in. For example, here you can change the speed per unit of the dynamic image by changing the fragColor section below to change the color processing. In the original example here is actually a mixture of incoming color and a basic color, here I changed except 1, what color is to use what color, but also consider mixed with pictures of material, in addition, the shader use floating-point Numbers are basically, here want to use “1.0”, for example, with a “1” would be an error. The shader for flow lines is similar, but it’s not that different, because I’m only using two simple examples here.

Easy to use complex effects: EarthSDK

If you don’t want to work on WebGL and don’t want to pay 50 cents for special effects, is there a way? It’s not impossible. When it comes to vertical areas, you need a dedicated developer to provide a development kit. Here I recommend EarthSDK to briefly show you how it works:

For reasons of image size, here to limit the number of frames, the actual effect is better, used here is a test of SHP file, according to the coordinate draw shapes, then according to the height the height of the lift model, after just application effect of the SDK, post configuration, a portion of the url to be the agent, here is my own deployment of SHP file:

{
  ref: 'tileset1'.czmObject: {
    xbsjType: 'Tileset'.xbsjGuid: '739ae1b9-bf61-455a-89d4-280668ed6f3c'.name: 'White model Test 1'.url: '/tileset/tileset.json'.xbsjStyle: 'var style = {color: "vec4(0, 0.5, 1.0,1)"\n}'.xbsjClippingPlanes: {},
    xbsjCustomShader: {
      fsBody: this.fsBody,
    },
  },
},
{
  ref: 'scaneline'.czmObject: {
    xbsjType: 'Scanline'.xbsjGuid: 'c827bdc1-c14b-4452-81de-7b2ce427b786'.name: 'Scanline'.position: [2.1201528021066163.0.5451706303633767.0].show: true.radius: 1000.timeDuration: '3'.currentTime: 0.color: [0.5.0.8.1.0.1.0],}},Copy the code

It’s not that difficult to configure. See the documentation on EarthSDK’s website for details

Another product of their family, CesiumLab, is also very practical in GIS development.

The resources

Flow line texture

Cesium dynamic solid wall effect

Cesium custom material

Fabric material definition and custom shader practice

In addition to the above, there are some good references

Cesium Reference resources

Cesium Official Tutorial 12– Fabric

【 3D GIS visualization 】 Realizing smart city based on Vue+Cesium+Supermap (iv