[flame_3d] Children added to an Object3D after it was rendered can be culled while on screen
维护者通常 1 天内回复
@ufrshubham 已经在做这个了。
开始于 2026年10月29日。
评估
这个 Issue 还没有评估数据。
描述
What happened?
When a child is added to a Component3D whose AABB has already been computed, the parent's cached AABB is not invalidated. The parent's AABB is the hull of its own geometry and the AABBs of its children, so it no longer covers the new child.
This matters when the parent is an Object3D: Object3D.renderTree frustum culls the parent's AABB first and skips the whole subtree when it is outside of the view. A child added inside the view, but outside of the parent's stale AABB, is therefore never rendered.
Component3D.onMount only marks the new child's own world transform as dirty, nothing marks the parent's AABB as dirty. A typical case is a spawner or pool that is an Object3D and adds children to itself at runtime.
What do you expect?
Adding a child to a component should invalidate the cached AABB of that component (and its ancestors), so that the new child is taken into account when culling.
How can we reproduce this?
- Run the
main.dartbelow withflutter run --enable-flutter-gpu(on Windows also add--enable-impeller). - The
Spawneris anObject3Dthat starts with a single (blue) child behind the camera, so its AABB is behind the camera too. After one second it adds a green cube to itself, in front of the camera. - Only the red control cube is ever rendered. The green cube is skipped because the
Spawneris culled using its stale AABB.
What steps should take to fix this?
Mark the parent's AABB dirty when a component is mounted:
@override
void onMount() {
super.onMount();
_markWorldTransformDirty();
if (parent case final Component3D parent) {
parent.markAabbDirty();
}
}
Doing the same in onRemove is optional. Without it the parent's AABB stays bigger than needed after a child is removed. That isn't visible and only makes culling less effective, but it keeps the bounds tight.
Marking is cheap since it stops at the first ancestor that is already dirty, and the parent's AABB is only recomputed lazily on the next read. This is the same cost as a child moving, which already marks its parents dirty.
Do have an example of where the bug occurs?
import 'package:flame/game.dart';
import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';
import 'package:flame_3d/graphics.dart';
import 'package:flame_3d/resources.dart';
import 'package:flutter/widgets.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await GpuBackend.initialize();
runApp(GameWidget(game: ReproGame()));
}
class ReproGame extends FlameGame3D {
ReproGame()
: super(
camera: CameraComponent3D(
position: Vector3(0, 2, -6),
target: Vector3(0, 1, 0),
),
);
@override
Future<void> onLoad() async {
world.addAll([
// Red: not nested, world position (1.5, 1, 0). Rendered.
MeshComponent(
mesh: _cubeMesh(const Color(0xFFFF0000)),
position: Vector3(1.5, 1, 0),
),
// A group with a single child behind the camera. After one second a
// green cube is added to it at world position (-1.5, 1, 0). The green
// cube is never rendered.
Spawner(
initial: MeshComponent(
mesh: _cubeMesh(const Color(0xFF0000FF)),
position: Vector3(0, 1, -20),
),
spawned: MeshComponent(
mesh: _cubeMesh(const Color(0xFF00FF00)),
position: Vector3(-1.5, 1, 0),
),
),
]);
}
Mesh _cubeMesh(Color color) => CuboidMesh(
size: Vector3.all(1),
material: UnlitMaterial(albedoColor: color),
);
}
/// An [Object3D] group that adds [spawned] to itself after one second.
class Spawner extends Object3D {
Spawner({required Component3D initial, required this.spawned})
: super(children: [initial]);
final Component3D spawned;
double _elapsed = 0;
@override
void update(double dt) {
super.update(dt);
_elapsed += dt;
if (_elapsed > 1 && spawned.parent == null) {
add(spawned);
}
}
@override
void draw(RenderContext context) {}
}
Relevant log output
No errors or warnings are logged.
Execute in a terminal and put output into the code block below
Flutter 3.47.8 • channel stable • https://github.com/flutter/flutter.git
Framework • revision 737ba8f885 (6 days ago) • 2026-10-22 21:37:31 -0700
Engine • hash dfd2e6cb7d319db05e3bf1503f3e2ac60f6288a2 (revision cf636b1dfb) (6 days ago) • 2026-10-23 02:13:38.000Z
Tools • Dart 3.13.7 • DevTools 2.60.0
Affected platforms
All
Other information
Reproduced on Windows (Impeller with the OpenGL ES backend), but the culling logic is platform independent.
Related to #4163, which is the other case of cached AABBs not being invalidated (when an ancestor moves). Both touch the same code in Component3D, but they can be reproduced and fixed independently.
If the parent is a plain Component3D the bug isn't visible, since only Object3D culls its subtree.
Are you interested in working on a PR for this?
- I want to work on this
- 主要语言
- Dart
- 星标
- 10.8k
- 派生
- 1.1k
- 平均合并
- 1 天 21 小时
- 30 天内合并 PR
- 52
环境准备
- 没有 Dockerfile 或 Docker Compose 文件
- 有 Pull Request 模板
- 阅读贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
flame-engine/flame 的其他 Issue
-
enhancement
难度 5/5 一周以上 新手友好度 35/100
flame-engine/flame#4176 ·
维护者通常 1 天内回复
-
discussion enhancement
难度 5/5 一周以上 新手友好度 25/100
flame-engine/flame#4175 ·
维护者通常 1 天内回复
-
enhancement
难度 5/5 一周以上 新手友好度 35/100
flame-engine/flame#4174 ·
维护者通常 1 天内回复
-
enhancement
难度 4/5 3-5 天 新手友好度 48/100
flame-engine/flame#4173 ·
维护者通常 1 天内回复
-
enhancement
难度 4/5 3-5 天 新手友好度 55/100
flame-engine/flame#4172 ·
维护者通常 1 天内回复
查看 flame-engine/flame 的全部 Issue
相似的 Issue
-
难度 2/5 1-3 小时 新手友好度 72/100
-
难度 2/5 1-3 小时 新手友好度 64/100
bannzai/medicalarm#478 ·
维护者通常 1 天内回复
-
genkit_anthropic: responses with an empty or unknown usage.service_tier fail with FormatException: Unknown ServiceTier可能已有人在做 @kenzieschmoll 今天认领。 未关闭
难度 2/5 1-3 小时 新手友好度 84/100
genkit-ai/genkit-dart#686 ·
维护者通常 3 天内回复
-
难度 2/5 1-3 小时 新手友好度 82/100
aaronified/fluenough#918 ·
维护者通常 1 天内回复
-
难度 2/5 1-3 小时 新手友好度 66/100
VeryGoodOpenSource/very_good_analysis#255 ·
维护者通常 1 天内回复