ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

R语言rayshader实战:从DEM数据到3D立体地形图渲染指南

R语言rayshader实战:从DEM数据到3D立体地形图渲染指南 拿到一份 DEM 数据想要把它变成一张能放在报告封面、论文配图甚至社交媒体上的立体地形图R 语言的 rayshader 包是目前我试过最顺手、效果也最惊艳的方案。它能在几分钟内把平淡的栅格高程数据渲染成有光影、有景深、可以自由旋转视角的 3D 模型而且全程不用离开 R 环境。这篇指南里我会把从数据获取、预处理到 3D 渲染、导出成图的完整流程拆开讲清楚包含我踩过的坑和一些能直接抄作业的参数配置适合刚接触地形可视化、或者已经在 R 里处理过空间数据但想进一步提升出图质量的读者。1. 项目概述与核心思路1.1 这到底在解决什么问题做地理数据分析的人应该都有这种体会DEM 数据本身只是一个二维的数值矩阵每一个像元记录的是该位置的海拔高度虽然可以拉伸成灰度图或者用色带渲染成平面图但人眼对三维形态的感知能力远比对颜色的感知能力敏锐得多。一个河谷的切割深度、一座火山的锥形轮廓、一条山脉的走向趋势在等高线图或者平面渲染图里往往要盯着看半天才能浮现出来而一旦转成 3D 透视图整个地形的结构关系能在一秒钟之内被大脑捕获。rayshader 解决的就是这个“从数字到形态”的关键一步它把高程矩阵映射为三维曲面再通过模拟太阳光照、添加阴影纹理、叠加或影像配色生成接近真实地貌视觉效果的可视化成果。1.2 为什么选择 R 语言加 rayshader 的组合做地形 3D 渲染其实有好几条技术路线Blender 加 GIS 插件、QGIS 的 qgis2threejs、Cesium 或 Mapbox 这类 WebGIS 方案、还有 Python 的 pyvista 等。这些工具我多少都碰过但回到日常数据分析工作流里它们多多少少存在割裂感。数据清洗、统计建模在 R 里完成换个工具做可视化就免不了导出导入和格式转换的麻烦。rayshader 最大的优势是直接嵌在 R 的生态里能用 tidyverse、sf、raster、terra 处理完数据之后立刻渲染整个环节不需要跳出 RStudio。另外一个原因在于 rayshader 本身的可控性相当高光源角度、阴影强度、水面反射、雾效、相机位置都可以一一调整相比黑盒式的在线工具它给使用者保留了非常充足的创作空间。1.3 从 DEM 到 3D 渲染的整体管线可以把完整流程拆成四个环节数据准备、数据预处理、3D 场景构建、渲染输出。数据准备阶段解决“DEM 从哪里来”的问题常见选择包括本地已有的 GeoTIFF 文件或者通过 elevatr 包直接下载全球范围的开源高程数据。预处理阶段负责统一坐标系、裁剪范围、处理缺失值必要时可以做轻微的平滑或重采样让数据更利于后续渲染。3D 场景构建是 rayshader 的主场包括把高度矩阵转成纹理贴图、设置相机视角、调整光照阴影、叠加水体图层等。最后是渲染输出可以选择快速出图用于预览也可以启用高质量渲染获得适合打印或出版的超大分辨率图片。2. 环境准备与数据获取2.1 安装 rayshader 及依赖环境的注意事项rayshader 的安装整体不难但如果你用的是 Windows 且 R 版本较旧个别依赖包可能需要预先配置。基础安装只需要一条命令install.packages(rayshader)建议同时安装配套的空间数据处理包后面做数据裁剪和投影转换时都会用到install.packages(c(elevatr, terra, sf, magrittr))Windows 用户容易忽略的一个坑是 Rtools。如果你的计划是从 GitHub 安装开发版 rayshader或者有些依赖包需要从源码编译没有 Rtools 会直接报错。Rtools 的安装版本需要与当前 R 版本对应这一点可以在 CRAN 的 Rtools 页面确认。我首次装的时候就是 R 4.3 配了旧版 Rtools排查半天才发现版本不匹配。Linux 和 macOS 用户则需要额外留意系统层面的图形库。这是因为 rayshader 底层会调用一些 C 图形处理代码如果系统缺少相关的依赖库编译阶段会抛出找不到头文件的错误。Ubuntu 系的话建议提前安装sudo apt install libgdal-dev libproj-dev libssl-dev2.2 获取 DEM 数据elevatr 包下载与本地数据读取本地已经有 GeoTIFF 格式的 DEM 数据的话用 terra 包读取即可library(terra) dem - rast(your_dem.tif)没有现成数据也不想自己去各个数据平台翻的话elevatr 包提供了一条便捷通道。它在后端调用 AWS Terrain Tiles 提供的开源高程数据可以根据你定义的地理范围自动裁剪和拼接默认使用的是 Mapzen 的高程服务。核心代码非常简洁library(elevatr) library(sf) # 定义一个范围比如某个研究区域的边界框 bounds - st_as_sfc(st_bbox(c(xmin 116.3, ymin 39.8, xmax 116.6, ymax 40.2), crs 4326)) dem_elevatr - get_elev_raster(bounds, z 10)其中 z 参数控制数据分辨率z 值越高瓦片越精细、数据量也越大。z 10 大约对应 30 米级别的分辨率适合展示大范围地形如果关注的是一个小流域或者山体可以调高到 z 12 或 13。不过要注意下载时间会随 z 值快速上升第一次使用建议从 z 9 或 10 开始测试流程。还有一点值得提醒get_elev_raster 返回的是 RasterLayer 对象在 terra 体系下可以用 rast() 转换一下统一数据类型。同时不同渠道拿到的 DEM 数据坐标系可能不同渲染本身不要求投影坐标系但经纬度坐标系WGS84在后续涉及距离或面积计算时会有变形问题建议提前用 project() 转换到合适的投影坐标系。2.3 DEM 数据的预处理裁剪、重采样、缺失值处理拿到原始 DEM 之后不要急着渲染先做三件小事。第一是裁剪到目标区域把无关的周边地形去掉这样既能减少内存占用也能避免无关地形抢走视觉焦点。可以用 terra 的 crop 和 mask 操作完成。第二是检查缺失值。SRTM 这类数据在陡峭山区和水体区域偶尔会出现空值直接渲染的话这些地方会呈现异常的凹陷或尖刺需要用 focal 函数做平滑插值或用邻近像元填充。第三是确认数据范围是否需要重采样。如果 DEM 分辨率太高、范围又很大意味着矩阵规模可能是上万乘上万不仅渲染慢还有可能导致显卡或内存溢出。可以先用聚合降低分辨率比如用 aggregate 做因子为 2 或 3 的聚合牺牲一点细节换取流程的顺畅。这一点在实际项目中非常常用我一般预览阶段用低分辨率数据最终出图再换回全分辨率。3. 核心渲染实现从高度矩阵到 3D 视图3.1 数据格式的转换逻辑rayshader 的核心渲染函数接收的不是 Spatial 对象或 RasterLayer而是普通的数值矩阵matrix。这个矩阵的每一个值对应一个像元的高度行和列则对应地物的平面坐标。所以渲染前的关键一步是把 terra 的 SpatRaster 转成矩阵。这个过程本身很简单但有一个方向问题需要注意矩阵的行方向和地理坐标的南北方向可能相反渲染出来的地形会出现上下颠倒的情况。处理方法是library(rayshader) library(terra) # 读取 DEM dem - rast(your_dem.tif) # 转换为矩阵 mat - matrix( extract(dem, ext(dem), cells TRUE)[, 3], nrow nrow(dem), ncol ncol(dem), byrow TRUE ) # 如果地形上下颠倒翻转矩阵 mat - apply(mat, 2, rev)实际上 rayshader 社区更常见的做法是直接使用raster_to_matrix()函数它会把 RasterLayer 对象转成矩阵并自动处理方向问题。配合 elevatr 下载的数据整个过程可以写成dem_raster - raster::raster(dem_elevatr) mat - rayshader::raster_to_matrix(dem_raster)3.2 第一次 3D 渲染玩转 plot_3d 的关键参数矩阵准备好之后最简单的 3D 渲染只需要一行核心函数mat %% height_shade(texture texture_desert()) %% plot_3d(heightmap mat, zscale 100, solid TRUE, shadow TRUE)这行代码做了两件事height_shade 根据高度生成一张色彩纹理图相当于给地形“涂上颜色”然后 plot_3d 把这个带有纹理的高度地图渲染成 3D 场景。关键在于 zscale 参数它控制垂直方向的比例系数。由于 DEM 数据中一个像元的平面距离动辄几十上百米而海拔变化可能只有几百米如果不做垂直拉伸渲染出来的地形会平坦得像一块板子zscale 设置得越小垂直方向的拉伸越明显。经验值是平坦区域、大范围场景用 200 到 500山地、小范围场景用 20 到 80。可以多试几个值找到视觉效果最舒服的倍数。plot_3d 还有一些值得调整的视觉参数。solid 控制是否在底部生成实体块可以让地形看起来像悬浮的模型shadow 控制是否生成阴影打开后会在地形底部产生一个深色投影空间感更强。threejs 或 rgl 窗口打开后你可以用鼠标拖拽旋转视角、滚轮缩放从不同角度检查地形的形态。3.3 让地形更真实光照、阴影和视角控制第一次渲染出来可能只是“有色地形”还不够“3D”。这里的关键在于 add_shadow 和光照角度。可以这样操作mat %% height_shade(texture texture_desert()) %% add_shadow(lamb_shade(mat, zscale 100, sunaltitude 30, sunangle 45), 0.5) %% plot_3d(heightmap mat, zscale 100, windowsize c(800, 600))lamb_shade 函数模拟的是朗伯体漫反射光照sunaltitude 是太阳高度角单位是度数值越小光线越倾斜、地形的阴影和立体感越强sunangle 是太阳方位角改变它会改变阴影的方向相当于移动太阳的位置让山脉的明暗关系发生变化。处理完光照之后用 add_shadow 把阴影叠加到纹理图上第二个参数控制阴影的透明度。视角控制同样重要。plot_3d 的默认视角是 45 度左右的俯视但很多场景下低角度视角更有冲击力。可以通过 render_camera 函数精细控制render_camera(phi 35, theta 30, zoom 0.6)phi 是相机与水平面的夹角可以理解为俯仰角theta 是相机绕地形旋转的角度相当于圆周方向的位置zoom 是缩放系数。建议在 RStudio 的 rgl 窗口里先手动旋转找到一个心仪的角度再把对应的 phi/theta 值填进代码保证后期重新渲染时视角一致。4. 视觉增强与配色方案选择4.1 如何根据地形类型选择合适的纹理色带配色是决定一张地形图质感的核心因素。rayshader 内置了不少纹理函数比如 texture_desert() 适合干旱区texture_imagination() 偏艺术化texture_water() 适合水文主题。但内置纹理有时候满足不了需求好在这个包提供了非常灵活的配色机制核心是使用 colorRampPalette 自定义色带custom_palette - colorRampPalette(c(#285942, #4B8C5A, #A8B85A, #E8D9A0, #D9B36C, #A67B5B))(256) mat %% height_shade(texture custom_palette) %% plot_3d(heightmap mat, zscale 100)自定义色带的逻辑并不复杂想一想这个地区的地貌特征低海拔是河谷常绿植被就用偏深的绿色中山区是草地或裸岩就过渡到黄褐色高海拔是裸岩或积雪就用灰色到白色收尾。这样渲染出来的图不仅能表达高度还能让读者一眼理解地表覆盖的大致类型。需要注意 colorRampPalette 生成的色带是离散的颜色向量256 个级别足够平滑少于 64 会看到明显的分层色带。4.2 叠加卫星影像或专题数据作为纹理除了用颜色表达高度之外rayshader 还允许把其他栅格数据作为纹理贴到 3D 地形上。最典型的是叠加卫星影像这样做出来的效果是“真实地球”风格地形的高程来自 DEM颜色来自遥感影像两者结合之后非常接近航拍视角。操作上也很直接# 假设 img 是与 DEM 范围一致的卫星影像已转成矩阵 mat %% sphere_shade(texture desert) %% add_overlay(img, alphalayer 1) %% plot_3d(heightmap mat, zscale 100)add_overlay 可以把任意 RGB 矩阵作为贴图覆盖在地形表面透明度由 alphalayer 控制。这个功能同样适用于叠加土壤类型图、土地覆盖分类图、人口密度图等专题数据相当于让三维地形成为一个画布上面展示的内容可以根据研究目的自由替换。我自己的经验是叠加高分辨率影像时注意检查两个栅格数据的范围、分辨率、投影是否一致不一致的话要先重采样和裁剪对齐否则贴图位置会发生偏移产生“张冠李戴”的错误。4.3 添加水面效果和雾效做出场景感很多地形可视化项目都涉及河流、湖泊、水库一类的水体要素。ray shader 的水面模拟是我非常喜欢的功能它并不是简单地把某块区域涂成蓝色而是渲染出带反射效果的水面质感。前提是准备一个水体掩膜矩阵水体区域值为 1非水体区域为 0然后用 render_water 添加到场景中water_mask - matrix(0, nrow nrow(mat), ncol ncol(mat)) water_mask[湖泊对应的行, 湖泊对应的列] - 1 plot_3d(heightmap mat, zscale 100) render_water(heightmap mat, waterheight 80, watermask water_mask, wateralpha 0.7)waterheight 是水面的海拔高度只有当 DEM 值低于这个高度的区域才会被水覆盖watermask 是可选的用来限定哪些位置可以出现水。wateralpha 控制水的透明度越大水体越不透明反射感越强。如果你只有一个固定的水面高度比如模拟海平面上升可以不传 watermask直接让所有低于 waterheight 的位置变成水面。雾效的加入则能大大增强场景的纵深感。render_atmosphere 可以在远处添加大气散射效果让地形边缘自然淡出降低人工感render_atmosphere(altitude 1000, sunangle 45, haze 0.3)altitude 模拟相机距离地面的高度这个值越小雾效越浓haze 控制大气的浑浊程度。实际使用中我在展示大范围山脉的时候喜欢加一点雾效能让远山有一种朦胧感更接近真实航拍。5. 高质量渲染与结果导出5.1 预览图与最终图的差异rgl 窗口里旋转查看的场景是实时渲染的受限于显示器分辨率和显卡性能画面往往比较粗糙有锯齿、阴影不够柔和。真正用于报告或出版的图片需要用 render_snapshot 或 render_highquality 单独输出。两者的区别可以这样理解render_snapshot 捕获的是当前 3D 窗口的画面速度非常快适合做快速预览render_highquality 则是重新启动一个离屏渲染管线逐像素计算每一束光线的颜色和阴影效果接近电影级画面但耗时显著增加。一张 2000 像素宽的图在普通电脑上可能要等一两分钟如果用 4K 分辨率、开抗锯齿和软阴影等上十几分钟也不奇怪。5.2 输出参数详解分辨率、抗锯齿、文件名高质量渲染的调用方式很直接render_highquality( filename terrain_3d.png, width 3000, height 2000, samples 16, antialias TRUE, clear FALSE )samples 控制在每个像素上采样光线的次数数值越高画质越细腻噪点越少但渲染时间成倍增加。入门建议从 4 开始效果不满意逐步上调到 16。antialias 开启后可以显著消除边缘锯齿代价同样是时间。实际渲染前先把相机角度调整到理想位置再调用导出函数如果发现导出后视角不对可以直接在 rgl 窗口拖拽调整然后重新执行 render_highquality不需要重复前面的 3D 场景构建代码。另外值得一提的还有 render_snapshot 的一个实用场景分享到社交媒体、嵌入网页或快速给同事看一版效果时完全没必要动用高画质渲染一张快图能大大提高迭代速度。我的习惯是先用 snapshot 确认构图和配色最后一步才上 highquality 出最终图。5.3 动起来让视角环绕地形的 GIF 与视频静态图之外rayshader 还提供了一种非常有传播力的输出方式——旋转视角动画。render_movie 可以让相机围绕地形旋转 360 度最终输出一个 MP4 视频或者一系列帧图片配合 ffmpeg 还能转成 GIF。代码模板如下render_movie(filename terrain_rotate.mp4, phi 35, theta seq(0, 360, length.out 180))theta 参数传入一个序列表示每一帧相机所处的角度。180 帧是一个比较流畅的配置如果机器性能一般可以减少到 120 帧。实际体验中这种旋转动画在学术汇报、项目展示和社交媒体上都非常讨喜几分钟的模型演示视频比十张静态图更能直观传达地形全貌。我自己做区域规划项目的汇报时经常把主导航图和一段 15 秒的三维旋转动画放在一起听众的专注度会明显上一个台阶。6. 渲染过程中的常见问题与避坑指南6.1 高频错误和排查方案速查表问题现象可能原因解决方案安装 rayshader 报错提示缺少 RtoolsWindows 环境未配置 Rtools到 CRAN 下载与当前 R 版本匹配的 Rtools 并完成安装加载包时提示找不到 GDAL系统缺少 GDAL 库Linux 安装 libgdal-devmacOS 用 Homebrew 安装 gdal渲染出来的地形上下颠倒矩阵行顺序与坐标方向不一致用 apply(mat, 2, rev) 翻转矩阵地形过于平坦几乎没有立体感zscale 设置太大试 50 到 100 之间的值按比例缩小叠加影像后纹理位置偏移两个栅格数据的投影或范围不一致用 project 和 resample 统一坐标系与分辨率内存不足R 会话直接崩溃DEM 范围太大或分辨率太高用 aggregate 降低矩阵规模或裁剪小范围测试render_highquality 渲染很久没结果samples 或分辨率设置过高先用小图和 samples4 测试再逐步加码窗口能显示图像但导出 PNG 全黑rgl 窗口被遮挡或显卡驱动兼容问题尝试调用 rgl.clear() 后重建场景或更新显卡驱动6.2 关于数据范围和分辨率匹配的经验规划一个地形可视化项目时最先确定的不是配色或视角而是“主角”的范围和尺度。范围决定了地形的叙事尺度同一座山放进十公里的大场景里它是山脉的一部分放进两公里的小场景里它是独立的视觉主体。相应地分辨率也要匹配范围大范围用高分辨率数据渲染矩阵会非常庞大普通笔记本可能直接卡死不如主动降低分辨率先把构图跑通之后再用局部放大或者提高分辨率产出终稿。这一步看起来很简单但很多人容易忽略结果浪费了大量时间在等待渲染上。实际使用中我还有一个习惯把范围边界多向外扩一点留出视觉缓冲空间避免地形模型的边缘正好切在山顶或山谷这种视觉焦点位置。好比拍照时在主体周围留白画面的构图会松弛很多。6.3 投影与坐标那点事别等到出图才发现由于 rayshader 本身只是在画数值矩阵它对坐标的依赖其实很低——你完全可以把 DEM 当成一张普通的灰度图像来处理。但这不代表可以忽略投影问题。如果后续需要把渲染图与矢量边界、标注点、比例尺等要素合成或者导出坐标信息供 GIS 软件读取那么从源数据开始就使用统一的投影坐标系会省很多事。更实际的一点是球面坐标经纬度在高纬度地区的面积变形非常严重如果一个研究区纬度较高直接渲染会让地形在东西方向被拉伸变形看起来非常别扭。这种情况下应该在预处理阶段用 project() 转换到适合当地的投影坐标系比如 UTM 分区投影。6.4 配色与光照的视觉错觉陷阱最后分享一个比较容易踩坑的地方配色和光照叠加后可能产生视觉误导。例如当使用从低海拔绿色到高海拔白色的渐变时如果光线恰好从某个角度照射白色雪顶区域会反射大量光线导致过曝丢失纹理细节。这在渲染高海拔、多雪区域时尤其明显。解决办法是把光线角度调得低一些或者使用更柔和的阴影叠加层。另外自定义色带时注意避免使用红绿这一类色盲用户难以区分的颜色组合。我通常会在渲染后把图给身边同事瞄一眼确认没有明显的视觉歧义再定稿。7. 从静态图到交互应用一些扩展玩法rayshader 并不只停留在静态出图这一步。借助另一个包你可以把渲染结果导出为交互式 3D 场景发布到网页上让读者自己拖拽旋转这个功能在做数据新闻、在线报告或教学材料时非常加分。核心操作是用save_3d相关的函数将 R 中的 3D 模型保存为 HTML 格式之后用浏览器打开就能直接交互浏览。步骤上并不复杂关键是先把 3D 场景构建好再调一次导出函数library(rayshader) library(htmlwidgets) plot_3d(heightmap mat, zscale 100) # 这里调整好视角后导出为交互式网页 save_3d(filename terrain_interactive.html)打开这个 HTML 文件后读者可以像操纵一个真实模型一样旋转、缩放地形从任意角度观察。这种做法相比静态图最大的优势在于它把“探索感”交给了读者而不是由作者固定一个观看视角。我拿一个流域的地形做过测试地理专业的同事说这种交互方式在讲解地形与水文关系的时候比 PPT 里的静态图直观太多了。另外rayshader 也可以与 Shiny 应用结合把渲染过程封装成一个交互式应用让使用者在网页端自行选择渲染区域、调整 zscale 和配色。当然这会涉及更多的工程开发内容通常适合需要面向非技术用户交付成果的团队。8. 实操中的几点体会用 rayshader 做地形可视化上手周期其实很短但想要出真正养眼、信息传达准确的图还是需要在使用中慢慢积累感觉。我自己总结了三条经验第一数据预处理花的时间永远值得裁剪、投影、缺失值处理这些前期工作决定了最终渲染质量的上限第二zscale 和视角是地形表现力的灵魂花时间试不同组合一定会有回报第三配色要克制避免过于花哨的颜色分带让地形本身的形态和光影表达主要的信息颜色只是辅助。另外在没有特殊要求的情况下建议始终按照“小图快测大图精渲”的节奏推进。先用较低分辨率和较小的导出尺寸确认画面构图和参数是否合适再把数据与分辨率升到最高做最终渲染。这样既能把试错成本保持在几秒钟又能在最终出图时获得最好的画质。你手上如果有现成的 DEM 数据不管是一片山地还是一个城市周边完全可以从今天这篇文章里的代码开始花十分钟跑通第一张三维地形图。等到跑通了再回来慢慢调整配色和光影相信你会发现原来从数据到触手可及的立体山川距离就这么近。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表