在 WebGL 可视化项目中,Three.js 常常被用来实现三维地球的渲染和交互。但仅仅显示地球贴图还不够,很多时候我们需要在球面上叠加地理边界(比如国家、省份轮廓),以增强可读性和交互性。本文将介绍如何在 Three.js 中利用 Canvas 叠加高分辨率边界线,并将其作为纹理映射到三维地球上。
一、实现目标
- 使用一张 8K 高清地球纹理 作为底图。
- 从 GeoJSON 数据 中解析边界信息。
- 在 Canvas 上绘制边界线,并与底图合成一张新纹理。
- 将合成后的纹理映射到球体几何体上,实现 高分辨率边界显示。
最终效果:
- 既保留了高清地球纹理的细节。
- 又在其上叠加了清晰的地理边界。
二、核心思路
-
加载底图与高程图:
- 使用
TextureLoader
加载地球基础纹理(8K 分辨率)。 - 加载地形高度贴图,用于球体表面微弱的位移效果。
- 使用
-
解析 GeoJSON 边界数据:
-
遍历
Polygon
和MultiPolygon
类型,逐点计算对应的纹理坐标。 -
采用 等经纬度展开映射:
inix = ((180 + lon) / 360) * width; y = ((90 - lat) / 180) * height;
-
-
Canvas 绘制边界:
- 将底图绘制到 Canvas 上。
- 使用
stroke
方法绘制边界线。 - 线条颜色、粗细可调节(高分辨率下线宽建议 >1.5)。
-
合成新纹理:
- 将 Canvas 转换为
THREE.CanvasTexture
。 - 作为着色器材质的
earthTexture
使用。
- 将 Canvas 转换为
-
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生成后调整
要代码的请留言,我会将源码发私信发出 示例地址
未来的拓展方向:
- 使用
Raycaster
增加鼠标交互(点击高亮区域)。 - 将边界独立为
LineLoop
或Mesh
,实现更复杂的交互。 - 支持多层纹理(如云层、大气层)以增强真实感。