文章目录

  • 前言
  • 为什么使用threebox
  • 代码示例
  • 总结

前言

最近在研究threejs和mapbox的结合,花了一天多的时间,结合threebox这个mapbox的三维库,给mapbox中创建自定义图层,添加自定义几何体,基于react-hooks实现,代码不多,但是threebox官网的例子给的很少,所以不少东西还是需要自己摸索下,特此记录下来。

参考:threebox.js


为什么使用threebox

mapbox官网有使用threejs的示例,但是由于threejs使用的是右手坐标系,而mapbox作为一个时空数据的渲染库,默认使用EPSG4326坐标系,参考官网mapbox-gl中创建threejs场景代码如下。

    // configuration of the custom layer for a 3D model per the CustomLayerInterfaceconst customLayer = {id: '3d-model',type: 'custom',renderingMode: '3d',onAdd: function (map, gl) {this.camera = new THREE.Camera();this.scene = new THREE.Scene();// create two three.js lights to illuminate the modelconst directionalLight = new THREE.DirectionalLight(0xffffff);directionalLight.position.set(0, -70, 100).normalize();this.scene.add(directionalLight);const directionalLight2 = new THREE.DirectionalLight(0xffffff);directionalLight2.position.set(0, 70, 100).normalize();this.scene.add(directionalLight2);// use the three.js GLTF loader to add the 3D model to the three.js sceneconst loader = new THREE.GLTFLoader();loader.load('https://docs.mapbox.com/mapbox-gl-js/assets/34M_17/34M_17.gltf',(gltf) => {this.scene.add(gltf.scene);});this.map = map;// use the Mapbox GL JS map canvas for three.jsthis.renderer = new THREE.WebGLRenderer({canvas: map.getCanvas(),context: gl,antialias: true});this.renderer.autoClear = false;},render: function (gl, matrix) {const rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0),modelTransform.rotateX);const rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0),modelTransform.rotateY);const rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1),modelTransform.rotateZ);const m = new THREE.Matrix4().fromArray(matrix);const l = new THREE.Matrix4().makeTranslation(modelTransform.translateX,modelTransform.translateY,modelTransform.translateZ).scale(new THREE.Vector3(modelTransform.scale,-modelTransform.scale,modelTransform.scale)).multiply(rotationX).multiply(rotationY).multiply(rotationZ);this.camera.projectionMatrix = m.multiply(l);this.renderer.resetState();this.renderer.render(this.scene, this.camera);this.map.triggerRepaint();}};

mapbox-gl中使用threejs场景需要创建type为custom的layer,onAdd函数只触发一次,用来初始化threejs场景,render在地图缩放、移动、旋转时都会触发,重新更新模型和相机的位置,如果涉及动画,代码量会更多。threebox提供方便的方法来管理地理坐标,以及同步地图和场景相机

代码示例

  • 添加threebox封装的球
  • 使用threejs添加一个立方体,并在它的上面添加一个上下浮动的圆锥
  • 基于着色器添加一个黄色的半球光罩,并由下至上渐透明

效果:

完整代码:

import React, { useRef, useEffect, useState } from 'react';
import mapboxgl from 'mapbox-gl';
import MapboxLanguage from '@mapbox/mapbox-gl-language';
import { Threebox, THREE } from 'threebox-plugin';
import 'antd/dist/antd.css';function App() {const mapContainerRef = useRef();const sphereMesh = useRef();const coneMesh = useRef();const lightRingMesh = useRef();const blueMaterial = new THREE.MeshPhongMaterial({color: '#4791ff',side: THREE.DoubleSide});const redMaterial = new THREE.MeshPhongMaterial({color: '#f73131',side: THREE.DoubleSide});const shaderMaterial = new THREE.ShaderMaterial({vertexShader: `varying vec3 vPosition;void main(){vec4 viewPosition = viewMatrix * modelMatrix *vec4(position,1);gl_Position = projectionMatrix * viewPosition;vPosition = position;}`,fragmentShader: `varying vec3 vPosition;uniform float uHeight;void main(){float gradMix = (vPosition.z+uHeight/2.0)/uHeight;gl_FragColor = vec4(1.0,1.0,0,1.0-gradMix);}`,transparent: true,side: THREE.DoubleSide,})const mapRef = useRef();let step = 200;let cylinderRadius = 0// 初始化基础图层useEffect(() => {mapboxgl.accessToken = 'your token'mapRef.current = new mapboxgl.Map({zoom: 14,center: [116.5, 39.9],pitch: 90,style: 'mapbox://styles/mapbox/streets-v11',container: mapContainerRef.current,antialias: true,},);mapRef.current.addControl(new MapboxLanguage({ defaultLanguage: "zh-Hans" }))mapRef.current.on('load', (e) => {//地图加载完后,才能进行添加图层//console.log('load happened', mapRef.current.style);mapRef.current.addLayer({id: 'custom_layer',type: 'custom',renderingMode: '3d',onAdd: function (map, mbxContext) {window.tb = new Threebox(map,mbxContext,{defaultLights: true,// enableSelectingFeatures: true, //change this to false to disable fill-extrusion features selection// enableSelectingObjects: true, //change this to false to disable 3D objects selection// enableDraggingObjects: true, //change this to false to disable 3D objects drag & move once selected// enableRotatingObjects: true, //change this to false to disable 3D objects rotation once selected// enableTooltips: true});// 示例一,threebox封装的小球sphereMesh.current = window.tb.sphere({ radius: 5, color: 'green', material: 'MeshStandardMaterial', anchor:'center' }).setCoords([116.48, 39.9, 200]);window.tb.add(sphereMesh.current);//示例二 圆锥const coneFeometry = new THREE.ConeGeometry(50, 50, 64);coneMesh.current = new THREE.Mesh(coneFeometry, redMaterial);// coneMesh.current.translateZ(50)coneMesh.current = window.tb.Object3D({ obj: coneMesh.current , units: 'meters', bbox: false, anchor:'center' }).setCoords([116.49, 39.9, 400]);coneMesh.current.rotation.x = -0.5 * Math.PI;window.tb.add(coneMesh.current); //示例三 立方体const geometry = new THREE.BoxGeometry(150, 150, 300);let cube = new THREE.Mesh(geometry, blueMaterial);cube = window.tb.Object3D({ obj: cube, units: 'meters', bbox: false, anchor:'center' }).setCoords([116.49, 39.9, 0]);window.tb.add(cube);  // 示例三 半球光罩 let cylinderGeom = new THREE.SphereBufferGeometry(10,32,32, 0,Math.PI)lightRingMesh.current  = new THREE.Mesh(cylinderGeom, shaderMaterial);lightRingMesh.current.rotation.x = -0.5 * Math.PI;lightRingMesh.current.geometry.computeBoundingBox();const { min, max } = lightRingMesh.current.geometry.boundingBox;//  设置物体高差let uHeight = max.y - min.y;shaderMaterial.uniforms.uHeight = {value: uHeight,};lightRingMesh.current = window.tb.Object3D({ obj: lightRingMesh.current, bbox: true, anchor:'center' }).setCoords([116.5, 39.9, 0])window.tb.add(lightRingMesh.current);// 执行动画animate()},// 地图更新时触发(拖拽、移动、缩放)render: function (gl, matrix) {window.tb.update();}})});}, []);function animate() {// console.log(lightRingMesh.current)requestAnimationFrame(() => { animate() });step += 0.03const z = Math.abs(50 * Math.cos(step)) + 400coneMesh.current.setCoords([116.49, 39.9, z]);cylinderRadius += 0.01;// 当半径大于1时,重新开始if (cylinderRadius > 1) {cylinderRadius = 0;}// console.log(lightRingMesh.current)}}return (<div style={{ display: 'flex' }}><divid="map-container"ref={mapContainerRef}style={{ height: '100vh', width: '100vw' }}/><div style={{ position: 'fixed', top: '0', right: '0' }}><button onClick={() => { sphereMesh.current.visible = !sphereMesh.current.visible }} style={{ marginRight: '10px' }}>修改显隐</button></div></div>);
}export default App;

总结

  • mapbox-gl :custom-layer
  • threejs
  • threebox

mapbox-gl结合threejs相关推荐

  1. GIS开发:推荐Mapbox gl解决方案

    在二维地图的开发中,实现类似于百度.高德地图那样加载简体的模型,使用mapbox gl是一个比较好的解决方案. https://docs.mapbox.com/mapbox-gl-js/api/ 类似 ...

  2. Mapbox gl tile瓦片渲染点以及图片Icon

    Mapbox gl tile瓦片渲染点以及图片Icon 1. 效果图 2. 源码 参考 1. 效果图 点效果图如下: 以图标渲染效果图如下: 注意图片要不能跨域,需要下载的下来才能正常展示. 2. 源 ...

  3. 初识mapbox GL

    一.概述 最近由于项目的需求,借此机会对mapbox GL做了一个系统的学习,同时也对整个学习过程做一个记录,一方面留作自用,另一方面也希望看到此文的人在学习mapbox GL的时候,能够有所启发.有 ...

  4. 进阶mapbox GL之paint和filter

    概述 通过前面的文章初识mapbox GL我们对mapbox GL有了一个相对比较全面的认识,本节结合一些示例,重点讲述一下mapbox GL里面的filter和paint的用法. 说明 本文中的示例 ...

  5. Mapbox GL JS 表达式概述

    表达式(expressions)是Mapbox GL JS的一个高级功能,它为数据的添加和渲染展示提供了更多的灵活性. 表达式的功能包括: 数据驱动样式:根据一个或多个数据属性指定样式规则. 算术:对 ...

  6. Mapbox GL JS 地图英文转中文的解决办法

    Mapbox GL JS地图框架的使用者已经越来越多了.不仅仅是因为它支持矢量瓦片,它里面内置了一些非常精美的地图,并且支持自定义搭配地图.对于个人开发者而言,一般都不具备自己搭建地图服务的硬件资源, ...

  7. Mapbox GL JS介绍及使用

    Mapbox GL JS介绍及使用:(以web端基本交互实现为例) Mapbox GL JS 是一个 JavaScript 库,它使用 WebGL,以 vector tiles 和 Mapbox st ...

  8. GIS开发:mapbox gl几种底图的加载

    mapbox gl除了加载客户端可控制样式的底图,也能够加载常规的影像底图. 这里指发布的影像或者矢量地图切片,在线经常使用的有天地图,谷歌地图.高德百度等,底图的加载,mapbox gl中,主要是修 ...

  9. Mapbox GL可视化之热力图

    本篇使用Mapbox GL JS实现数据的热力图可视化,以截止到2020-03-01日的新冠疫情作为示例数据. 一 什么是热力图 热力图使用颜色的深浅表示数值的变化,从而表示数据的分布情况,这使得用户 ...

  10. 喜大普奔——Mapbox GL JS支持多种投影了

    1. 写在前面 Mapbox GL JS 在v2.6.0之前不支持投影,默认地图是Mercator投影方式,如果项目中使用其他类型的投影(比如加载经纬度直投4326的地图切片),必须通过修改源码的方式 ...

最新文章

  1. 施一公:西湖大学评审终身副教授不看文章数,而是看你能不能讲好一个故事...
  2. ListView 异步更新出现问题的解决(Handler)
  3. 【转】3D图形引擎(DX9): FX
  4. 测试点解析:1049 数列的片段和_12行代码AC
  5. H264视频通过RTMP直播
  6. python自动化测试脚本可以测php吗_请对比分析一下php的自动化测试与python的自动化测试...
  7. jaxb java xml序列化_XML编程总结(六)——使用JAXB进行java对象和xml格式之间的相互转换...
  8. Scrapy框架实现爬虫
  9. 横向对比5大开源语音识别工具包,CMU Sphinx最佳
  10. FastReport添加Sqlite数据源
  11. STM32F412 can测试问题汇总
  12. 理论力学知识要点归纳(三)
  13. win10通过开启teredo访问ipv6
  14. python原生是什么意思_什么是 云原生?
  15. 查找算法--Java实例/原理
  16. android线程池!如何试出一个Android开发者真正的水平?内容太过真实
  17. Dell服务器启动显示Entering System Servvice To cancel.
  18. 两点经纬度计算方位角,以正北为0度
  19. 爱奇艺《大魔术师》海内外备受关注
  20. vue 八大生命周期

热门文章

  1. 此Flash Player与您的地区不相容(完美解决)
  2. Mysql成语数据库_mysql学习笔记
  3. IDataSet接口的使用和矢量图层复制
  4. e book website
  5. clearfix的运行机制和进化
  6. 彻底关闭Windows10更新 以及使用搜狗输入法
  7. 2023深度分析中国软件质量存在的问题
  8. 天玑1000+和骁龙865哪个好-天玑1000+和骁龙865跑分对比
  9. php 数组字符串输出,php遍历数组输出成字符串例子
  10. python程序流程控制练习题