deck.gl SimpleMeshLayer 入门实战:在三维地图中批量渲染任意 3D 模型
【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl
本教程以仓库中的 mesh 最小示例 为骨架,系统讲解 deck.glSimpleMeshLayer的安装、数据格式、核心配置与源码级渲染原理。读完你将从零搭建一个在三维空间中批量摆放、旋转、缩放并着色任意 OBJ/PLY 等网格模型的 deck.gl 应用,并理解其底层如何把每个实例的位置、朝向与缩放矩阵写入 GPU 顶点属性。
示例概览:一个最小可运行的 SimpleMeshLayer 应用
仓库中的examples/website/mesh是一个刻意保持精简的 deck.gl 示例,仅由 4 个文件组成:
- README.md:示例说明与运行方式
- app.jsx:核心应用代码(React +
@deck.gl/react) - index.html:页面入口,挂载
#app容器 - package.json:依赖与脚本声明
其核心思想一句话概括:用SimpleMeshLayer把同一个 3D 几何体(mesh)在数据流中渲染出大量实例,每个实例拥有独立的位置、颜色、朝向与缩放。官方 API 文档给出的典型场景是"在地图上可视化一支各有位置和朝向的 3D 车队"(见 SimpleMeshLayer 文档)。
快速上手:安装、运行与依赖
将examples/website/mesh目录内容复制到你的项目后,按 README 中的方式安装依赖并启动:
# 安装依赖 npm install # 或使用 yarn yarn # 使用 vite 打包并启动开发服务器 npm start查看 package.json 可以看到示例的依赖构成:
deck.gl(^9.0.0):聚合了 core 与各图层模块的入口包@loaders.gl/obj(^4.4.3):用于解析 OBJ 格式模型的 loader@math.gl/core(^4.1.0):数学工具react/react-dom(^18.0.0):渲染层vite(^7.3.3):构建与开发服务器
脚本定义与运行方式一一对应:start对应vite --open,start-local则复用仓库根目录的本地开发配置(vite --config ../../vite.config.local.mjs),build执行vite build产出静态文件。
如果不想使用 React,文档也提供了 NPM 安装与预打包脚本两种接入方式(见 simple-mesh-layer.md):
npm install deck.gl # 或按需安装 npm install @deck.gl/core @deck.gl/mesh-layers使用预打包脚本(pre-bundled scripts)时:
<script src="https://unpkg.com/deck.gl@^9.0.0/dist.min.js"></script>然后通过全局命名空间使用:new deck.SimpleMeshLayer({})。
数据格式:mesh 与 loaders.gl
README 明确指出,示例所用的 OBJ 模型来自 John Burkardt 的数据样例集;而真正让 SimpleMeshLayer 可以加载各类 3D 格式的关键是 loaders.gl——它提供了与 deck.gl 无缝协作的一系列通用 3D 格式 loader。
在示例中,app.jsx 先注册 loader,再通过 URL 加载模型:
import {OBJLoader} from '@loaders.gl/obj'; import {registerLoaders} from '@loaders.gl/core'; // 把处理 mesh 格式的 loader 注册进全局 registerLoaders([OBJLoader]); const MESH_URL = 'https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/mesh/minicooper.obj';随后把这个 URL 直接传给图层的mesh属性即可。换成 PLY 等其他格式同样简单(见官方文档的 PLY 示例):
import {SimpleMeshLayer} from '@deck.gl/mesh-layers'; import {PLYLoader} from '@loaders.gl/ply'; new SimpleMeshLayer({ mesh: 'path/to/model.ply', loaders: [PLYLoader] });从 simple-mesh-layer.ts 源码 可以看出,mesh与texture都被声明为{type: 'object', value: null, async: true},即异步属性:传入 URL 字符串后,deck.gl 会借助已注册的 loader 异步解析。实际上mesh属性接受三种形态:
- 指向网格描述文件的 URL(格式需被 loaders.gl 支持,并注册对应 loader);
- 一个 luma.gl 的
Geometry实例; - 一个包含
positions(Float32Array,相对物体中心的三维顶点偏移,单位为米)、normals(三维法线)、texCoords(二维纹理坐标)的普通对象。
源码中的normalizeGeometryAttributes(simple-mesh-layer.ts)展示了网格数据的归一化逻辑:它会同时兼容小写(positions/normals/texCoords)与 loaders.gl 惯例的大写(POSITION/NORMAL/TEXCOORD_0/COLOR_0)属性名,缺失的colors、normals、texCoords会被自动填充为占位数组(颜色填 1、法线填 0、纹理坐标填 0),保证着色器总能拿到完整的顶点属性。
核心配置参数详解
以下参数均出自 SimpleMeshLayer 官方 API 文档,并可由 defaultProps 逐一印证默认值与类型。
mesh
- 类型:
string | object - 作用:每个数据对象要渲染的几何体,取值方式见上文三种形态。
texture 与 textureParameters
texture:默认null。网格的纹理。接受字符串(URL 或 Data URL)、WebGL2 合法像素源、luma.glTexture实例,或可传给Texture构造器的普通对象(如{width, height, data}),也接受解析到以上类型的 Promise。当texture存在时用它渲染几何体;否则使用getColor获取的对象颜色。textureParameters:自定义纹理采样参数。不指定时使用线性平滑插值的默认值:
{ minFilter: 'linear', magFilter: 'linear', mipmapFilter: 'linear', addressModeU: 'clamp-to-edge', addressModeV: 'clamp-to-edge' }在源码中,texture同样被标记为async: true,且未提供纹理时会创建一个 1x1 的emptyTexture占位,以避免 luma.gl 的缺省 uniform 警告(simple-mesh-layer.ts)。
渲染选项
| 参数 | 默认值 | 说明 |
|---|---|---|
sizeScale | 1 | 每个几何体的整体缩放倍数(min: 0),支持过渡动画 |
wireframe | false | 是否以线框模式渲染 mesh |
material | true | 应用于挤出多边形上的光照材质属性,配合光照效果(LightingEffect)使用,可配置项参见 使用光照指南 |
其中sizeScale在顶点着色器中直接对局部坐标做乘法:vec3 pos = (instanceModelMatrix * positions) * simpleMesh.sizeScale + instanceTranslation;(见 simple-mesh-layer-vertex.glsl.ts)。而wireframe的实现也相当直白——源码在每次状态更新时按需切换图元拓扑:
this.state.model.setTopology(this.props.wireframe ? 'line-strip' : 'triangle-list');不过需要留意源码注释的提醒:这是一种"快速而粗糙"的线框实现(用 LINE_STRIP 重画同一份 mesh),并不会严格沿原始网格的边绘制。
数据访问器(Accessors)
| 访问器 | 默认值 | 说明 |
|---|---|---|
getPosition | object => object.position | 获取每个对象在数据流中的中心锚点位置,支持过渡动画 |
getColor | [0, 0, 0, 255] | 若 mesh 不含顶点色,用它渲染每个对象;若含顶点色则二者混合;设为[255,255,255]保留原始颜色;指定texture后二者都被忽略。颜色格式[r,g,b,[a]],通道取值 0-255 |
getOrientation | [0, 0, 0] | 物体朝向,欧拉角[pitch, yaw, roll],单位为度,会与图层的modelMatrix复合 |
getScale | [1, 1, 1] | 沿各轴的缩放因子 |
getTranslation | [0, 0, 0] | 相对getPosition锚点的平移,[x, y, z]单位为米 |
getTransformMatrix | null | 显式提供 4x4 列主序模型矩阵,一旦提供将覆盖getOrientation、getScale、getTranslation |
每个访问器都遵循 deck.gl 的统一规则:传入数组则对所有对象生效(常量),传入函数则对每个对象分别求值。示例 app.jsx 用程序化生成的 10×10 网格数据演示了这种用法,每个数据点携带position、color、orientation三个字段:
const SAMPLE_DATA = (([xCount, yCount], spacing) => { const data = []; for (let x = 0; x < xCount; x++) { for (let y = 0; y < yCount; y++) { data.push({ position: [(x - (xCount - 1) / 2) * spacing, (y - (yCount - 1) / 2) * spacing], color: [(x / (xCount - 1)) * 255, 128, (y / (yCount - 1)) * 255], orientation: [(x / (xCount - 1)) * 60 - 30, 0, -90] }); } } return data; })([10, 10], 120);这里 120 是实例间距,10×10 共 100 辆"迷你库珀"(minicooper.obj),每个实例的朝向随 x 坐标在 -30° 到 30° 之间渐变,视觉上形成整齐又富有变化的阵列。
源码级原理:实例化矩阵的生成与坐标系统
这是 SimpleMeshLayer 最值得深挖的实现细节。从 initializeState 可以看到,图层在 AttributeManager 中注册了三类实例化(instanced)属性:
instancePositions:float64 类型(支持高精度 64 位坐标),由getPosition驱动;instanceColors:unorm8 类型,由getColor驱动;instanceModelMatrix:一个 12 元素复合属性,由getOrientation、getScale、getTranslation、getTransformMatrix四个访问器共同驱动。
顶点着色器(simple-mesh-layer-vertex.glsl.ts)中这 12 个元素被拆解为三个 3 分量矩阵列向量instanceModelMatrixCol0/1/2加上instanceTranslation,完成"旋转 × 缩放 → 平移"的实例变换。
四者如何协同由 matrix.ts 中的MATRIX_ATTRIBUTES.update决定:
- 若提供了
getTransformMatrix(16 元素矩阵),直接取其 3×3 旋转缩放部分与 3 分量平移(getExtendedMat3FromMat4),完全忽略另外三个访问器; - 否则用
calculateTransformMatrix把欧拉角(角度制,内部乘以Math.PI / 180转弧度)与缩放因子合成旋转缩放矩阵,再把getTranslation写入矩阵第 4 列。
更关键的细节在shouldComposeModelMatrix(matrix.ts):只有当坐标系为cartesian、meter-offsets,或默认坐标系且视口非地理空间时,实例变换才会与图层modelMatrix的旋转部分复合。在 LNGLAT / LNGLAT_OFFSET(经纬度)坐标系下该复合会被禁用,因为"旋转经纬度坐标"没有物理意义。这也是示例特意使用OrbitView+coordinateSystem: 'cartesian'的原因——在一个纯三维欧氏空间中展示模型阵列,避免地理坐标带来的投影复杂性(app.jsx)。
此外,着色器中的flatShading开关(由网格是否含法线决定)与hasTexture标志(由是否传入纹理决定)共同控制片元着色阶段的明暗与纹理采样,仓库中的 WebGPU 测试(test/modules/mesh-layers/simple-mesh-layer.spec.ts)也验证了无纹理与带纹理两种路径在 WebGPU 设备上均能正常初始化图层与模型。
组合示例:光照、阴影与地面
示例没有停留在"只画一层 mesh",还展示了两个重要的组合技巧(app.jsx):
- 光照效果:创建
AmbientLight(环境光)与DirectionalLight(平行光,开启_shadow: true投影)组成LightingEffect,挂到DeckGL的effects上——注意 WebGPU 模式下跳过光照(isWebGPU ? [] : [lightingEffect]),因为该路径暂未启用相关效果; - 阴影接收地面:用一个
SolidPolygonLayer绘制透明大平面(范围 -1000 到 1000,z=-40),作为阴影投射的承接面,与 SimpleMeshLayer 一起使用coordinateSystem: 'cartesian'。
OrbitView配合initialViewState(rotationX、rotationOrbit、fov: 30、zoom: 0)提供围绕目标点旋转观察的 3D 视口,near: 0.1, far: 2设置了相机裁剪平面。
扩展阅读
- 官方 API 参考:SimpleMeshLayer 文档
- 完整源码:modules/mesh-layers/src/simple-mesh-layer
- 模块导出入口:modules/mesh-layers/src/index.ts(同时导出
SimpleMeshLayer与ScenegraphLayer,后者用于加载 glTF 场景图) - 测试用例:test/modules/mesh-layers/simple-mesh-layer.spec.ts
- 教程配套示例目录:examples/website/mesh
【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考