deck.gl PointLight 点光源完全指南:参数详解、坐标系统与实战接入
【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl
导读
PointLight是 deck.gl 核心库中用于模拟"从某一点向四面八方均匀发光"的光源类,是 LightingEffect 支持的三种基础光源(环境光 AmbientLight、平行光 DirectionalLight、点光源 PointLight)之一。本文以 point-light.md 文档为主线,结合 核心源码 与官方示例,完整讲解 PointLight 的构造参数、光照衰减模型、地理/非地理坐标投影机制,以及如何在 JS、React 与 pydeck 中把它接入渲染管线,让读者可以独立为柱状图、热力图、3D 模型等 2.5D/3D 图层配置逼真的点光源照明。
PointLight 是什么
点光源(Point Light)是计算机图形学中最基础的光源模型之一:它有一个确定的空间位置,光线从该点向所有方向均匀辐射,因此物体不同朝向的表面会因与光源的方位关系产生明暗差异,从而呈现出立体感。
在 deck.gl 中,PointLight类的定位非常纯粹:
- 它只描述光源本身(颜色、强度、位置、衰减系数),不参与渲染;
- 实际的照明计算由
LightingEffect统一驱动,后者会把所有注册的光源投影到图层坐标系后交给 shader 模块处理; - 光照只作用于支持
material属性的 2.5D(如挤出的HexagonLayer、PolygonLayer)或 3D(如PointCloudLayer、SimpleMeshLayer)图层,参见 using-effects.md 中的 "Material Settings" 一节。
注意:原文档注明"最多支持 5 个方向光",实际该数量限制来自底层 luma.gl 光照 shader 模块对光源总数的约束,点光源同样计入总光源预算,配置过多光源时需按需取舍。
构造 PointLight
构造函数签名
const pointLight = new PointLight({color, intensity, position, attenuation});所有参数均为可选,全部省略时使用源码中的默认值(见 point-light.ts):
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
color | number[3] | [255, 255, 255] | 光源颜色,RGB 各通道取值 0–255 |
intensity | number | 1.0 | 光源强度,数值越大越亮 |
position | number[3] | [0, 0, 1] | 光源位置,坐标系统取决于当前视图(详见下文) |
attenuation | number[3] | [1, 0, 0] | 衰减系数[C_constant, C_linear, C_quadratic],默认不随距离衰减 |
原文档给出的最小可用示例:
const pointLight = new PointLight({ color: [128, 128, 0], intensity: 2.0, position: [0, 0, 200] });源码层面的构造细节
阅读 point-light.ts 的实现可以发现几个容易被忽视的行为:
- 自动生成 id:构造函数不要求传入
id,未指定时自动生成point-${idCount++}(模块级计数器自增),便于在LightingEffect内部区分多个光源; - 类型标记:实例上固定
type = 'point',LightingEffect正是依据该标记将光源归类到pointLights数组(见 lighting-effect.ts); - 投影缓存:构造时通过
this.projectedLight = {...this}复制一份"投影后"的光源,每次渲染时复用该对象、只更新投影后的坐标,避免频繁分配内存。
光照衰减模型
attenuation是 PointLight 最有特色的参数,它根据光源到表面的距离D按如下公式削减光照强度:
Intensity = Intensity / (C_constant + C_linear * D + C_quadratic * D * D)三个系数的物理含义:
C_constant(常数项):距离无关的基础衰减,通常设为 1;C_linear(线性项):随距离线性衰减,适合模拟较近范围的光照;C_quadratic(二次项):随距离平方衰减,更接近真实世界光强按距离平方反比衰减的物理规律。
文档给出的两条实用建议:
- 物理近似:需要(近似)物理正确的衰减时使用
attenuation: [1, 0, n],其中n为二次项系数,取值越大光照衰减越剧烈; - 默认关闭衰减:默认值
[1, 0, 0]意味着分母恒为 1,光源强度不随距离变化——适合希望光照均匀覆盖整个场景的情况。
源码中 getAttenuation 的实现非常直白:显式传入attenuation则原样使用,否则回退到DEFAULT_ATTENUATION = [1, 0, 0]。
position 的坐标系统:地理视图与非地理视图
这是 PointLight 最容易踩坑的地方:position的坐标语义取决于当前 deck.gl 视图(View)的类型,原文档明确指向 deck.md#views:
- 地理视图(geospatial views):如
MapView、GlobeView,position 解释为[longitude, latitude, altitude](经度、纬度、海拔高度,单位一般为米),光源可直接"钉"在地球表面某一点上空; - 非地理视图(non-geospatial views):如
OrthographicView、OrbitView,position 解释为普通的世界坐标[x, y, z]。
这一转换在源码中由getProjectedLight完成(point-light.ts):
const position = projectPosition(this.position, { viewport, coordinateSystem, coordinateOrigin, fromCoordinateSystem: viewport.isGeospatial ? COORDINATE_SYSTEM.LNGLAT : COORDINATE_SYSTEM.CARTESIAN, fromCoordinateOrigin: [0, 0, 0] });其核心逻辑是:判断当前 viewport 是否地理空间(viewport.isGeospatial),若是则把 position 当作LNGLAT(经纬度)输入,否则当作CARTESIAN(笛卡尔)输入,再通过projectPosition统一投影到图层的公共坐标空间。也就是说,光源坐标与图层数据遵循同一套坐标系统,LightingEffect文档中的备注"Point light position uses the same coordinate system as view state"(lighting-effect.md)正是这个意思。
为什么按图层逐个投影
注意getProjectedLight的签名是getProjectedLight({layer}),原因是 deck.gl 中不同图层可能使用不同的坐标系统与坐标原点(coordinateSystem、coordinateOrigin)。因此 lighting-effect.ts 的_getLights会逐图层调用pointLight.getProjectedLight({layer}),为每个图层生成投影后的光源列表,再统一交给光照 shader。测试用例 lighting-effect.spec.ts 验证了这一点:在MapView视口下,CameraLight的默认位置[0, 0, 1]被投影为[0, 0, 0.018310546875],而手动修改后的PointLight颜色[255, 0, 0]被原样导出到 shader 属性中。
在应用中接入 PointLight
方式一:JS / TypeScript(Deck 实例)
PointLight本身不直接渲染,必须挂在LightingEffect上,再通过Deck的effects参数启用。官网 3D 热力图示例(3d-heatmap/app.tsx)展示了标准组合:环境光打底 + 两个点光源营造局部高光:
import {Deck, AmbientLight, PointLight, LightingEffect} from '@deck.gl/core'; const ambientLight = new AmbientLight({ color: [255, 255, 255], intensity: 1.0 }); const pointLight1 = new PointLight({ color: [255, 255, 255], intensity: 0.8, position: [-0.144528, 49.739968, 80000] // 经度、纬度、海拔(米) }); const pointLight2 = new PointLight({ color: [255, 255, 255], intensity: 0.8, position: [-3.807751, 54.104682, 8000] }); const lightingEffect = new LightingEffect({ambientLight, pointLight1, pointLight2}); const deckInstance = new Deck({ initialViewState: { longitude: -1.415727, latitude: 52.232395, zoom: 6.6, pitch: 40.5, bearing: -27 }, controller: true, effects: [lightingEffect] // 关键:把光照效果加入渲染管线 });方式二:React(DeckGL 组件)
React 用法几乎一致,只是把effects传给<DeckGL>组件:
import {DeckGL} from '@deck.gl/react'; import {AmbientLight, PointLight, LightingEffect} from '@deck.gl/core'; const lightingEffect = new LightingEffect({ ambientLight: new AmbientLight({color: [255, 255, 255], intensity: 1.0}), pointLight1: new PointLight({color: [255, 255, 255], intensity: 0.8, position: [-0.14, 49.7, 80000]}) }); function App() { return ( <DeckGL initialViewState={{longitude: -1.4, latitude: 52.2, zoom: 6.6, pitch: 40.5}} controller effects={[lightingEffect]} > {/* 地图底图组件 */} </DeckGL> ); }方式三:pydeck(Python 绑定)
pydeck 通过pdk.Effect以声明式 JSON 描述光源,属性名自动做 snake-case 到 camel-case 转换(见 effect.rst)。官方示例 point_light.py 完整演示了用点光源照亮多边形底面和柱状图:
import pydeck as pdk light = pdk.Effect( "PointLight", color=[255, 150, 90], intensity=3.0, position=[-1.5, -1, 180000], ) lighting = pdk.Effect("LightingEffect", gallery_light=light) deck = pdk.Deck( layers=layers, # 包含 material=True 的 PolygonLayer / ColumnLayer effects=[lighting], initial_view_state=initial_view_state, map_provider=None, show_error=True, ) deck.to_html("point_light.html", css_background_color="#111827")注意其中的图层都设置了material=True——这与 JS 侧一致:只有声明了 material 的图层才会参与光照计算。
与 LightingEffect 的协作机制
把PointLight接入LightingEffect后,其完整工作流程可以归纳为(对应 lighting-effect.ts 的setProps):
- 分类注册:
LightingEffect遍历构造时传入的所有光源,依据type字段分别归入ambientLight、directionalLights、pointLights三个集合; - 默认光源兜底:若用户一个光源都没提供,
_applyDefaultLights会自动注入一个白色环境光(intensity 1.0)加两个方向光(lighting-effect.ts),所以不配置任何 effects 也能看到立体效果; - 逐图层投影:
getShaderModuleProps阶段调用_getLights,为每个图层生成投影后的光源列表并写入lightingshader 模块属性; - shader 消费:图层顶点/片元着色器结合
phongMaterial/gouraudMaterial(即图层material属性)完成 Phong 光照着色计算。
与另外两种光源的对比:
| 特性 | AmbientLight | DirectionalLight | PointLight |
|---|---|---|---|
| 有无位置 | 无 | 只有方向 | 有位置(可衰减) |
| 光源数量 | 仅 1 个 | 多个 | 多个(默认[]) |
| 距离衰减 | 不适用 | 不适用 | 支持(attenuation) |
| 典型场景 | 打底避免全黑 | 模拟太阳等远距离光 | 模拟灯泡、路灯等局部光源 |
对应测试 lighting-effect.spec.ts 也验证了:空构造的LightingEffect会自动创建 1 个环境光和 2 个方向光,这正是默认光照的由来。
与图层 material 属性配合
要让 PointLight 真正在视觉上生效,目标图层必须设置material属性。material 是一个普通 JS 对象,控制表面如何响应光照(默认值见 using-effects.md):
ambient(0–1,默认0.35):环境光反射系数;diffuse(0–1,默认0.6):漫反射系数,决定点光源直射面的亮度;shininess(>0,默认32):高光锐度,越大高光越集中;specularColor(number[3],RGB 各通道 0–1,默认[0.15, 0.15, 0.15]):镜面高光颜色。
设置为true时全部采用默认值。例如:
new GeoJsonLayer({ id: 'geojson-layer', data: '/path/to/data.geo.json', extruded: true, // 光照只作用于挤出多边形 getElevation: f => f.properties.height, material: { ambient: 0.8, specularColor: [0.3, 0.1, 0.2] } });在 3D 热力图示例中,HexagonLayer的 material 被配置为{ambient: 0.64, diffuse: 0.6, shininess: 32, specularColor: [51, 51, 51]}(3d-heatmap/app.tsx),配合两个位置不同的点光源,柱体侧面呈现出从暗到亮的光照渐变,立体感显著增强。
调试与验证建议
- 确认图层参与光照:检查图层是否设置了
material,且属于 2.5D/3D 图层(如挤出的聚合图层、PointCloudLayer、SimpleMeshLayer); - 确认 effects 已挂载:
effects: [lightingEffect]是必须步骤,且注意覆盖默认光照——一旦自定义LightingEffect中没有环境光,未受点光源直射的面可能接近全黑,建议保留一个低强度AmbientLight; - 地理视图下的位置单位:
position的第三维(altitude)通常以米为单位,示例中 3D 热力图使用了80000/8000米级别的海拔,配合elevationScale才能看到明显效果; - 回归测试:仓库测试 lighting-effect.spec.ts 覆盖了光源构造、shader 属性导出、阴影 pass 创建与清理等关键路径,修改光照相关代码后应保持这些用例通过。
小结
PointLight 为 deck.gl 场景提供了基于位置的局部照明能力,核心要点可归纳为四条:四个构造参数全部可选且均有默认值;attenuation支持按距离衰减的物理光照模型;position的语义随视图类型(地理/非地理)自动切换,由getProjectedLight结合projectPosition完成投影;必须通过LightingEffect挂载并配合图层的material属性才能生效。理解这些机制,即可像官方 3D 热力图与 pydeck 示例那样,用少量代码为三维场景营造出富有层次的光照氛围。
关键参考路径
- API 文档:point-light.md、lighting-effect.md
- 实现源码:modules/core/src/effects/lighting/point-light.ts、modules/core/src/effects/lighting/lighting-effect.ts
- 官方示例:examples/website/3d-heatmap/app.tsx、bindings/pydeck/examples/lighting/point_light.py
- 开发者指南:docs/developer-guide/using-effects.md
- 测试用例:test/modules/core/effects/lighting-effect.spec.ts
【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考