完整指南:从 RFC 提案到扩展开发实战)
deck.gl 图层着色器钩子Layer Shader Hooks完整指南从 RFC 提案到扩展开发实战【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl导读图层着色器钩子Layer Shader Hooks是 deck.gl 为图层着色器设计的公开注入点 API它允许开发者在不复制、不修改图层原始 GLSL 源码的前提下向官方图层的顶点/片元着色器注入自定义逻辑用于实现尺寸变换、位置微调、颜色过滤等通用效果。本文以 layer-shader-hook-rfc.md 为骨架结合当前仓库中deck.gl/core、deck.gl/layers与deck.gl/extensions的真实实现完整讲解四大标准钩子的语义、VertexGeometry/FragmentGeometry结构体约定以及 Brushing、DataFilter、Clip、FillStyle 等官方扩展如何基于这些钩子构建可复用能力。读完本文你将能够编写自己的着色器注入模块并让它在同一主版本内的未来版本中保持兼容。一、背景为什么需要着色器钩子1.1 子类化图层时注入着色器的两大痛点在 deck.gl 中通过继承Layer子类化官方图层时开发者常常需要向图层的 GLSL 着色器注入自定义代码。RFC 指出此前只有两种做法且都存在明显缺陷复制并整体修改着色器开发者指南曾建议用户把整份着色器源码复制出来再修改。由于图层着色器被视作私有 API任何后续版本对这些着色器的改动都会直接破坏此类自定义图层非常脆弱。字符串注入shader injection早期的 brushing 示例和TripsLayer采用动态字符串拼接把自定义代码拼进基础着色器。这种方式虽然更稳健一些但仍有四个遗留问题子类可读性差因为它不包含完整的着色器代码注入通常依赖对基础图层实现的假设如 attribute 名、varying 名未来版本中极易失效每个图层的 attribute 各不相同注入代码难以在图层间移植标准的注入点#decl、#main-start、#main-end能力非常有限——如果子类想在投影发生前修改着色器中的中间值例如ScatterplotLayer顶点着色器中的offset就必须依靠基础着色器中的关键字符串进行注入这更加脆弱。1.2 契机luma.gl v7.1 的 shader hook 系统RFC 提出时luma.gl v7.1 已经引入了新的 shader hooks 机制createShaderHook使得为着色器创建公开 API成为可能。deck.gl 的 RFC 正是要在这套机制之上定义一组标准的、公开文档化的图层着色器钩子。从当前仓库源码可以看到这套机制已经在 modules/core/src/shaderlib/index.ts 中被固化下来deck.gl 在getShaderAssembler()中通过shaderAssembler.addShaderHook(...)统一注册了四个钩子声明const SHADER_HOOKS_GLSL [ vs:DECKGL_FILTER_SIZE(inout vec3 size, VertexGeometry geometry), vs:DECKGL_FILTER_GL_POSITION(inout vec4 position, VertexGeometry geometry), vs:DECKGL_FILTER_COLOR(inout vec4 color, VertexGeometry geometry), fs:DECKGL_FILTER_COLOR(inout vec4 color, FragmentGeometry geometry) ];这意味着只要一个图层在着色器中声明并调用这些钩子任何附加到该图层的扩展Extension或自定义 shader module都可以通过inject机制向这些钩子注入代码而不需要了解该图层着色器的内部实现细节。二、目标用例钩子要解决的真实场景RFC 明确了四个目标标准化图层着色器让不熟悉 WebGL 的人也能轻松微调同一套注入代码可复用于多种图层自定义图层在同一主版本内的后续版本中保持可用在此基础上把图层和图层扩展打包为可复用组件分发。RFC 还给出了三个具体用例它们恰好都已在当前仓库中落地为官方扩展2.1 通用 GPU 数据过滤DataFilterExtension基础版 GPU 数据过滤见>struct VertexGeometry { vec4 position; // 公共空间位置仅投影后可用 vec3 worldPosition; // 与当前顶点关联的主世界空间位置 vec3 worldPositionAlt;// 次世界空间位置仅边/线段类实例可用 vec3 normal; // 公共空间法线 vec2 uv; // 顶点的 uv vec3 pickingColor; // 拾取颜色 };各字段的语义映射来自 RFC图层类型worldPosition语义worldPositionAlt语义SolidPolygonLayer顶点位置来自getPolygon侧面下一个顶点位置来自getPolygonBitmapLayer顶点位置来自bounds空PathLayer当前线段起点来自getPath当前线段终点来自getPathArcLayer / LineLayer源位置来自getSourcePosition目标位置来自getTargetPosition其他所有图层对象中心来自getPosition空3.3 片元几何结构体 FragmentGeometry片元着色器侧的结构体当前只暴露一个字段vec2 uv即该片元的 UV 坐标GLSL 定义见 geometry.ts。值得注意的是仓库还提供了对应的 WGSL 版本geometry.tsVertexGeometry与FragmentGeometry在 WGSL 中同样可用不过目前 shaderlib/index.ts 中SHADER_HOOKS_WGSL尚为空数组Not yet supported即四个标准钩子当前仅在 GLSL 管线中注册。3.4 四个标准钩子逐一解析vs:DECKGL_FILTER_SIZE(inout vec3 size, VertexGeometry geometry)在投影之前被调用用于修改与当前顶点关联的 common space 尺寸。RFC 给出的各图层语义图层类型size的语义ScatterplotLayer / IconLayer / PointCloudLayer / ColumnLayer相对于中心的偏移PathLayer / ArcLayer / LineLayer当前顶点的挤出量extrusionSimpleMeshLayer局部位置local positionSolidPolygonLayer / BitmapLayer不适用在 scatterplot-layer-vertex.glsl.ts 中可以看到该钩子的真实调用位置——顶点着色器在计算出offset包含半径、描边宽度、像素偏移等之后、执行投影之前调用if (scatterplot.billboard) { gl_Position project_position_to_clipspace(instancePositions, instancePositions64Low, vec3(0.0), geometry.position); DECKGL_FILTER_GL_POSITION(gl_Position, geometry); vec3 offset edgePadding * positions * outerRadiusPixels; offset.xy instancePixelOffset; DECKGL_FILTER_SIZE(offset, geometry); // -- 尺寸钩子 gl_Position.xy project_pixel_size_to_clipspace(offset.xy); } else { vec3 offset edgePadding * positions * project_pixel_size(outerRadiusPixels); offset.xy project_pixel_size(instancePixelOffset); DECKGL_FILTER_SIZE(offset, geometry); // -- 尺寸钩子 gl_Position project_position_to_clipspace(instancePositions, instancePositions64Low, offset, geometry.position); DECKGL_FILTER_GL_POSITION(gl_Position, geometry); }vs:DECKGL_FILTER_GL_POSITION(inout vec4 position, VertexGeometry geometry)在投影之后被调用用于修改当前顶点的裁剪空间位置即gl_Position。这是扩展最常用的钩子之一因为此时geometry.position已经填充了公共空间位置可以放心读取。vs:DECKGL_FILTER_COLOR(inout vec4 color, VertexGeometry geometry)在投影之后被调用用于修改当前顶点的颜色顶点着色器阶段的颜色修改。在 ScatterplotLayer 中填充色与描边色都会分别经过一次该钩子scatterplot-layer-vertex.glsl.tsvFillColor vec4(instanceFillColors.rgb, instanceFillColors.a * layer.opacity); DECKGL_FILTER_COLOR(vFillColor, geometry); vLineColor vec4(instanceLineColors.rgb, instanceLineColors.a * layer.opacity); DECKGL_FILTER_COLOR(vLineColor, geometry);fs:DECKGL_FILTER_COLOR(inout vec4 color, FragmentGeometry geometry)在片元着色器中修改当前片元的颜色。这是被官方扩展使用频率最高的钩子常配合discard语句实现片元级剔除。四、源码级验证官方图层如何接入钩子标准钩子并非只在 RFC 中纸上谈兵当前仓库的绝大多数官方图层都在着色器中声明并调用了它们。以下是在 modules/layers、modules/geo-layers、modules/aggregation-layers、modules/mesh-layers、modules/carto 下均可检索到钩子调用的部分图层基础图层layersScatterplotLayer、ArcLayer、LineLayer、PathLayer、IconLayer、ColumnLayer、PointCloudLayer、SolidPolygonLayer、BitmapLayer、TextLayer 的 multi-icon-layer 与 text-background-layer地理图层geo-layersTripsLayer、MeshLayer网格图层mesh-layersSimpleMeshLayer、ScenegraphLayer聚合图层aggregation-layersGridLayer、HexagonLayer、ScreenGridLayer、HeatmapLayerCartoRasterLayer。这意味着你在使用这些图层时为它们附加任意一个基于标准钩子的扩展都能正常工作无需针对图层类型编写分支。五、官方扩展实战钩子的四种注入范式deck.gl/extensions中的四个官方扩展恰好构成了标准钩子的完整教科书它们的inject定义展示了每种钩子的典型用法。注入的 key 可以直接是钩子名如vs:DECKGL_FILTER_SIZE也可以是经典注入点如vs:#main-start、vs:#main-end、vs:#decl、fs:#decl。5.1 BrushingExtension位置读取与片元剔除刷选需要比较鼠标位置与对象位置。其 shader moduleshader-module.ts在顶点钩子中利用geometry.worldPosition/geometry.worldPositionAlt计算可见性再在片元钩子中discard不可见像素// vs:DECKGL_FILTER_GL_POSITION 注入 vec2 brushingTarget; vec2 brushingSource; if (brushing.target 3) { // source_target brushingTarget geometry.worldPositionAlt.xy; brushingSource geometry.worldPosition.xy; } else if (brushing.target 0) { // source brushingTarget geometry.worldPosition.xy; } else if (brushing.target 1) { // target brushingTarget geometry.worldPositionAlt.xy; } else { // custom brushingTarget brushingTargets; } bool visible; if (brushing.target 3) { visible brushing_arePointsInRange(brushingSource, brushingTarget); } else { visible brushing_isPointInRange(brushingTarget); } brushing_setVisible(visible); // fs:DECKGL_FILTER_COLOR 注入 if (brushing.enabled brushing_isVisible 0.5) { discard; }注意该模块把geometry变量来自 geometry.ts 的VertexGeometry当作共享结构体使用这正是 deck.gl 将geometry注册为默认 shader moduleDEFAULT_MODULES见 shaderlib/index.ts的原因。5.2 DataFilterExtension尺寸缩放 颜色变换 顶点塌缩数据过滤扩展shader-module.ts同时演示了三种钩子组合vs:#main-start计算过滤值dataFilter_valuevs:#main-end若被过滤则将顶点投影到vec4(0.)把顶点塌缩到原点实现隐藏vs:DECKGL_FILTER_SIZE按过滤值缩放size实现圆点进入/退出时间窗口时的生长/收缩动画RFC 中 kepler.gl 期望的效果fs:DECKGL_FILTER_COLOR被过滤片元discard或通过color.a * dataFilter_value实现半透明过渡。// vs:DECKGL_FILTER_SIZE 注入 if (dataFilter.transformSize) { size size * dataFilter_value; } // fs:DECKGL_FILTER_COLOR 注入 if (dataFilter_value 0.0) discard; if (dataFilter.transformColor) { color.a * dataFilter_value; }从 getUniforms 可以看到filterRange、filterSoftRange、filterEnabled、filterTransformSize、filterTransformColor等 props 最终被换算为 GPU uniform含 64 位高精度拆分getUniforms64。5.3 ClipExtension实例级/片元级两种裁剪策略Clip 扩展clip-extension.ts展示了钩子在不同策略下的用法按实例裁剪clipByInstance: true在vs:DECKGL_FILTER_GL_POSITION中基于geometry.worldPosition.xy判断整个对象是否在裁剪矩形内按片元裁剪clipByInstance: false顶点钩子把geometry.position.xy传出片元钩子在fs:DECKGL_FILTER_COLOR中逐像素判断是否丢弃用于展示与裁剪矩形相交的多边形局部。// 实例级 vs:DECKGL_FILTER_GL_POSITION: clip_isVisible float(clip_isInBounds(geometry.worldPosition.xy)); , fs:DECKGL_FILTER_COLOR: if (clip_isVisible 0.5) discard; , // 片元级 vs:DECKGL_FILTER_GL_POSITION: clip_commonPosition geometry.position.xy; , fs:DECKGL_FILTER_COLOR: if (!clip_isInBounds(clip_commonPosition)) discard; ,另外Clip 扩展在 WebGPU 设备上会返回空 shader 配置clip-extension.ts印证了标准钩子当前仅支持 GLSL 管线的限制。5.4 FillStyleExtension纹理采样与图案混合图案填充扩展shader-module.ts在vs:DECKGL_FILTER_GL_POSITION中投影已完成geometry.position可用计算纹理 UV并在fs:DECKGL_FILTER_COLOR中采样 sprite 图案、与背景色做 Porter-Duff source-over 混合// fs:DECKGL_FILTER_COLOR 注入节选 if (fill.patternEnabled) { vec2 patternUV fract(fill_uv); vec2 texCoords fill_patternBounds.xy fill_patternBounds.zw * patternUV; vec4 grad fill_patternBounds.zwzw * vec4(dFdx(fill_uv), dFdy(fill_uv)); vec4 patternColor textureGrad(fill_patternTexture, texCoords, grad.xy, grad.zw); color.a * patternColor.a; if (!fill.patternMask) { color.rgb patternColor.rgb; } color fill_blendOverBackground(color, fill_backgroundColor); }该模块还演示了如何把图层 propsfillPatternEnabled、fillPatternMask、fillPatternSizeUnits等通过getUniforms转换成 uniform并依赖project模块dependencies: [project]读取视口信息——这正是 RFC 所说把扩展打包为可复用组件的典型结构。六、扩展开发实践如何编写自己的钩子注入综合以上官方扩展的实现一个自定义扩展的 shader module 具备如下骨架以注入fs:DECKGL_FILTER_COLOR为例import type {ShaderModule} from luma.gl/shadertools; export const myShaderModule: ShaderModule { name: my-effect, vs: /* 可选顶点着色器源码 */, fs: /* 可选片元着色器源码 */, // 关键向标准钩子注入代码 inject: { fs:DECKGL_FILTER_COLOR: /* glsl */ // 此处可以安全地使用 geometry.uvFragmentGeometry color.rgb mix(color.rgb, targetColor, strength); }, getUniforms: (opts) { /* 把 JS props 换算为 GPU uniform */ }, uniformTypes: { /* 声明 uniform 类型供 WebGPU 等管线使用 */ } };然后把该 module 挂到自定义的LayerExtension的getShaders()返回的modules数组中即可应用到任意已接入标准钩子的官方图层上。需要注意的兼容性边界钩子声明由 getShaderAssembler 统一注册当前仅 GLSL 生效若设备为 WebGPU部分扩展会静默降级如 ClipExtension 返回空配置geometry变量由默认模块注入自定义 module 中可直接读取但不要覆盖其定义颜色已归一化到[0, 1]坐标默认是 common space非 common space 的值需在命名与注释中显式说明。七、与经典注入点的关系及演进RFC 明确指出现有注入点#decl、#main-start、#main-end能力有限——它们只能在着色器头/尾插入代码无法安全地修改投影前的中间量。标准钩子的出现把注入从位置约定升级为语义化 API#decl/#main-start/#main-end仍被广泛使用例如 DataFilterExtension 在#main-start计算过滤值、ClipExtension 在#decl声明 varying用于准备数据DECKGL_FILTER_*钩子则用于在精确时机改写关键量钩子内部的geometry结构体保证了对各图层语义的一致抽象。两者互补扩展作者常用经典注入点声明辅助变量/计算中间结果再用标准钩子完成对尺寸、位置、颜色的最终改写。这正是 shaderlib/index.ts 同时保留两种机制的设计意图。结语Layer Shader Hooks 把 deck.gl 图层的着色器从黑盒私有实现转变成了带语义化扩展点的公开 API。从本文的源码对照可以看出四个标准钩子vs:DECKGL_FILTER_SIZE、vs:DECKGL_FILTER_GL_POSITION、vs:DECKGL_FILTER_COLOR、fs:DECKGL_FILTER_COLOR配合VertexGeometry/FragmentGeometry结构体已在所有主流官方图层中落地并支撑起了 Brushing、DataFilter、Clip、FillStyle 等通用扩展这也使一套注入代码同时适用于多个图层、且能在同一主版本内跨版本保持稳定成为现实。对于想要扩展 deck.gl 渲染能力的开发者掌握这套钩子机制是进入官方扩展生态最直接、最兼容的路径。【免费下载链接】deck.glWebGL2 powered visualization framework项目地址: https://gitcode.com/GitHub_Trending/de/deck.gl创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考