最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

three.js實現(xiàn)3D地圖的項目實踐

 更新時間:2026年03月22日 11:44:30   作者:codingWhat  
three.js 是一個在瀏覽器里把 3D 畫出來的圖形庫,本文就來詳細(xì)的介紹一下three.js實現(xiàn)3D地圖的項目實踐,感興趣的可以了解一下

Three.js

three.js 是一個在瀏覽器里把 3D 畫出來的圖形庫。你可以把它想成搭舞臺,那咋把舞臺搭起來呢?

  1. Scene:搭舞臺的“背景板”(燈光、模型、網(wǎng)格)。
  2. Camera:決定你從哪個方向看(透視相機更像“人眼”視角,正交相機更像“測繪”視角)。
  3. Renderer:負(fù)責(zé)“出畫面”(每一幀把 scene + camera 渲染到畫布)。

看一眼最小骨架,后面不迷路:

import * as THREE from "three"

const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(45, width / height, 0.1, 1000)
camera.position.set(0, 0, 10)

const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(width, height)
container.appendChild(renderer.domElement)

function animate() {
  requestAnimationFrame(animate)
  renderer.render(scene, camera)
}
animate()

理解了這三件事,你再把“地區(qū)輪廓”轉(zhuǎn)換成 3D 網(wǎng)格,就等于把數(shù)據(jù)變成了可以被光照和相機看到的模型。

實現(xiàn)流程

  1. 讀取 GeoJSON:先把 Polygon 統(tǒng)一成和 MultiPolygon 一樣的數(shù)據(jù)形態(tài);
  2. 遍歷每個 polygon ring:把點序列寫進 THREE.Shape(moveTo/lineTo)
  3. 用 ExtrudeGeometry 把二維輪廓“拉起來”成三維實體(depth 決定高度,bevel 決定邊緣質(zhì)感)
  4. 給幾何體分配材質(zhì)組合:上表面更亮、側(cè)邊更有層次,立體感才會成立
  5. 用 Box3.expandByObject() 算中心與尺寸:一鍵對齊相機,讓視角永遠(yuǎn)落在地圖上
  6. 初始化 CSS2DRenderer:在動畫循環(huán)里把 WebGL 和 2D 標(biāo)簽疊加起來,再驅(qū)動光柱/粒子等小動效

1) 統(tǒng)一 GeoJSON:讓Polygon和MultiPolygon數(shù)據(jù)結(jié)構(gòu)一致

很多 GeoJSON 在實際項目里會“有時是 Polygon,有時是 MultiPolygon”。如果你寫死一套遍歷邏輯,就會出現(xiàn):某些地區(qū)根本沒被繪制出來,或者邏輯分支越寫越亂。

我是這樣實現(xiàn)的:如果幾何類型是 Polygon,就把 coordinates 包一層,讓它變成 MultiPolygon 風(fēng)格的二維數(shù)組。這樣后續(xù)只寫一套循環(huán)就夠了。

// 統(tǒng)一 GeoJSON(關(guān)鍵點:Polygon -> MultiPolygon-like)
export default function useConversionStandardData() {
  const transfromGeoJSON = (worldData) => {
    const features = worldData.features
    for (let i = 0; i < features.length; i++) {
      const element = features[i]
      if (element.geometry.type === "Polygon") {
        // Polygon: [ [ [x,y], ... ] ]
        // 包一層 -> MultiPolygon-like: [ [ [x,y], ... ] ]
        element.geometry.coordinates = [element.geometry.coordinates]
      }
    }
    return worldData
  }
  return { transfromGeoJSON }
}

數(shù)據(jù)與坐標(biāo):先想清楚你畫的是“平面”還是“球面”

THREE.Shape 本質(zhì)上只認(rèn)“二維平面坐標(biāo)”。所以第一件事不是寫代碼,而是先確認(rèn):你的 GeoJSON 點 (x, y) 在你的 Three.js 世界里,究竟應(yīng)該落到哪里。

  • 如果你的數(shù)據(jù)已經(jīng)是“平面化坐標(biāo)”(例如直接用 GeoJSON 的 (x, y) 去描輪廓),那就可以直接 Shape -> ExtrudeGeometry,不用經(jīng)緯度轉(zhuǎn)換。
  • 如果你的數(shù)據(jù)是經(jīng)緯度 (lon, lat),你就必須先做坐標(biāo)轉(zhuǎn)換。路線是:用球面映射把點投到球面上,再用四元數(shù)讓面朝向球面法線。

2) 核心:從輪廓到 3D 面(Shape + ExtrudeGeometry)

這一段就是“把平面地圖變成立體模型”的關(guān)鍵啦:只要你理解了它,后面的居中、標(biāo)簽、動效就都只是加配件。

在 Three.js 里:

  • THREE.Shape 負(fù)責(zé)把輪廓“描一遍”(moveTo/lineTo)
  • THREE.ExtrudeGeometry 負(fù)責(zé)把描好的輪廓“拉高變厚”(depth/bevel 等參數(shù)決定你要多立體)

我們可以把GeoJSON 的 ring 逐點喂給 Shape,再一口氣拉伸成網(wǎng)格。注意:Mesh 的材質(zhì)傳數(shù)組是為了“上表面更亮、側(cè)邊更有陰影感”。

import * as THREE from "three"
const extrudeSettings = { depth: 0.2, bevelEnabled: true, bevelSegments: 1, bevelThickness: 0.1 }
function buildRegion3D({ geoJson, topFaceMaterial, sideMaterial }) {
  const mapGroup = new THREE.Group()
  geoJson.features.forEach((feature) => {
    const province = new THREE.Object3D()
    const coordinates = feature.geometry.coordinates
    coordinates.forEach((multiPolygon) => {
      multiPolygon.forEach((polygon) => {
        const shape = new THREE.Shape()
        polygon.forEach(([x, y], i) => (i === 0 ? shape.moveTo(x, y) : shape.lineTo(x, y)))
        const geometry = new THREE.ExtrudeGeometry(shape, extrudeSettings)
        province.add(new THREE.Mesh(geometry, [topFaceMaterial, sideMaterial]))
      })
    })
    mapGroup.add(province)
  })
  return mapGroup
}

3) 自動居中

你也不想每換一份 GeoJSON 就手動調(diào)相機坐標(biāo),對吧?那就用包圍盒做“自動聚焦”。

Box3().expandByObject() 會把整個地圖包起來,你拿到中心點以后就可以:讓相機 lookAt 它,控制器 target 也跟著指向它。

import * as THREE from "three"

function initCameraTargetByBox({ group, camera, controls }) {
  const box3 = new THREE.Box3().expandByObject(group)
  const center = new THREE.Vector3()
  box3.getCenter(center)

  camera.lookAt(center.x, center.y, 0)
  if (controls?.target) controls.target = new THREE.Vector3(center.x, center.y, 0)
}

效果會立刻變“穩(wěn)定”:換地區(qū)數(shù)據(jù)也能落在視野正中。

4) 標(biāo)簽(CSS2D)與光柱

做到“有形”還不夠,得讓人看得懂。標(biāo)簽負(fù)責(zé)告訴你“這是什么”,光柱負(fù)責(zé)把注意力“引到那里”。

標(biāo)簽(CSS2DRenderer)

這里我沒有做3D字體幾何體(太重也太麻煩),而是用 HTML div 作為“貼紙”。CSS2DObject 讓它能跟隨相機正確投影。

import { CSS2DObject } from "three/examples/jsm/renderers/CSS2DRenderer"

const create2DTag = (html, className = "") => {
  const div = document.createElement("div")
  div.innerHTML = html
  div.className = className
  div.style.pointerEvents = "none"
  div.style.visibility = "hidden"

  const label = new CSS2DObject(div)
  label.show = (text, point) => {
    label.element.innerHTML = text
    label.element.style.visibility = "visible"
    label.position.copy(point)
  }
  label.hide = () => { label.element.style.visibility = "hidden" }
  return label
}

渲染時只要每幀調(diào)用一次 css2dRender.render(scene, camera),標(biāo)簽就會“自動跟鏡頭走”。

光柱

光柱的漂亮之處在于“看起來更立體”,實現(xiàn)方案:兩張切圖交叉,再配合透明渲染參數(shù)避免穿幫。

import * as THREE from "three"

// textureLoader / 紋理 url 在外層準(zhǔn)備
const createLightPillar = (lon, lat, height, textureUrl, color = 0x00ffff) => {
  const group = new THREE.Group()
  const geometry = new THREE.PlaneBufferGeometry(height / 6.219, height)
  geometry.rotateX(Math.PI / 2)
  geometry.translate(0, 0, height / 2)

  const material = new THREE.MeshBasicMaterial({
    map: textureLoader.load(textureUrl),
    color,
    transparent: true,
    depthWrite: false,
    side: THREE.DoubleSide,
  })

  const a = new THREE.Mesh(geometry, material)
  const b = a.clone()
  b.rotateZ(Math.PI / 2)

  group.add(a, b)
  group.position.set(lon, lat, 0)
  return group
}

地圖面生成后把光柱加進 mapGroup 就行

const light = createLightPillar(...lightCenter, heightScaleFactor, lightPillarTextureUrl)
light.position.z = 0.31
mapGroup.add(light)

5) 讓動畫活起來

你不需要把每一幀都燒到極致,但需要讓“畫面在動”,讓它不顯得生硬:

  • 背景光圈緩慢旋轉(zhuǎn)
  • 粒子沿 z 軸上升再重置
  • 2D 標(biāo)簽每幀由 CSS2DRenderer 重新渲染

核心循環(huán)就四件小事:WebGL 渲染、2D 標(biāo)簽疊加、粒子/旋轉(zhuǎn)等狀態(tài)更新、以及 TWEEN.update() 推進動畫:

loop() {
  requestAnimationFrame(() => this.loop())
  this.renderer.render(this.scene, this.camera)
  if (this.rotatingApertureMesh) this.rotatingApertureMesh.rotation.z += 0.0005
  if (this.css2dRender) this.css2dRender.render(this.scene, this.camera)
  for (const p of this.particleArr || []) {
    p.updateSequenceFrame()
    p.position.z += 0.01
    if (p.position.z >= 6) p.position.z = -6
  }
  TWEEN.update()
}

踩過的坑分享給大家,少走些彎路

  1. 坐標(biāo)系不匹配:THREE.Shape 只認(rèn)平面坐標(biāo)。你的 GeoJSON 如果和 Three.js 的繪制坐標(biāo)不一致,就會出現(xiàn)“地圖飛走了”的尷尬,需要先做投影/坐標(biāo)轉(zhuǎn)換。
  2. 空洞(holes)處理:把 ring 直接塞進 Shape,沒有顯式處理 shape.holes。一旦你的 GeoJSON 帶內(nèi)環(huán)(島/湖泊/凹洞),不處理 holes 就會“該挖的地方?jīng)]挖開”。
  3. bevel 參數(shù)太大:倒角太厚會讓面數(shù)暴漲,性能變差。一般從小 bevel 開始試,滿足質(zhì)感再加料。
  4. 數(shù)據(jù)點順序:點序自交或亂序時,Shape 可能生成失敗,或者“看起來像被折彎”。這類問題通常要先檢查幾何數(shù)據(jù)本身。

到此這篇關(guān)于three.js實現(xiàn)3D地圖的項目實踐的文章就介紹到這了,更多相關(guān)three.js實現(xiàn)3D地圖內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • JS仿Windows實現(xiàn)桌面主題特效

    JS仿Windows實現(xiàn)桌面主題特效

    這篇文章主要介紹了如何利用HTML+CSS+JS模仿Windows實現(xiàn)桌面主題特效,文中的示例代碼講講詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-06-06
  • JavaScript中的style.cssText使用教程

    JavaScript中的style.cssText使用教程

    這篇文章主要介紹了JavaScript中的cssText是什么,style.cssText使用教程,cssText返回值是什么,需要的朋友可以參考下
    2014-11-11
  • 理解?JavaScript?對象屬性訪問的復(fù)雜性(示例代碼)

    理解?JavaScript?對象屬性訪問的復(fù)雜性(示例代碼)

    在?JavaScript?編程中,開發(fā)者經(jīng)常需要對對象的屬性進行訪問,然而,訪問方式的不同可能導(dǎo)致代碼行為的差異,尤其在動態(tài)屬性的處理中,本文介紹JavaScript對象屬性訪問的復(fù)雜性,感興趣的朋友跟隨小編一起看看吧
    2024-12-12
  • 微信小程序?qū)崿F(xiàn)拍照功能

    微信小程序?qū)崿F(xiàn)拍照功能

    這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)拍照功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • JS/HTML5游戲常用算法之碰撞檢測 包圍盒檢測算法詳解【圓形情況】

    JS/HTML5游戲常用算法之碰撞檢測 包圍盒檢測算法詳解【圓形情況】

    這篇文章主要介紹了JS/HTML5游戲常用算法之碰撞檢測 包圍盒檢測算法,結(jié)合實例形式詳細(xì)分析了圓形包盒情況下的碰撞檢測算法相關(guān)原理與實現(xiàn)技巧,需要的朋友可以參考下
    2018-12-12
  • JS使用數(shù)組實現(xiàn)的隊列功能示例

    JS使用數(shù)組實現(xiàn)的隊列功能示例

    這篇文章主要介紹了JS使用數(shù)組實現(xiàn)的隊列功能,結(jié)合實例形式分析了javascript基于數(shù)組的隊列定義、元素添加、讀取等相關(guān)操作技巧,需要的朋友可以參考下
    2019-03-03
  • js 將json字符串轉(zhuǎn)換為json對象的方法解析

    js 將json字符串轉(zhuǎn)換為json對象的方法解析

    將json字符串轉(zhuǎn)換為json對象的方法。在數(shù)據(jù)傳輸過程中,json是以文本,即字符串的形式傳遞的,而JS操作的是JSON對象,所以,JSON對象和JSON字符串之間的相互轉(zhuǎn)換是關(guān)鍵
    2013-11-11
  • 原生JS實現(xiàn)加載進度條

    原生JS實現(xiàn)加載進度條

    這篇文章主要為大家詳細(xì)介紹了原生JS實現(xiàn)加載進度條,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-10-10
  • JavaScript實現(xiàn)仿Clock ISO時鐘

    JavaScript實現(xiàn)仿Clock ISO時鐘

    這篇文章給大家分享了JavaScript實現(xiàn)仿Clock ISO時鐘的方法以及實例代碼,有興趣的朋友參考學(xué)習(xí)下下。
    2018-06-06
  • 初探j(luò)s和簡單隱藏效果的實例

    初探j(luò)s和簡單隱藏效果的實例

    下面小編就為大家分享一篇初探j(luò)s和簡單隱藏效果的實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2017-11-11

最新評論

兰坪| 巩义市| 朝阳县| 双江| 清丰县| 新田县| 营山县| 邢台市| 修文县| 蒲城县| 宜兴市| 东阳市| 四川省| 股票| 奉化市| 临高县| 涞水县| 嘉荫县| 沁源县| 阿勒泰市| 弋阳县| 邵阳市| 长宁县| 和静县| 绥德县| 开化县| 江城| 喀喇沁旗| 台山市| 新巴尔虎左旗| 鸡泽县| 偃师市| 那坡县| 海安县| 阜新市| 巴楚县| 泌阳县| 承德县| 调兵山市| 镇宁| 乐昌市|