cesium polyline 自定义材质图片运动线

cesium polyline 自定义图片运动线

捣鼓了一下cesium的自定义Material,使用glsl实现自定义材质的polyline,视角缩放图片也不会变形,并支持材质沿线运动。


下面逐步讲一下怎么实现的,同时也是对cesium自定义Material的写法有个初步认识。

一、新建自定义Material

我们参考cesium源码中的任意一个MaterialProperty比如PolylineOutlineMaterial.js

Cesium 源码 git 地址 - PolylineOutlineMaterial.js

可以看到一个MaterialProperty需要实现getTypegetValueequals三个方法,照葫芦画瓢创建一下我们自己的MaterialProperty。


const defaultColor = Cesium.Color.TRANSPARENT;
const defaultImage = require('../../../assets/images/color.png');
const defaultImageimageW = 10
const defaultAnimation = false
const defaultDuration = 3000;

function ImageLineMaterial(opt) {
  opt = Cesium.defaultValue(opt, Cesium.defaultValue.EMPTY_OBJECT);
  this._definitionChanged = new Cesium.Event();
  // 定义材质变量
  this._color = undefined;
  this._colorSubscription = undefined;
  this._backgroundColor = undefined;
  this._backgroundColorSubscription = undefined;
  this._image = undefined;
  this._imageSubscription = undefined;
  this._imageW = undefined;
  this._imageWSubscription = undefined;
  this._animation = undefined;
  this._animationSubscription = undefined;
  this._duration = undefined;
  this._durationSubscription = undefined;
  // 变量初始化
  this.color = opt.color || defaultColor; //颜色
  this.backgroundColor = opt.backgroundColor || defaultColor; //颜色
  this._image = opt.image || defaultImage; //材质图片
  this.imageW = opt.imageW || defaultImageimageW
  this.animation = opt.animation || defaultAnimation
  this.duration = opt.duration || defaultDuration
  this._time = undefined;
}
// 材质类型
ImageLineMaterial.prototype.getType = function (time) {
  return "ImageLine";
};
// 这个方法在每次渲染时被调用,result的参数会传入glsl中。
ImageLineMaterial.prototype.getValue = function (time, result) {
  if (!Cesium.defined(result)) {
    result = {};
  }
  result.color = Cesium.Property.getValueOrClonedDefault(this._color, time, defaultColor, result.color);
  result.backgroundColor = Cesium.Property.getValueOrClonedDefault(this._backgroundColor, time, defaultColor, result.backgroundColor);
  result.image = this._image;
  result.imageW = this._imageW;
  result.animation = this._animation;
  if (this._time === undefined) {
    this._time = new Date().getTime();
  }
  result.time = ((new Date().getTime() - this._time)%this._duration) / this._duration;
  return result;
};

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

Object.defineProperties(ImageLineMaterial.prototype, {
  isConstant: {
    get: function get() {
      return false;
    }
  },
  definitionChanged: {
    get: function get() {
      return this._definitionChanged;
    }
  },
  color: Cesium.createPropertyDescriptor('color'),
  backgroundColor: Cesium.createPropertyDescriptor('backgroundColor'),
  image: Cesium.createPropertyDescriptor('image'),
  imageW: Cesium.createPropertyDescriptor('imageW'),
  animation: Cesium.createPropertyDescriptor('animation'),
  duration: Cesium.createPropertyDescriptor('duration'),
});

二、注册Material

当我们创建了自定义Material后,还需要将Material注册。
cesium源码中的MaterialProperty是在Material.js中统一注册,我们参考其中一个,直接在新建的自定义Material后面增加注册逻辑就可以了。
Cesium 源码 git 地址 - Material.js


Cesium.Material._materialCache.addMaterial("ImageLine", {
  fabric: {
    type: "ImageLine",
    uniforms: {
      // uniforms参数跟我们上面定义的参数以及getValue方法中返回的result对应,这里值是默认值
      color: new Cesium.Color(1, 0, 0, 1.0),
      backgroundColor: new Cesium.Color(0, 0, 0, 0.0),
      image: '',
      imageW: 1,
      animation: false,
      duration: 30,
      time: 0
    },
	// source编写glsl,可以使用uniforms参数,值来自getValue方法的result
    source: ``
  },
  translucent: function translucent() {
    return true;
  }
});
// 写到Cesium对象上,就可以像其他MaterialProperty一样使用了
Cesium.Material.ImageLineType = 'ImageLine'
Cesium.ImageLineMaterialProperty = ImageLineMaterial

三、编写glsl部分

glsl最主要的方法就是czm_getMaterial,这个方法接受一个materialInput,可用于获取渲染材质对象czm_material,我们通过修改材质对象来实现渲染自定义图片。
Cesium 源码 git 地址 - PolylineOutlineMaterial.glsl

1、使用图片渲染polyline

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    vec2 st = materialInput.st;
    float s = st.s;
    float t = st.t;
    vec4 colorImage = texture2D(image, vec2(fract(s), t));
    material.diffuse = colorImage.rgb; 
    return material;
}

materialInput中的st分别为材质的横向和纵向坐标,取值在0-1。texture2D方法用于从图片(image)中指定位置(vec2)获取像元,然后用获取到的像元rgb值赋给material.diffuse,图片就被渲染到线上了。
但是现在的图片被拉伸到了整根线上,也不会动,效果很差。

2、让图片动起来

根据上一步知道像元是怎么取的,那么只要让取的位置不停的有规律的变化,图片就动起来了。
我们利用getValue 的time来重新计算s值。

czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    vec2 st = materialInput.st;
    float s = st.s-time;//增加运动效果
    float t = st.t;
    vec4 colorImage = texture2D(image, vec2(fract(s), t));
    material.diffuse = colorImage.rgb; 
    return material;
}

3、改变图片尺寸

我们知道的图片尺寸单位是像素,需要转换成glsl单位才可保证图片不变形,这一步要使用到czm_pixelRatio这个参数,他是像素到单位尺寸的转换系数,像素*czm_pixelRatio可得到我们要的单位。(由于使用了fwidth方法,需要在glsl开头设置 #extension GL_OES_standard_derivatives : enable

#extension GL_OES_standard_derivatives : enable
czm_material czm_getMaterial(czm_materialInput materialInput)
{
    czm_material material = czm_getDefaultMaterial(materialInput);
    vec2 st = materialInput.st;
    float s = st.s/ (abs(fwidth(st.s)) * imageW * czm_pixelRatio);
    s = s-time;//增加运动效果
    float t = st.t;
    vec4 colorImage = texture2D(image, vec2(fract(s), t));
    material.diffuse = colorImage.rgb; 
    return material;
}

到这一步图片已经可以保持比例且运动起来了。

四、使用自定义Material

import 'ImageLineMaterial.js'//使用前先引用,保证自定义材质先注册好
viewer.entities.add({
  polyline:{
    positions:...,
    material: new Cesium.ImageLineMaterialProperty({
      image: require(图片路径),
	}),//使用我们自定义的材质
  }
})

后话

通过以上代码就可以创建一个可自定义图片贴图的运动线,但如果按照本文做下来会结果发现两个问题:

  1. primitive使用这个材质的话图片不会动;
  2. 如果设置贴地 clampToGround:true,图片会碎裂;

下面是解决办法:

1.如何让材质在primitive上也动起来

现有逻辑是在getValue 方法中计算time再传入着色器中,这个方法primitive不会自动调用。因此我们要修改着色器逻辑,使用czm_frameNumber这个内置变量来控制偏移量。
czm_frameNumber是从渲染完开始经过的帧数。
将涉及time的部分做一下修改:

s = s-time;//增加运动效果
改成
s = s-czm_frameNumber / 60;//每60帧循环

2.解决图片碎裂

polyline的片段着色器会自动接收到线的角度,利用角度计算应读取的图片像素位置,而不是直接使用st.s,便可以解决碎裂问题。
修改如下:

//片段着色器开头增加获取角度和计算方法
varying float v_polylineAngle;
mat2 rotate(float rad) {
  float c = cos(rad);
  float s = sin(rad);
  return mat2(
      c, s,
      -s, c
  );
}
czm_material czm_getMaterial(czm_materialInput materialInput)
{
  ...
  //修改s的获取方式
  float s = st.s/ (abs(fwidth(st.s)) * imageW * czm_pixelRatio);
  //改为
  vec2 pos = rotate(v_polylineAngle) * gl_FragCoord.xy;
  float s = pos.x / (imageW * czm_pixelRatio);
  float t = st.t;
}

由于不使用fwidth了,开头的**#extension GL_OES_standard_derivatives : enable**也可以去掉了。

你可能感兴趣的:(gis,cesium,js,前端,javascript)