用Three.js可视化数字高程模型【DEM】
在这个教程中,我们将学习如何使用three.js渲染土耳其最高的Ağrı山脉的数字高程模型(DEM)数据,使用的工具包括Three.js、geotiff、webpack和QGIS。
我们将要使用的数据是由USGS (美国地质调查局)免费提供的。使用USGS Earth Explorer我下载了Ağrı山脉的DEM(数字高程模型)和卫星图像,这是一个休眠的火山,也是土耳其境内最高的山。
我使用USGS应用程序下载了一些卫星影像,然后尝试着找出云层覆盖率小于10%的图像:
Landsat🛰️ - 用卫星影像做纹理
如果你不熟悉遥感和图像处理,你可能没有听说过Landsat。Landsat是由美国地质调查局控制的卫星,为研究人员提供约30平方米的分辨率的科学卫星图像已经很多年了。图像的一个像素覆盖30平方米的地面区域,卫星的摄像头与地球垂直。现在,有些卫星的分辨率可以做到小于一米,但一般来说,它们的数据不能免费获得。所以,Landsat对我们来说已经足够了,我们将使用Landsat卫星影像作为我们的3D模型的纹理。
下载Landsat卫星图像可以点这里。图像的云层覆盖范围应小于 10%,并且应将其添加到标准中。很难找到一个好的,因为山是如此之高,大部分情况下图像中都有很多云。在找到合适的图像后,我意识到Landsat覆盖了一个巨大的区域,需要裁剪感兴趣的区域作为3D模型的纹理。但更重要的是,我们需要一个数字高程模型来将山脉可视化。
SRTM🗻 - 栅格化DEM数据
SRTM是Shuttle Radar Topography Mission的缩写,中文含义是航天飞机雷达地形任务。SRTM由美国宇航局运营,提供栅格化的数字高程模型。SRTM 的 30平米 分辨率的DEM数据,意味着一个像素覆盖约30平米的面积,并且将该地区的平均高程作为像素值。这些数据对于使用three.js生成我们的山地模型非常有价值。
数据预处理
我们使用 QGIS栅格工具剪切、制作DEM和相关卫星图像的掩膜,并将它们复制到工作目录:
看起来像Mouth Doom,这是在QGIS中使用默认调色板显示高程模型的效果。考虑到性能问题,我裁剪了两个不同尺寸的图像,你可以在代码仓库中找到。在下面的示例中我们将使用其中较小的那个以便快速查看运行结果。
Three.js
Three.js是一个优秀的JS库,使WebGL更易于使用WebGL。在three.js世界中,我们需要一些基本的设置,其中的4个基本组件是:
- 场景
- 渲染器
- 相机
- 对象(包含材质)
添加场景灯光
我们将从添加场景开始,然后设置渲染器、摄像头、控件和光线。添加光线至关重要,否则你在场景中看不到任何东西。
setupLight() {
this.light = new THREE.DirectionalLight(0xffffff);
this.light.position.set(500, 1000, 250);
this.scene.add(this.light);
}
用DEM数据生成山的模型
我们要渲染的几何形状不是使用Blender、Maya等软件建模的,相反,我们将使用DEM数据直接用js生成一个3D模型,借助于"geotiff"库:
import * as GeoTIFF from "geotiff";
setupTerrainModel() {
const readGeoTif = async () => {
...
};
readGeoTif();
}
首先读取图像文件:
const rawTiff = await GeoTIFF.fromUrl(terrain);
const tifImage = await rawTiff.getImage();
const image = {
width: tifImage.getWidth(),
height: tifImage.getHeight()
};
// Our initial plane geometry
const geometry = new THREE.PlaneGeometry(
image.width,
image.height,
image.width - 1,
image.height - 1
);
在setupTerrainModel函数实现中,将剪裁的图像添加到项目后,我们使用geotiff库来读取DEM文件,并添加一个新的与DEM图像相同大小的PlaneGeometry对象。好了,让我们来生成这个大家伙:
// Read image pixel values that each pixel corresponding a height
const data = await tifImage.readRasters({ interleave: true });
// Fill z values of the geometry
console.time("parseGeom");
geometry.vertices.forEach((geom, index) => {
geom.z = (data[index] / 20) * -1;
});
console.timeEnd("parseGeom");
接下来我们读取每个像素的值 - 对应于高程值。我只是试探地将这个值除以20,使其看起来不错,并乘以-1,否则模型将颠倒过来 —这是因为three.js的z坐标方向 —我稍后会解释。使用console.time来跟踪代码性能。
现在我们的模型就可以显示出来了,但没有卫星图像,它只是一个3D白模:
纹理拟合
生成模型后,我们将使用 RGB 卫星图像,该图像也是之前用 QGIS 剪接的:
import * as mountainImage from "../textures/agri-small-autumn.jpg";
// ... other stuff
const texture = new THREE.TextureLoader().load(mountainImage);
const material = new THREE.MeshLambertMaterial({
wireframe: false,
side: THREE.DoubleSide,
map: texture
});
我们正在加载卫星图像,并保存在material变量中,以便后续在Three.js的 MESH对象上使用。不要忘记旋转对象,因为three.js采用右手坐标系,这意味着,默认情况下,Z轴不是朝上而是指向你。关于这一点的详细解释可以查看这里。
const mountain = new THREE.Mesh(geometry, material);
mountain.position.y = -100;
mountain.rotation.x = Math.PI / 2;
this.scene.add(mountain);
你可以在开始时使用camera.up.set(0,0,1)
旋转 x 轴或旋转相机,然后在上面放置山的模型,沿y 轴向下移动一点点,稍微调整下,就可以了。
源代码下载:
原文链接:Visualizing a mountain using Three.js, Landsat and SRTM
BimAnt翻译整理,转载请标明出处