[flame_3d] Children added to an Object3D after it was rendered can be culled while on screen
Maintainer thường phản hồi trong vòng 1 ngày
Đánh giá
Issue này chưa được đánh giá.
Mô tả
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
- Ngôn ngữ chính
- Dart
- Star
- 10.8k
- Fork
- 1.1k
- Merge trung bình
- 1 ngày 21 giờ
- Pull request đã merge (30 ngày)
- 50
Chuẩn bị môi trường
- Không có Dockerfile hay tệp Docker Compose
- Có mẫu pull request
- Đọc hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của flame-engine/flame
-
`PostProcessComponent` throws `StateError` when it has no `PositionComponent` childrenCó thể đã có người làm @ufrshubham đã nhận 1 ngày trước. Đang mởbug
flame-engine/flame#4182 · 1 người được giao ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
flame-engine/flame#4176 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
discussion enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 25/100
flame-engine/flame#4175 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 35/100
flame-engine/flame#4174 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
enhancement
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 48/100
flame-engine/flame#4173 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của flame-engine/flame
Issue tương tự
-
dart easy first-contributor first-contributors good first issue help wanted up-for-grabs
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 92/100
fork-commit-merge/fork-commit-merge#8563 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
from.page-issue
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 85/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 1/5 Dưới một giờ Mức phù hợp với người mới 82/100
bannzai/Pilll#2080 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
good first issue help wanted
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
mollykitten/waveform#112 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
auto-implement-claude
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 70/100
Maintainer thường phản hồi trong vòng 1 ngày