Arcgisjs结合threejs做动态线图层

Arcgis js 三维动态管线图层

在Arcgis js官方示例中,有基于BaseLayerViewGL2D写的动态线图层案例。(案例地址
该案例仅在mapview下有效,sceneview就不显示了,下面将结合threejs对该案例进行修改,实现sceneview下的三维动态线图层。
三维动态线

1、引入相关模块

我们会使用到threejsexternalRenderers等一些其他的arcgis模块,所以先引入他们。

 <script type="module">
import * as THREE from './three.js';<!-- 在开头引入three.js文件 -->
require([
      ...
      //在arcgis require中引入这些模块
      "esri/views/layers/LayerView",
      "esri/views/3d/externalRenderers",
      "esri/geometry/geometryEngine",
      "esri/geometry/projection",
      "esri/geometry/Polyline",
      "esri/views/SceneView"
    ], (
      ...
      LayerView,
      externalRenderers,
      geometryEngine,
      projection,
      Polyline,
      SceneView
    )
</script>

2、创建CustomLayerView3D

CustomLayerView3D我们会继承layerview,使他得以利用layerview的生命周期去做一些创建销毁。
同时我们会利用externalRenderers,这是sceneview叠加我们自定义的webgl内容的关键。
先将原有案例页面的代码复制到你的编译器中,创建CustomLayerView3D ,我们会需要重写如下这些方法

const CustomLayerView3D = LayerView.createSubclass({
	view: null,
    layer: null,
	constructor({ view, layer }) {
      this.view = view;
      this.layer = layer;
    },
    destroy() {},
    setup(context) {},
    render(context) {},
})

修改CustomLayer.createLayerView引入CustomLayerView3D


...
const CustomLayer = GraphicsLayer.createSubclass({
        createLayerView: function (view) {
          if (view.type === "2d") {
            return new CustomLayerView2D({
              view: view,
              layer: this
            });
          } else {
            const layerView = new CustomLayerView3D({
              view: view,
              layer: this
            });
            externalRenderers.add(view, layerView)
            return layerView;
          }
        }
      });

修改mapview为sceneview

const view = new SceneView({
  container: "viewDiv",
  map: map,
  center: [-74.006, 40.7128],
  zoom: 15
});

3、编写threejs代码

编写setup方法,创建threejs scene,并创建线对象

setup(context) {
  this.renderer = new THREE.WebGLRenderer({
    context: context.gl, // 可用于将渲染器附加到已有的渲染环境(RenderingContext)中
    premultipliedAlpha: false, // renderer是否假设颜色有 premultiplied alpha. 默认为true
  });
  this.renderer.setPixelRatio(window.devicePixelRatio); // 设置设备像素比。通常用于避免HiDPI设备上绘图模糊
  this.renderer.setViewport(0, 0, this.view.width, this.view.height); // 视口大小设置
  this.renderer.autoClearDepth = false; // 定义renderer是否清除深度缓存
  this.renderer.autoClearStencil = false; // 定义renderer是否清除模板缓存
  this.renderer.autoClearColor = false; // 定义renderer是否清除颜色缓存
  this.scene = new THREE.Scene(); // 场景
  this.camera = new THREE.PerspectiveCamera(this.view.camera.fov, this.view.canvas.width / this.view.canvas.height); // 相机
  this.u_current_time = {
    value: 0.0
  }
  this.updatePositions();
  context.resetWebGLState();
},

在updatePositions方法中遍历图层graphics生成three.Mesh对象,我们使用TubeGeometry来做三维线。为了使线条动起来,我们使用自定义的shader材质,传入时间参数u_current_time,并在每次渲染时改变它。

updatePositions() {
  this.removeAllLine()
  const graphics = this.layer.graphics;
  for (let i = 0; i < graphics.items.length; ++i) {
    const graphic = graphics.items[i];
    const path = graphic.geometry.paths[0];
    const distance = this.getLength(new Polyline({
      paths: [path],
      spatialReference: graphic.geometry.spatialReference
    }))
    const color = this.getVec4Color(graphic.attributes["color"]);
	//在原来的片段着色器上改改重新实现动态效果
    const fragmentSource = `
        precision highp float;
        varying vec2 vUv;
        uniform float u_current_time;
        uniform vec4 v_color;
        uniform float v_distance;
        const float TRAIL_SPEED = 50.0;
        const float TRAIL_LENGTH = 300.0;
        const float TRAIL_CYCLE = 1000.0;

        void main(void) {
          float speed = TRAIL_SPEED/v_distance;
          float len = TRAIL_LENGTH/v_distance;
          float cycle = TRAIL_CYCLE/v_distance;
          
          float d = mod(vUv.x - u_current_time * speed, cycle);
          float a1 = d < len ? mix(0.0, 1.0, d / len) : 0.0;
          float a2 = exp(-abs((vUv.y-0.5)*2.0) * 3.0);
          float a = a1 * a2;
          gl_FragColor = v_color * a;
        }`;
    const material = new THREE.ShaderMaterial({
      transparent: true,
      depthTest: false,
      uniforms:
      {
        u_current_time: this.u_current_time,
        v_distance: {
          value: distance
        },
        v_color: {
          value: new THREE.Vector4(...color)
        },
      },
      vertexShader: `
        varying vec2 vUv;
        void main() {
          vUv = uv;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }`,
      fragmentShader: fragmentSource
    });
    const points = this.toRenderCoordinates(path.map(p => [...p, 20])).vector3List
    let curve = new THREE.CatmullRomCurve3(points)
    //多边形面数越多越吃内存,暂未优化的情况下,加载案例中的所有数据需要2G内存……
    const geometry = new THREE.TubeGeometry(curve, points.length * 5, 20, 12, false)
    const line = new THREE.Mesh(geometry, material);
    line.castShadow = true;
    line.receiveShadow = false;
    this.scene.add(line);
    this.catchLines.push(line);
  }
},
removeAllLine() {
  this.catchLines.forEach(line => {
    this.scene.remove(line)
  });
  this.catchLines = []
},
getLength(geom) {
  let newGeom = geom
  if (geom.spatialReference.isGeographic) {
    newGeom = projection.project(geom, {
      wkid: 3857
    })
  }
  return geometryEngine.planarLength(newGeom, "meters")
},
getVec4Color(color) {
  return [
    color[0] / 255,
    color[1] / 255,
    color[2] / 255,
    1
  ]
},
toRenderCoordinates(points) {
  if (!points || !Array.isArray(points) || points.length === 0) {
    return {
      vector3List: [],
      points: []
    }
  }
  let vector3List; // 顶点数组
  let renderCoordinates = new Array(points.length * 3);
  let geographicCoordinates = []
  points.forEach(point => {
    geographicCoordinates.push(...point)
  })
  externalRenderers.toRenderCoordinates(this.view, geographicCoordinates, 0,
    this.view.spatialReference, renderCoordinates, 0, points.length);
  vector3List = points.map((p, i) => {
    return new THREE.Vector3(renderCoordinates[i * 3], renderCoordinates[i * 3 + 1], renderCoordinates[i * 3 + 2])
  })
  
  return {
    vector3List,
    points
  }
}

编写render方法,同步相机,修改动态参数

render(context) {
  if (!this.layer.visible) {
    return;
  }
  // 更新相机参数
  const cam = context.camera;
  this.camera.position.set(cam.eye[0], cam.eye[1], cam.eye[2]);
  this.camera.up.set(cam.up[0], cam.up[1], cam.up[2]);
  this.camera.lookAt(
    new THREE.Vector3(cam.center[0], cam.center[1], cam.center[2])
  );

  // 投影矩阵可以直接复制
  this.camera.projectionMatrix.fromArray(cam.projectionMatrix);
  this.renderer.state.reset();
  context.bindRenderTarget()
  this.u_current_time.value = performance.now() / 1000.0
  this.renderer.render(this.scene, this.camera);

  externalRenderers.requestRender(this.view);

  context.resetWebGLState();
},

4、补全constructor和destroy

constructor中增加projection.load以便后续使用

constructor({ view, layer }) {
  this.view =view;
  this.layer = layer;
  projection.load();
},

destroy编写清除代码

destroy() {
  this.handles.destroy()
  this.clearScene()//清理threejs
  externalRenderers.remove(this.view, this)//从externalRenderers中删除自身
  this.view.layerViews.remove(this)//从view.laerviews中删除自身
},
clearScene() {
  if (this.scene) {
    this.scene.traverse((child) => {
      if (child.material) {
        child.material.dispose();
      }
      if (child.geometry) {
        child.geometry.dispose();
      }
      child = null;
    });
    this.renderer.forceContextLoss();
    this.renderer.dispose();
    this.scene.clear();
    this.catchLines = [];
    this.scene = null;
    this.camera = null;
    this.renderer.domElement = null;
    this.renderer = null;
  }
},

完成——

示例数据四百多条线,每条几十到几百个坐标点,创建tube后占用内存超过2个G,后续需找方法优化。电脑性能不好的朋友最好不要把数据全加进来,适当减少一些数据量,不然页面可能报内存不足而崩溃。

5、优化

思想:利用自定义 attribute,复用Material对象。
修改constructor方法,在对象初始化时创建公共Material对象

constructor({ view, layer }) {
  this.view = view;
  this.layer = layer;
  this.material = this.initMaterial()
  projection.load();
},
initMaterial() {
  this.u_current_time = {
    value: 0.0
  }
  const vertexShader = `
        attribute vec4 v_color;
        attribute float v_distance;
        varying vec2 vUv;
        varying vec4 vcolor;
        varying float vdistance;
        void main() {
          vUv = uv;
          vcolor = v_color;
          vdistance = v_distance;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }`
  const fragmentSource = `
        precision highp float;
        varying vec2 vUv;
        varying vec4 vcolor;
        varying float vdistance;
        uniform float u_current_time;
        const float TRAIL_SPEED = 50.0;
        const float TRAIL_LENGTH = 300.0;
        const float TRAIL_CYCLE = 1000.0;

        void main(void) {
          float speed = TRAIL_SPEED/vdistance;
          float len = TRAIL_LENGTH/vdistance;
          float cycle = TRAIL_CYCLE/vdistance;
          
          float d = mod(vUv.x - u_current_time * speed, cycle);
          float a1 = d < len ? mix(0.0, 1.0, d / len) : 0.0;
          float a2 = exp(-abs((vUv.y-0.5)*2.0) * 3.0);
          float a = a1 * a2;
          gl_FragColor = vcolor * a;
        }`;
  return new THREE.ShaderMaterial({
    transparent: true,
    depthTest: false,
    uniforms:
    {
      u_current_time: this.u_current_time,
    },
    vertexShader: vertexShader,
    fragmentShader: fragmentSource
  });
},

修改updatePositions,给geometry写入attribute

updatePositions(){
	...
	const geometry = new THREE.TubeGeometry(curve, points.length * 5, 20, 12, false)
	const colors = new Float32Array(geometry.attributes.position.count * 4)
	const distances = new Float32Array(geometry.attributes.position.count)
	for (let i = 0; i < geometry.attributes.position.count; i++) {
	  colors[i * 4] = color[0]
	  colors[i * 4 + 1] = color[1]
	  colors[i * 4 + 2] = color[2]
	  colors[i * 4 + 3] = color[3]
	  distances[i] = distance
	}
	geometry.setAttribute('v_color', new THREE.Float32BufferAttribute(colors, 4))
	geometry.setAttribute('v_distance', new THREE.Float32BufferAttribute(distances, 1))
	const line = new THREE.Mesh(geometry, this.material)//改成了使用公共material
	...
}

优化后可加载案例所有数据,内存占用不超过100M。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值