Three.js 实现高分辨率地球边界可视化

在 WebGL 可视化项目中,Three.js 常常被用来实现三维地球的渲染和交互。但仅仅显示地球贴图还不够,很多时候我们需要在球面上叠加地理边界(比如国家、省份轮廓),以增强可读性和交互性。本文将介绍如何在 Three.js 中利用 Canvas 叠加高分辨率边界线,并将其作为纹理映射到三维地球上。


一、实现目标

  1. 使用一张 8K 高清地球纹理 作为底图。
  2. GeoJSON 数据 中解析边界信息。
  3. Canvas 上绘制边界线,并与底图合成一张新纹理。
  4. 将合成后的纹理映射到球体几何体上,实现 高分辨率边界显示

最终效果:

  • 既保留了高清地球纹理的细节。
  • 又在其上叠加了清晰的地理边界。

二、核心思路

  1. 加载底图与高程图

    • 使用 TextureLoader 加载地球基础纹理(8K 分辨率)。
    • 加载地形高度贴图,用于球体表面微弱的位移效果。
  2. 解析 GeoJSON 边界数据

    • 遍历 PolygonMultiPolygon 类型,逐点计算对应的纹理坐标。

    • 采用 等经纬度展开映射

      ini 复制代码
      x = ((180 + lon) / 360) * width;
      y = ((90 - lat) / 180) * height;
  3. Canvas 绘制边界

    • 将底图绘制到 Canvas 上。
    • 使用 stroke 方法绘制边界线。
    • 线条颜色、粗细可调节(高分辨率下线宽建议 >1.5)。
  4. 合成新纹理

    • 将 Canvas 转换为 THREE.CanvasTexture
    • 作为着色器材质的 earthTexture 使用。
  5. Shader 实现表面位移 + 纹理采样

    • 顶点着色器:根据高程贴图位移顶点。
    • 片元着色器:采样合成后的纹理,并输出到屏幕。

三、关键代码

1. GeoJSON 转 Canvas 边界绘制

ini 复制代码
function createBorderTexture(geojson, baseImage) {
  const canvas = document.createElement("canvas");
  canvas.width = baseImage.width;
  canvas.height = baseImage.height;
  const ctx = canvas.getContext("2d");

  // 绘制底图
  ctx.drawImage(baseImage, 0, 0, canvas.width, canvas.height);

  // 设置边界线样式
  ctx.strokeStyle = "red";
  ctx.lineWidth = 2.0;

  // 遍历边界
  geojson.features.forEach(feature => {
    const type = feature.geometry.type;

    if (type === "Polygon") {
      feature.geometry.coordinates.forEach(ring => {
        ctx.beginPath();
        ring.forEach(([lon, lat], idx) => {
          const x = ((180 + lon) / 360) * canvas.width;
          const y = ((90 - lat) / 180) * canvas.height;
          if (idx === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        });
        ctx.closePath();
        ctx.stroke();
      });
    }

    if (type === "MultiPolygon") {
      feature.geometry.coordinates.forEach(polygon => {
        polygon.forEach(ring => {
          ctx.beginPath();
          ring.forEach(([lon, lat], idx) => {
            const x = ((180 + lon) / 360) * canvas.width;
            const y = ((90 - lat) / 180) * canvas.height;
            if (idx === 0) ctx.moveTo(x, y);
            else ctx.lineTo(x, y);
          });
          ctx.closePath();
          ctx.stroke();
        });
      });
    }
  });

  return new THREE.CanvasTexture(canvas);
}

2. Shader 材质

ini 复制代码
const material = new THREE.ShaderMaterial({
  uniforms: {
    earthTexture: { value: borderTexture },
    heightTexture: { value: heightTexture },
    displacementScale: { value: 0.1 }
  },
  vertexShader: `
    uniform sampler2D heightTexture;
    uniform float displacementScale;
    varying vec2 vUv;
    void main() {
    // 将uv 帖图传给 片元着色器使用
      vUv = uv;
      // 将灰度图获取r的值为 0-1 
      float height = texture2D(heightTexture, uv).r;
      // normal * height 计算示例 vec3(0,0.5,0) * 0.2  => vec3(0,1,0)
      // position + normal 计算示例  vec3(0,0.5,0) + vec3(0,0.2,0) => vec3(0,0.7,0)
      // 在计算完成之后会在球体中将 顶点 生成对应的高度
      vec3 newPosition = position + normal * height * displacementScale;
      gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    }
  `,
  fragmentShader: `
  // 获取地图 8K
    uniform sampler2D earthTexture;
    // 获取顶点着色器中的uv
    varying vec2 vUv;
    void main() {
    // 计算顶点对应的rgb值 
      vec3 color = texture2D(earthTexture, vUv).rgb;
      // 将 rgb 返回给显示做颜色显示
      gl_FragColor = vec4(color, 1.0);
    }
  `
});

3. 地球几何体

ini 复制代码
const geometry = new THREE.SphereGeometry(1, 128, 128);
earth = new THREE.Mesh(geometry, material);

四、交互与控制

在场景中使用 OrbitControls

  • 支持旋转、缩放。
  • 限制相机缩放距离,保证交互舒适性。
ini 复制代码
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.08;
controls.minDistance = 1.2;
controls.maxDistance = 10;

同时,可以通过按钮控制地球的自动旋转:

csharp 复制代码
if (rotateStatus.value) {
  group.rotation.y += 0.002;
}

五、总结与拓展

本文通过 Canvas 动态合成纹理 的方式,实现了在 8K 高清地球贴图上叠加边界线 的效果。这样不仅保证了纹理的清晰度,还能灵活调整边界的样式。

该文章由AI生成后调整

要代码的请留言,我会将源码发私信发出 示例地址

未来的拓展方向:

  1. 使用 Raycaster 增加鼠标交互(点击高亮区域)。
  2. 将边界独立为 LineLoopMesh,实现更复杂的交互。
  3. 支持多层纹理(如云层、大气层)以增强真实感。
相关推荐
南屿im24 分钟前
用 Node.js 开发命令行工具:打造你的高效 CLI
前端·javascript
ObjectX前端实验室2 小时前
【react18原理探究实践】render阶段【首次挂载】
前端·react.js
ObjectX前端实验室2 小时前
【react18原理探究实践】组件的 props 和 state 究竟是如何确定和存储的?
前端·react.js
fxshy2 小时前
解决 Web 应用加载地图资源时的 HTTP 与 HTTPS 混合内容问题
前端·网络协议·http
一个很帅的帅哥3 小时前
Vue keep-alive
前端·javascript·vue.js·keep-alive
lbh3 小时前
Chrome DevTools 详解(一):Elements 面板
前端·javascript·浏览器
明里人3 小时前
React 状态库:Zustand 和 Jotai 怎么选?
前端·javascript·react.js
sniper_fandc3 小时前
Vue3双向数据绑定v-model
前端·vue
訾博ZiBo3 小时前
为什么我的 React 组件会无限循环?—— 一次由 `onClick` 引发的“惨案”分析
前端·react.js
my一阁4 小时前
一文解决Chrome使用
前端·chrome