赞
踩
Threejs中文网具体地址
npm 中 Three的下载路径
从这个里面我知道 如果需要成功加载一个3D图形,我需要 5步走
//引入 import * as THREE from 'three'; const width = window.innerWidth, height = window.innerHeight; // 1.创建一个场景 const scene = new THREE.Scene(); // 2.创建一个相机 PerspectiveCamera(透视摄像机)。 const camera = new THREE.PerspectiveCamera( 70, width / height, 0.01, 10 ); camera.position.z = 1; //向z轴偏移 // 3.创建渲染器 const renderer = new THREE.WebGLRenderer( { antialias: true } ); renderer.setSize( width, height ); //渲染多大 要尺寸 //4.创建一个物体 构造器 Mesh 由 几何体+材质构成 const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 ); //BoxGeometry 立方缓冲几何体 const material = new THREE.MeshNormalMaterial(); //材质 基础网格材质 const mesh = new THREE.Mesh( geometry, material ); // 构造器 scene.add( mesh ); //5.渲染场景 renderer.setAnimationLoop( animation ); document.body.appendChild( renderer.domElement ); //添加到页面中 // animation function animation( time ) { mesh.rotation.x = time / 2000; mesh.rotation.y = time / 1000; renderer.render( scene, camera ); }
这里地图的 几何体我们该用什么? 地图的材质要用什么? 地图的数据该从哪里来?如何才能展现出来?
地图的几何体用 :
挤压缓冲几何体(ExtrudeGeometry) 参考示例
基础线条材质(LineBasicMaterial) 参考示例 用来单独描线的 也可以不用 ,根据需求来
地图的材质用:
基础网格材质(MeshBasicMaterial) 介绍
示例:
/* ExtrudeGeometry(shapes : Array, options : Object) shapes — 形状或者一个包含形状的数组。 options — 一个包含有下列参数的对象: curveSegments — int,曲线上点的数量,默认值是12。 steps — int,用于沿着挤出样条的深度细分的点的数量,默认值为1。 depth — float,挤出的形状的深度,默认值为1。 bevelEnabled — bool,对挤出的形状应用是否斜角,默认值为true。 bevelThickness — float,设置原始形状上斜角的厚度。默认值为0.2。 bevelSize — float。斜角与原始形状轮廓之间的延伸距离,默认值为bevelThickness-0.1。 bevelOffset — float. Distance from the shape outline that the bevel starts. Default is 0. bevelSegments — int。斜角的分段层数,默认值为3。 extrudePath — THREE.Curve对象。一条沿着被挤出形状的三维样条线。Bevels not supported for path extrusion. UVGenerator — Object。提供了UV生成器函数的对象。 该对象将一个二维形状挤出为一个三维几何体。 当使用这个几何体创建Mesh的时候,如果你希望分别对它的表面和它挤出的侧面使用单独的材质,你可以使用一个材质数组。 第一个材质将用于其表面;第二个材质则将用于其挤压出的侧面。 属性 */ const length = 12, width = 8; const shape = new THREE.Shape(); //形状 shape.moveTo( 0,0 ); shape.lineTo( 0, width ); shape.lineTo( length, width ); shape.lineTo( length, 0 ); shape.lineTo( 0, 0 ); const extrudeSettings = { steps: 2, depth: 16, bevelEnabled: true, bevelThickness: 1, bevelSize: 1, bevelOffset: 0, bevelSegments: 1 }; const geometry = new THREE.ExtrudeGeometry( shape, extrudeSettings ); const material = new THREE.MeshBasicMaterial( { color: 0x00ff00 } ); const mesh = new THREE.Mesh( geometry, material ) ; scene.add( mesh );
由此 我们知道了,地图的是通过这个 shape 描绘成形状展示的,数据可以通过 datav.aliyun 地图json小工具 获取到
数据拿到之后,就是展示的问题,直接展示是不行的,需要通过 d3 对数据处理,才能按照正确的地图样子展示
import * as d3 from "d3"; //莫开托坐标 矫正地图坐标
//center 的位置可以自己定
const handleProj = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0]) // d3投影转换函数
接下来就是封装地图信息 ,使它变成这个样子 (这里用到了)
const shape = new THREE.Shape(); //形状
shape.moveTo( 0,0 );
shape.lineTo( 0, width );
shape.lineTo( length, width );
shape.lineTo( length, 0 );
shape.lineTo( 0, 0 );
/* '/src/assets/map/map.json' 是在src目录下自己创建的, map.json 是通过 datav这个地图小工具下载的 */ import * as d3 from "d3"; //莫开托坐标 矫正地图坐标 import map from '../assets/map/map.json' const handleProj = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0]) // d3投影转换函数 const mapContainer = new THREE.Object3D() // 存储地图Object3D对象 // 处理地图数据 GeoJson data const handleData = (jsonData) => { const feaureList = jsonData.features; feaureList.forEach((feature) => { // 每个feature都代表一个省份 const province = new THREE.Object3D; province.properties = feature.properties.name // 省份名称 province.name = feature.properties.name // 省份名称 mapContainer.name = feature.properties.name // 省份名称 const coordinates = feature.geometry.coordinates // 省份坐标信息 // 处理的原因可以自己打印map.json 看 if (feature.geometry.type === 'MultiPolygon') { coordinates.forEach((coord) => { coord.forEach((coordinate) => { // 三维多边形 const extrudeMesh = creatDepthPolygon(coordinate) extrudeMesh.properties = feature.properties.name // 线条 const line = createLine(coordinate); province.add(extrudeMesh) province.add(line) }) }) } if (feature.geometry.type === 'Polygon') { coordinates.forEach((coordinate) => { // 三维多边形 const extrudeMesh = creatDepthPolygon(coordinate) extrudeMesh.properties = feature.properties.name // 线条 const line = createLine(coordinate); province.add(extrudeMesh) province.add(line) }) } mapContainer.add(province) }) scene.add(mapContainer) } // 创建三维多边形 const creatDepthPolygon = (coordinate) => { const shape = new THREE.Shape(); coordinate.forEach((item, index) => { // 每一个item都是MultiPolygon中的一个polygon const [x_XYZ, y_XYZ] = handleProj(item) if (index === 0) { shape.moveTo(x_XYZ, -y_XYZ) } else { shape.lineTo(x_XYZ, -y_XYZ) } }) const extrudeSettings = { steps: 2, depth: 16, bevelEnabled: true, bevelThickness: 1, bevelSize: 1, bevelOffset: 0, bevelSegments: 1 }; const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings) //挤压缓冲几何体 const material = new THREE.MeshBasicMaterial({ // color: new THREE.Color(Math.random() * 0xffffff), // 每个省随机赋色 color: '#d13a34', transparent: true, opacity: 0.6 }) return new THREE.Mesh(geometry, material) } // 创建线条 const createLine = (coordinate) => { const material = new THREE.LineBasicMaterial({ color: '#ffffff' }); const points = [] coordinate.forEach((item, index) => { // 每一个item都是MultiPolygon中的一个polygon const [x_XYZ, y_XYZ] = handleProj(item) points.push(new THREE.Vector3(x_XYZ, -y_XYZ, 25)) }) const geometry = new THREE.BufferGeometry().setFromPoints(points); return new THREE.Line(geometry, material); } //调用 handleData(map)
以上到这里 ,一个不能动的地图出现了!
tip: 如果有不展示的 可以更改相机的缩放,d3投影的缩放,加点环境光(下面都是我修改和添加了的)
// 这里的 都是修改过的 const handleProj = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0]) // d3投影转换函数 const mapContainer = new THREE.Object3D() // 存储地图Object3D对象 // 创建相机 const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000); camera.position.z = 1000; // 创建3D场景对象Scene const scene = new THREE.Scene(); scene.background = new THREE.Color('#ffffff'); // 将背景颜色设置为白色 // 初始化环境光 const initLight = () => { const ambLight = new THREE.AmbientLight('#ffffff', 0.3) // 基本光源 const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯 spotLight.position.set(40, 200, 10); spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用 scene.add(ambLight, spotLight); // 向场景中添加光源 }
设置相机控件轨道控制器OrbitControls 相机控件轨道控制器
注意!!!
Threejs 中的控件时需要 引入的
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 设置相机控件轨道控制器OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true //阻尼 更真实
这个是动态的地图
map地图
贴上全部代码,有些可以自行删减
<template> <div id="info"></div> </template> <script setup> import { onMounted,ref } from 'vue' import * as THREE from 'three' import * as d3 from "d3"; //莫开托坐标 矫正地图坐标 import map from '../assets/map/map.json' // 引入轨道控制器扩展库OrbitControls.js import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; // 文本缓冲几何体 import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'; // 一个用于加载JSON格式的字体的类 import { FontLoader } from 'three/addons/loaders/FontLoader.js'; const width = window.innerWidth, height = window.innerHeight; const handleProj = d3.geoMercator().center([109, 34.5]).scale(1000).translate([0, 0]) // d3投影转换函数 const mapContainer = new THREE.Object3D() // 存储地图Object3D对象 // 创建相机 const camera = new THREE.PerspectiveCamera(70, width / height, 0.01, 10000); camera.position.z = 1000; // 创建3D场景对象Scene const scene = new THREE.Scene(); scene.background = new THREE.Color('#ffffff'); // 将背景颜色设置为白色 // 初始化环境光 const initLight = () => { const ambLight = new THREE.AmbientLight('#ffffff', 0.3) // 基本光源 /** * 设置聚光灯相关的的属性,详情见P54 */ const spotLight = new THREE.SpotLight(0xFFFFFF); // 聚光灯 spotLight.position.set(40, 200, 10); spotLight.castShadow = true; // 只有该属性为true时,该点光源允许产生阴影,并且下列属性可用 scene.add(ambLight, spotLight); // 向场景中添加光源 } // 初始化渲染器 const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(width, height); // 初始化地理数据集 const initGeom = () => { // 加载中国地区的geoJson数据集 // const fileLoader = new THREE.FileLoader(); // fileLoader.load('/src/assets/map/map.json', // (data) => { // const chinaJson = JSON.parse(data) // handleData(chinaJson) // } // ) handleData(map) } // 处理地图数据 GeoJson data const handleData = (jsonData) => { const feaureList = jsonData.features; feaureList.forEach((feature) => { // 每个feature都代表一个省份 const province = new THREE.Object3D; province.properties = feature.properties.name // 省份名称 province.name = feature.properties.name // 省份名称 mapContainer.name = feature.properties.name // 省份名称 const coordinates = feature.geometry.coordinates // 省份坐标信息 if (feature.geometry.type === 'MultiPolygon') { coordinates.forEach((coord) => { coord.forEach((coordinate) => { // 三维多边形 const extrudeMesh = creatDepthPolygon(coordinate) extrudeMesh.properties = feature.properties.name // 线条 const line = createLine(coordinate); province.add(extrudeMesh) province.add(line) }) }) } if (feature.geometry.type === 'Polygon') { coordinates.forEach((coordinate) => { // 三维多边形 const extrudeMesh = creatDepthPolygon(coordinate) extrudeMesh.properties = feature.properties.name // 线条 const line = createLine(coordinate); province.add(extrudeMesh) province.add(line) }) } mapContainer.add(province) }) scene.add(mapContainer) } // 创建三维多边形 const creatDepthPolygon = (coordinate) => { const shape = new THREE.Shape(); coordinate.forEach((item, index) => { // 每一个item都是MultiPolygon中的一个polygon const [x_XYZ, y_XYZ] = handleProj(item) if (index === 0) { shape.moveTo(x_XYZ, -y_XYZ) } else { shape.lineTo(x_XYZ, -y_XYZ) } }) const extrudeSettings = { steps: 2, depth: 16, bevelEnabled: true, bevelThickness: 1, bevelSize: 1, bevelOffset: 0, bevelSegments: 1 }; const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings) //挤压缓冲几何体 const material = new THREE.MeshBasicMaterial({ // color: new THREE.Color(Math.random() * 0xffffff), // 每个省随机赋色 color: '#d13a34', transparent: true, opacity: 0.6 }) return new THREE.Mesh(geometry, material) } // 创建线条 const createLine = (coordinate) => { const material = new THREE.LineBasicMaterial({ color: '#ffffff' }); const points = [] coordinate.forEach((item, index) => { // 每一个item都是MultiPolygon中的一个polygon const [x_XYZ, y_XYZ] = handleProj(item) points.push(new THREE.Vector3(x_XYZ, -y_XYZ, 25)) }) const geometry = new THREE.BufferGeometry().setFromPoints(points); return new THREE.Line(geometry, material); } // 光线投射Raycaster const raycaster = new THREE.Raycaster(); const pointer = new THREE.Vector2(); //鼠标放上去 改变颜色 显示地区名字 let activeIntersects = []; //鼠标滑过数据 const onPointerMove = (event) => { let info = document.querySelector('#info') // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1) pointer.x = (event.clientX / window.innerWidth) * 2 - 1; pointer.y = - (event.clientY / window.innerHeight) * 2 + 1; // 通过摄像机和鼠标位置更新射线 raycaster.setFromCamera(pointer, camera); // 判断数组是否有数据,有数据全部设置为原始数据 if (activeIntersects.length) { for (let i = 0; i < activeIntersects.length; i++) { activeIntersects[i].object.material.color.set('#d13a34'); } } // 计算物体和射线的焦点 const intersects = raycaster.intersectObjects(scene.children); if (intersects.length && intersects[0].object.parent.name) { // 设置hove 弹框的宽高 info.style.left = event.clientX + 'px' info.style.top = event.clientY + 'px' info.style.display = 'block' info.innerHTML = intersects[0].object.parent.name }else{ info.style.display = 'none' } // 数组数据清空 activeIntersects = [] // 滑过的当前这个高亮 for (let i = 0; i < intersects.length; i++) { if (intersects[i].object.type === 'Mesh') { intersects[i].object.material.color.set(0xff0000); activeIntersects.push(intersects[i]) } } } window.addEventListener('pointermove', onPointerMove); // 设置相机控件轨道控制器OrbitControls const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true //阻尼 更真实 // // 辅助线 AxesHelper // const axesHelper = new THREE.AxesHelper( 500 ); // scene.add( axesHelper ); // // Three.js 中绘制标签信息 地图名称 // // 创建省份名称标签 // var loader = new FontLoader(); // loader.load('/src/assets/fonts/helvetiker_regular.typeface.json', function (font) { // const geometry = new TextGeometry('mapContainer.name ', { // font: font, // size: 80, // height: 5, // curveSegments: 12, // bevelEnabled: true, // bevelThickness: 10, // bevelSize: 8, // bevelSegments: 5 // } ); // const textMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00 }); // const textMesh = new THREE.Mesh(geometry, textMaterial); // textMesh.position.x = -1; // textMesh.position.y = 1; // scene.add(textMesh); // }); // 渲染 // 因为后期是每一帧都需要渲染,需要封装一个渲染函数 const render = () => { // 使用渲染器,通过相机 将场景渲染出来 renderer.render(scene, camera) // 渲染下一帧的时候会调用render函数 requestAnimationFrame(render) } // 4.获取dom实例 onMounted(() => { initGeom(); initLight(); render() document.body.appendChild(renderer.domElement); }) </script> <style> #info { position: absolute; background: rgba(0, 0, 0, 0.5); color: #fff; border-radius: 2px; padding: 5px 10px; display: none; width: auto; /* 设置宽度自适应 */ } </style>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。