- 图形学
- 游戏开发
- 3D渲染
【免费下载链接】Babylon.js
Babylon.js is a powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework.
网格混合(Mesh Blending)是 Babylon.js 中一个针对WebGL2 与 WebGPU的屏幕空间 SceneColor 后处理效果,用于柔化不透明或 alpha-test 网格之间可见的接触缝隙,让石头压进地面、植被插入山坡这类“假接触”场景不再出现生硬接缝。本文以 specs/mesh-blending/architecture.md 为骨架,结合packages/dev/core中的实现源码,完整讲解其标签编码规则、四档半径类、两种深度表示、四档质量等级、调试视图,以及经典后处理与 FrameGraph/NRGE 两种接入方式,读完即可在 Babylon.js 中配置并使用该效果。
效果定位与使用边界
从架构文档的定义看,Mesh Blending 是一个纯视觉(visual)效果:
- 它只作用于 SceneColor,用于柔化不透明或 alpha-tested 网格之间可见的接触;
- 不合并几何体(不改变顶点/网格结构);
- 不改变碰撞形状、深度缓冲、法线缓冲、间接光照、阴影贴图与阴影几何;
- 透明物体的渲染完全由调用方自行控制(caller-controlled)。
这一点在 MeshBlendingPostProcess 的类注释 中同样被强调:效果不会修改 geometry、collision queries、depth、normals 或 shadow geometry。因此它适合解决的是“接触处漏光/接缝”这一视觉问题,而不是几何布尔或物理碰撞问题。
网格标签(Mesh Tags)与半径类
标签编码:groupId 与 radiusClass 打包进一个字节
为网格开启混合,需要在网格上写入AbstractMesh.meshBlendingTag,其值由PackMeshBlendingTag(groupId, radiusClass)构造。源码中该函数位于 packages/dev/core/src/Meshes/meshBlendingTag.ts,编码规则如下:
groupId必须是 0~63 的整数(越界会抛出RangeError);radiusClass必须是 0~3 的整数(对应MeshBlendingRadiusClass枚举);- 返回值
(radiusClass << 6) | groupId,即组 ID 占用低 6 位,半径类占用高 2 位。
AbstractMesh.meshBlendingTag的 setter(见 abstractMesh.pure.ts)会校验:值必须为 0,或包含 1~63 之间的组 ID,否则抛出RangeError。同时提供逆操作UnpackMeshBlendingTag(tag)将打包值解码为{ groupId, radiusClass },并对超出 0~255 的输入抛错。
分组语义
架构文档定义了清晰的组规则,源码注释完全一致:
- 组
0禁用混合(标签为 0 即不参与); - 组
1..63由应用自行分配,代表逻辑组; - 同一非零组内的像素互不混合——同组网格被当作一个逻辑物体,例如构成同一栋建筑的多个 mesh 之间不应出现接缝软化;
- 不同非零组的像素在通过边界(boundary)、深度(depth)、跨度(span)与接触角(contact-angle)校验后可以混合。
源码中PackMeshBlendingTag的文档(meshBlendingTag.ts)与AbstractMesh.meshBlendingTag注释均确认了“同组视为一个逻辑表面”的语义。
四档半径类
MeshBlendingRadiusClass枚举(meshBlendingTag.ts)提供四档半径类:
| 枚举值 | 数值 | 含义 |
|---|---|---|
Small | 0 | 小混合半径 |
Medium | 1 | 中等混合半径 |
Large | 2 | 大混合半径 |
ExtraLarge | 3 | 超大混合半径 |
每个半径类组合了两个可配置量(定义见 thinMeshBlendingPostProcess.ts):
worldRadius:以 Babylon 世界单位编写的混合半径;minimumProjectedRadius:以物理渲染目标像素为单位的最小投影半径。
CreateDefaultMeshBlendRadiusDefinitions()提供默认值(thinMeshBlendingPostProcess.ts):
| 半径类 | worldRadius | minimumProjectedRadius |
|---|---|---|
| Small | 0.06 | 1.5 |
| Medium | 0.1 | 3 |
| Large | 0.2 | 3 |
| ExtraLarge | 0.3 | 5 |
在接触处,两个参与表面中较小的半径类生效。最小投影半径按物理像素计量,作用是让远处的接缝依然可见(近大远小投影后世界半径变小,像素下限兜底);世界半径则保证近处宽度稳定。透视相机与正交相机都支持,包括反转深度(reverse-depth)渲染。
从源码可进一步看到半径的投影逻辑:_ProjectMeshBlendWorldRadiusToPixels使用0.5 * renderTargetHeight * |projectionYScale|将世界半径换算为物理像素,正交相机不除以深度、透视相机除以视深;随后_CalculateMeshBlendSearchRadius取max(投影半径, minimumProjectedRadius)再乘以质量等级的radiusScale(thinMeshBlendingPostProcess.ts)。半径定义对象还带有运行时校验 setter,拒绝NaN、负数等非法值(见 thinMeshBlendingPostProcess.ts)。
实例的限制
实例(Instances)与薄实例(Thin Instances)使用其源网格的标签,不支持逐实例的 mesh-blending 标签。这属于架构文档明确声明的限制,接入时需要将标签设置在源网格上。
几何输入与后端支持
输入纹理清单
效果消费以下**单采样(single-sampled)**几何纹理:
- SceneColor(场景颜色);
- 打包的 mesh-blending 标签纹理——
R8UI格式、最近邻采样、无 mipmap; - 视空间或屏幕空间深度(View / Screen depth);
- 可选的线性基色/Albedo 纹理,用于阴影估计(shadow estimation)。
架构文档与源码双重确认:SceneColor 与所有提供的几何输入必须具有相同的物理尺寸和采样覆盖(sample coverage)。经典后处理的_ValidateInputTextureDimensions会逐一校验 tag/depth/baseColor 的宽高与 samples 是否一致,不一致即抛错(见 meshBlendingPostProcess.ts)。
各输入纹理的格式约束在经典包装的校验函数中非常明确(meshBlendingPostProcess.ts):
- 标签纹理:必须是 2D、
TEXTURETYPE_UNSIGNED_BYTE+TEXTUREFORMAT_RED_INTEGER、最近邻采样、无 mipmap、单采样; - 深度纹理:2D,格式为 RED/RG/RGBA,类型为 FLOAT 或 HALF_FLOAT;仅当使用
MeshBlendDepthType.Screen时才允许归一化无符号字节(UNSIGNED_BYTE);无 mipmap、单采样; - 基色纹理(可选):2D、非整数的 RGB/RGBA 颜色格式(UNSIGNED_BYTE / HALF_FLOAT / FLOAT)、无 mipmap、单采样。
alpha-tested 片元只有在通过材质配置的 alpha 截止值(alpha cutoff)后才会写入标签,因此被剔除的片元不会留下“幽灵接缝”。当省略基色/Albedo 时,阴影估计与所有基色采样全部编译剔除(compiled out),不需要回退纹理或额外的几何附件。
后端范围:WebGL2 与 WebGPU
- 支持WebGL2 与 WebGPU;
- Native 被拒绝,原因是其当前纹理格式映射无法创建所需的
R8UI标签渲染目标; - WebGL1 同样不支持。
支持判断逻辑集中在_IsMeshBlendingSupported(meshBlendingTag.ts):要求shaderPlatformName !== "NATIVE"且(isWebGPU或webGLVersion === 2)。单元测试也覆盖了这两种拒绝场景(meshBlendingPostProcess.test.ts)。
透明网格的注意事项
透明网格被允许,但应用必须自行保证它们不会让 SceneColor 与单层的深度、Albedo、标签输入不一致。具体约束:
- WebGL2 需要每目标混合参数(per-target blend-parameter)支持:混合须对颜色输出保持开启,同时对整数标签附件关闭;不支持该能力的配置若请求透明标签渲染,会被拒绝。从 geometryRendererTask.ts 可见,当
renderTransparentMeshes || renderSprites || renderParticles且非 WebGPU、无blendParametersPerTarget能力时会触发校验/拒绝逻辑; - 与参与混合的表面**在屏幕空间不相交(screen-space-disjoint)**的透明网格可以安全渲染;
- 重叠的透明表面会独立于 SceneColor 覆盖或混合几何输入,可能产生空洞、假边界或错误颜色;
- 推荐配置:在网格混合之后合成透明内容(compositing transparent content after mesh blending)。
两种接入方式:Classic 渲染器与 FrameGraph
Classic 渲染器(经典后处理)
经典路径的接入步骤:
- 启用
GeometryBufferRenderer所需的输出,将samples设为1,并开启 mesh-blending 标签输出; - 仅在需要阴影估计时提供对齐的基色纹理;
- 用这些调用方自有的纹理构造
MeshBlendingPostProcess。
关键语义:后处理绝不会启用、重配置、调整尺寸或销毁 GeometryRenderer 及其提供的纹理,因此调用方创建的输入必须随 SceneColor 输入一起 resize。单元测试中"does not dispose caller-owned input textures"明确验证了这一点(meshBlendingPostProcess.test.ts)。
构造参数定义在IMeshBlendingPostProcessOptions(meshBlendingPostProcess.ts):
meshBlendTagTexture(必填):打包的R8UI标签纹理;depthTexture(必填):与标签纹理对齐的视深或屏幕深度纹理;baseColorTexture(可选):与 SceneColor 对齐的线性基色/Albedo 纹理,省略则编译剔除阴影估计;- 继承自
IMeshBlendConfiguration的quality、radiusClasses、slopeFactor、depthType、debugMode; effectWrapper(可选):调用方自有的ThinMeshBlendingPostProcess包装,同一时刻只能附着到一个经典后处理上(_ClaimMeshBlendingEffectWrapper用 WeakMap 强制独占,见 thinMeshBlendingPostProcess.ts)。
经典包装是纯运行时对象:因其几何输入是调用方自有的运行时资源,它不能被序列化、解析或克隆。源码中serialize()直接抛错、clone()返回null,并在构造时设置doNotSerialize = true(meshBlendingPostProcess.ts)。需要可序列化配置时应使用 FrameGraph/NRGE 路径。
Frame Graph 接入
FrameGraph 路径的接入步骤:
- 从
FrameGraphGeometryRendererTask请求屏幕/视空间深度输出与mesh-blending 标签输出;仅在需要阴影估计时才请求并连接 Albedo; - 标签可以位于任意颜色附件位置,但必须使用
TEXTURETYPE_UNSIGNED_BYTE+TEXTUREFORMAT_RED_INTEGER;混合的浮点与整数附件会按各自的标量类型被声明与清除; - 将这些句柄与匹配的 SceneColor 连接到
FrameGraphMeshBlendingTask,或在 NodeRenderGraph 中连接到NodeRenderGraphMeshBlendingPostProcessBlock; - 不覆盖
renderTransparentMeshes,按场景的重叠与合成需求自行设置。
FrameGraphMeshBlendingTask(meshBlendingTask.ts)在record()阶段会对标签/深度/基色/源纹理逐一做格式、维度、采样数校验,并自动设置最近邻采样、绑定标签/深度(可选基色)采样器、加入纹理依赖。NRGE 节点块NodeRenderGraphMeshBlendingPostProcessBlock(meshBlendingPostProcessBlock.pure.ts)提供了 Quality、Small/Medium/Large/ExtraLarge 世界半径与最小像素、Slope factor、Debug mode 等可在属性面板编辑的项,并支持serialize/_deserialize(默认值回退到CreateDefaultMeshBlendRadiusDefinitions、slopeFactor默认 2、debugMode默认 Off、quality默认 Medium),因此FrameGraph/NRGE 配置可以序列化。
共享核心:ThinMeshBlendingPostProcess
两条路径都基于ThinMeshBlendingPostProcess(thinMeshBlendingPostProcess.ts),所以 quality、radius、slope、depth、debug 配置通过同一份实现完成编译与绑定。该类负责:
- 管理相机(投影半径与视空间位置重建)与
projection/inverseProjection/inverseViewuniform; - 绑定四个半径类的
blendWorldRadii与minimumProjectedRadii(setFloat4); - 绑定
meshBlendIsOrthographic(依据相机模式判断)、slopeFactor; - 绑定内部稳定的蓝噪声搜索纹理
_stableBlueNoiseTexture(128×128、RG 格式、最近邻、wrap 寻址,详见测试 meshBlendingPostProcess.test.ts),并随dispose()销毁。
着色器片段位于 packages/dev/core/src/Shaders/meshBlending.fragment.fx,WebGPU 侧对应 packages/dev/core/src/ShadersWGSL 中的 meshBlending 片段(见_gatherImports的动态导入)。
质量等级(Quality)与开销
MeshBlendQuality.Medium是默认质量等级。四档质量是编译期着色器变体,每个等级由一组精确的采样/搜索参数定义(_MeshBlendQualitySettings,见 thinMeshBlendingPostProcess.ts):
| 参数 | Low | Medium | High | Cinematic |
|---|---|---|---|---|
| 搜索方向数 directionCount | 3 | 3 | 3 | 8 |
| 径向采样数 radialSampleCount | 2 | 3 | 3 | 6 |
| 方向细化采样数 | 1 | 2 | 3 | 4 |
| 方向细化步数 | 2 | 4 | 5 | 5 |
| 精确边界采样数 exactEdgeSampleCount | 5 | 8 | 10 | 50 |
| 半径缩放 radiusScale | 0.5 | 0.9 | 1 | 0.95 |
| 全随机旋转 fullRandomRotation | 否 | 否 | 是 | 是 |
| 搜索抖动因子 searchJitterFactor | 0.5 | 0.5 | 0.5 | 1 |
| 四近邻回退 | 否 | 否 | 是 | 是 |
| 小物体保护 | 否 | 否 | 是 | 是 |
| 多目标次级混合 | 否 | 否 | 是 | 是 |
| 颜色插值 | sRGB | OKLab | OKLab | OKLab |
架构文档的定性描述与源码完全对应:
Low:更少的搜索,sRGB 插值;Medium:增加 OKLab 插值;High:增加完整搜索旋转(full search rotation)、一像素回退(one-pixel fallback)、小物体保护(tiny-object protection)与次级接缝目标(secondary junction target);Cinematic:增加方向与边界搜索工作量。
这些参数通过_GetMeshBlendQualityDefines生成为着色器宏(如MESH_BLEND_DIRECTION_COUNT 8、MESH_BLEND_FULL_RANDOM_ROTATION、MESH_BLEND_TINY_OBJECT_SAFEGUARD、MESH_BLEND_COLOR_INTERPOLATION_OKLAB等,见 thinMeshBlendingPostProcess.ts)。单元测试逐项断言了四档等级的精确设置与生成的宏(meshBlendingPostProcess.test.ts)。
开销随屏幕覆盖面积与质量增长,因为每个参与像素都要在打包的标签纹理上执行搜索。架构文档的建议很明确:在目标硬件上对开启/关闭及各质量档分别做 profile,而不是依赖一个通用的毫秒阈值。
颜色插值:sRGB 与 OKLab
Low 使用 sRGB 域插值;Medium 及以上使用OKLab感知颜色空间插值。源码在 thinMeshBlendingPostProcess.ts 提供了完整的线性 sRGB ↔ OKLab 转换与插值函数,HDR 分量被保留(不做 gamut/HDR 钳制)。单元测试验证了 OKLab 往返精度与 Low/Medium 插值差异(meshBlendingPostProcess.test.ts)。该方案引用自 Björn Ottosson 的 OKLab 论文。
时间稳定性
内部搜索纹理是确定性空间噪声:它旋转并抖动搜索方向,但帧间不变。效果没有时间累积,不需要 TAA。对应测试还验证了蓝噪声的低频功率被抑制(meshBlendingPostProcess.test.ts)。
接触校验与 slope 因子
除了质量等级,IMeshBlendConfiguration还包含两个影响接触表现的配置项:
depthType(MeshBlendDepthType.View默认 0 /Screen1):选择视空间有符号相机 Z(GeometryRenderer 的 view-depth 输出)或归一化屏幕深度(硬件深度)。经典包装校验:Screen 深度才允许 UNSIGNED_BYTE 类型;slopeFactor:接触斜率收窄因子,1 表示禁用收窄,默认值为 2。_CalculateMeshBlendSlopeScale在slopeFactor > 1时把余旋对齐度映射到[0.25, 1]的乘数区间(见 thinMeshBlendingPostProcess.ts),对陡峭接触收窄混合半径、避免过宽的过度混合。校验要求slopeFactor为 ≥1 的有限数(thinMeshBlendingPostProcess.ts)。
接触处像素通过边界、深度、跨度、接触角四类校验后才会混合;_CalculateMeshBlendFade将候选边界距离归一化后输出0~0.5 的混合权重(thinMeshBlendingPostProcess.ts)。
调试视图(Debug Views)
MeshBlendDebugMode枚举(thinMeshBlendingPostProcess.ts)提供 13 种确定性调试可视化,均通过编译期宏开关(MESH_BLEND_DEBUG_*)启用:
| 枚举值 | 可视化内容 |
|---|---|
Off | 渲染最终混合后的场景颜色 |
PackedTag | 打包的组与半径类标签 |
CandidateDirectionDistance | 精化后的候选方向与归一化边界距离 |
SeamFade | 选中的半径类与最终接缝淡化 |
RejectionReason | 接触校验接受/拒绝的原因 |
StageWork | 每个像素近似执行的着色器工作量 |
Continuation | 目标延续成功、回退使用与拒绝 |
TinyObject | 对薄投影物体施加的有效半径缩减 |
MultiTarget | 多网格接缝处主/次级目标选择 |
TargetColor | 更远处、边界+1、边界+2 与构造的目标颜色采样 |
ShadowAttenuation | 基色阴影传递启发式的淡化衰减(无 Albedo 输入时为中性) |
ColorInterpolation | 目标颜色构造后的活动颜色插值模式 |
WorldPosition | 重建的世界位置 |
测试对每个调试模式断言了对应宏的生成(meshBlendingPostProcess.test.ts)。注意切换quality/debugMode/depthType/hasBaseColorTexture会触发 shader 重编译(updateEffect),而仅修改slopeFactor等非编译期参数不会(测试 meshBlendingPostProcess.test.ts 已验证该行为)。
官方示例与性能基准
架构文档提供了两个 Playground 片段(注意:需要包含 mesh-blending API 的 Babylon.js 构建;在该构建部署到 Playground Preview 之前,请通过本地 Playground 运行):
- Classic、FrameGraph、NRGE 对等演示:
#O05LI8#6。场景使用反转深度、把标签放在 MRT 槽位 0,并包含不带 Albedo 的 NRGE 路径;在 hash 前追加?engine=webgpu即可切换到 WebGPU; - 功能与性能演示:
#XVZTSI#3。包含岩石与地面接触、共享组与不同组、远处接缝、全部四档半径类、小道具、三网格接缝、alpha-tested 植被、质量/调试控件与相对 GPU 计时。
部署后,相同片段 ID 可在 Playground Preview 上以?version=preview#O05LI8#6与?version=preview#XVZTSI#3访问。
性能演示暴露了全局函数window.runMeshBlendBenchmark():它使用EngineInstrumentation.captureGPUFrameTime,报告同一设备上 disabled、Low、Medium、High、Cinematic 五种模式的比率。这些结果属于诊断性测量而非通过/失败阈值,与架构文档“在目标硬件上 profile 而非依赖通用毫秒阈值”的建议一致。
设计来源与实现独立性
架构文档明确指出:该实现是为 Babylon.js 独立编写的,没有使用或复现 MeshBlend 插件的源码,但其行为目标与参数模型受到以下公开资源的启发:
- MeshBlend(行为目标与参数模型参考);
- Jack Tollenaar 的《Screen space mesh seam blending》(屏幕空间接缝检测、镜像颜色采样与后处理混合);
- Jack Tollenaar 的《Fast mesh seam blending》(性能分析与优化考量,当前实现并未复现其 indirect-compute 流水线);
- Björn Ottosson 的《A perceptual color space for image processing》(OKLab 转换与插值)。
在 Babylon.js 中体验完整能力时,建议依次:用PackMeshBlendingTag为网格分组 → 按接触尺度挑选四档半径类并调整默认半径 → 选择质量等级并在目标硬件上 profile → 借助MeshBlendDebugMode定位接缝/拒绝原因 → 需要可序列化配置时走 FrameGraph/NRGE 路径,否则使用经典MeshBlendingPostProcess。
- 图形学
- 游戏开发
- 3D渲染
【免费下载链接】Babylon.js
Babylon.js is a powerful, beautiful, simple, and open game and rendering engine packed into a friendly JavaScript framework.
相关推荐
openage 地形混合(Terrain Blending)原理与 blendomatic.dat 格式全解析
openage 地形混合(Terrain Blending)原理与 blendomatic.dat 格式全解析 地形混合(terrain blending)是
游戏开发图形学Traefik Mesh:轻量级服务网格解决方案详解
Traefik Mesh:轻量级服务网格解决方案详解 什么是Traefik Mesh Traefik Mesh是一款专为Kubernetes设计的轻量级服务网格
Subtracks多语言支持:使用Weblate实现国际化翻译的完整指南
Subtracks多语言支持:使用Weblate实现国际化翻译的完整指南 Subtracks是一款专为Subsonic兼容服务器设计的开源音乐流媒体应用,支持全
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考