三维与渲染

flutter_scene 半透明多层远景的斜视遮挡错序与显式 renderOrder

AlphaMode.blend 默认按包围盒中心在相机空间的深度排序;当宽幅远景与窄幅前景分层叠放时,室内斜视机位会出现包围盒中心深度翻转,需对拓扑关系固定的视差层显式指定单调 renderOrder。

在三维书房的后墙窗洞外,为了兼顾景深视差与绘制开销,窗外景色由多层带透明通道的 UnlitMaterial(AlphaMode.blend)面片按距离由远及近叠放组成:依次是天空底图、体积云带、城市天际线、宽幅近景商业街,以及位于街道前方的窄幅前景标识物与行人剪影。

现象与原因

调试时遇到一个和机位强相关的遮挡问题:把镜头推近到窗户正前方垂直往外看时,各层前后遮挡完全正常;但退回室内默认的斜向全景机位(从书桌侧前方斜着望向后墙窗户)后,明明世界坐标更靠近窗户的前景窄幅面片(z = 14.8 m),却被后方的商业街背景层(z = 15.4 m)直接盖住看不见了。

查看 flutter_scene(0.24.3)对半透明网格的排序逻辑后确认了原因:

  1. 开启 AlphaMode.blend 的半透明 RenderItem 默认关闭深度缓冲写入(depthWrite = false),像素的前后覆盖关系完全取决于提交绘制的先后顺序。
  2. 当多个半透明节点的 renderOrder 相同时(默认均为 0),渲染器会按各网格包围盒中心(Bounding-Box Center)在相机空间中的深度值从远到近排序。

当宽幅商业街面片横向跨度很大(或带有轻微环绕弧度),而前景物体只是偏居窗洞一侧的窄小面片时,在室内大角度斜视机位下,宽幅背景的几何中心投影到相机视线轴上的深度,反而比偏在一侧的前景中心更靠近相机。结果排序器认为商业街“更近”,把它排在前景之后绘制,直接覆盖了已画好的前景像素。

处理方式与适用边界

对于窗外视差背景这类前后拓扑顺序在任何合法机位下都恒定不变的分层结构,不再依赖运行时的包围盒中心距离排序,改为从远到近显式赋予严格单调递增的 node.renderOrder(同时整体保持小于室内窗户玻璃与粒子系统的默认值 0.0):

// 从最远到最近严格递增,确保斜视机位下绘制顺序不翻转:
// 天空 (-5.0) -> 云带 (-4.0) -> 天际线 (-3.0)
// -> 近景街道 (-2.0) -> 街头剪影 (-1.5) -> 前景物件 (-1.0) -> 室内玻璃/粒子 (0.0)
skyNode.renderOrder = -5.0;
cloudNode.renderOrder = -4.0;
cityNode.renderOrder = -3.0;
streetNode.renderOrder = -2.0;
trafficNode.renderOrder = -1.5;
foregroundNode.renderOrder = -1.0;

配置后在室内默认斜向全景机位与窗前正视机位下同时核对,前景层均不再被后方宽幅街景错误遮挡。

需要注意适用边界:显式固定 renderOrder 只适合平面视差层、贴花等前后包裹关系固定不变的半透明面片;如果两个半透明三维物体在空间中互相穿插,或者相机会绕到它们背后观察,硬编码 renderOrder 反而会在背面视角下制造错误的强制覆盖。