Skip to content

Commit fbd9b67

Browse files
authored
refactor(flame_3d)!: Allow LightComponents to be nested (#3883)
`LightComponents` can now be nested inside any `Component3D` allowing for a more flexible light system. On top of that refactored the light system so that every Material decided how lights should be applied. https://github.com/user-attachments/assets/c3bceb92-5ed6-4910-b9fe-4efda6e4b600
1 parent 043d6e8 commit fbd9b67

10 files changed

Lines changed: 71 additions & 104 deletions

File tree

packages/flame_3d/example/lib/components/player.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ class Player extends MeshComponent
3636
albedoTexture: ColorTexture(BasicPalette.yellow.color),
3737
),
3838
),
39+
children: [
40+
LightComponent.point(
41+
position: Vector3(0, 1.5, 0.8),
42+
color: BasicPalette.white.color,
43+
intensity: 10,
44+
),
45+
],
3946
);
4047

4148
@override
Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,29 @@
11
import 'dart:async';
2+
import 'dart:ui';
23

3-
import 'package:flame/components.dart';
4-
import 'package:flame/palette.dart';
54
import 'package:flame_3d/components.dart';
6-
import 'package:flame_3d/game.dart';
75
import 'package:flame_3d/resources.dart';
86

9-
class RenderedPointLight extends Component with HasGameReference<FlameGame3D> {
10-
final Vector3 position;
7+
class RenderedPointLight extends Component3D {
118
final Color color;
129

1310
RenderedPointLight({
14-
required this.position,
11+
required super.position,
1512
required this.color,
1613
});
1714

18-
late LightComponent _light;
19-
2015
@override
2116
FutureOr<void> onLoad() async {
22-
// TODO(luan): support lights being added nested in the component tree.
23-
game.world.add(
24-
_light = LightComponent.point(
25-
position: position,
26-
color: color,
27-
),
28-
);
2917
addAll([
18+
LightComponent.point(color: color),
3019
MeshComponent(
3120
mesh: SphereMesh(
3221
radius: 0.05,
3322
material: SpatialMaterial(
34-
albedoTexture: ColorTexture(
35-
color,
36-
),
23+
albedoTexture: ColorTexture(color),
3724
),
3825
),
39-
position: position,
4026
),
4127
]);
4228
}
43-
44-
@override
45-
void onRemove() {
46-
if (_light.isMounted) {
47-
game.world.remove(_light);
48-
}
49-
super.onRemove();
50-
}
5129
}

packages/flame_3d/lib/src/camera/world_3d.dart

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,21 @@ class World3D extends flame.World with flame.HasGameReference {
2020
super.children,
2121
super.priority,
2222
Color clearColor = const Color(0x00000000),
23-
}) : device = GraphicsDevice(clearValue: clearColor) {
24-
children.register<LightComponent>();
25-
}
23+
}) : device = GraphicsDevice(clearValue: clearColor);
2624

2725
/// The graphical device attached to this world.
2826
@internal
2927
final GraphicsDevice device;
3028

31-
Iterable<Light> get lights =>
32-
children.query<LightComponent>().map((component) => component.light);
29+
final List<Light> _lights = [];
30+
31+
/// Register a [light] with this world.
32+
@internal
33+
void addLight(Light light) => _lights.add(light);
34+
35+
/// Unregister a [light] from this world.
36+
@internal
37+
void removeLight(Light light) => _lights.remove(light);
3338

3439
final _paint = Paint();
3540

@@ -46,6 +51,7 @@ class World3D extends flame.World with flame.HasGameReference {
4651
);
4752

4853
device
54+
..lights = _lights
4955
// Set the view matrix
5056
..view.setFrom(camera.viewMatrix)
5157
// Set the projection matrix
@@ -54,7 +60,6 @@ class World3D extends flame.World with flame.HasGameReference {
5460

5561
culled = 0;
5662

57-
_prepareDevice();
5863
// ignore: invalid_use_of_internal_member
5964
super.renderFromCamera(canvas);
6065

@@ -69,11 +74,6 @@ class World3D extends flame.World with flame.HasGameReference {
6974
image.dispose();
7075
}
7176

72-
// TODO(luan): consider making this a fixed-size array later
73-
void _prepareDevice() {
74-
device.lightingInfo.lights = lights;
75-
}
76-
7777
// TODO(wolfenrain): this is only here for testing purposes
7878
int culled = 0;
7979
}

packages/flame_3d/lib/src/components/light_component.dart

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1+
import 'dart:typed_data';
12
import 'dart:ui';
23

3-
import 'package:flame_3d/camera.dart';
44
import 'package:flame_3d/components.dart';
55
import 'package:flame_3d/game.dart';
66
import 'package:flame_3d/resources.dart';
77

8-
/// A [Component3D] that represents a light source in the 3D world.
8+
/// A [Component3D] that represents a light source in 3D space.
99
class LightComponent extends Component3D {
1010
LightComponent({
1111
required this.source,
@@ -37,7 +37,10 @@ class LightComponent extends Component3D {
3737
final LightSource source;
3838

3939
late final Light _light = Light(
40-
transform: transform,
40+
position: Vector3.fromBuffer(
41+
worldTransformMatrix.storage.buffer,
42+
12 * Float32List.bytesPerElement,
43+
),
4144
source: source,
4245
);
4346

@@ -46,9 +49,20 @@ class LightComponent extends Component3D {
4649
@override
4750
void onMount() {
4851
super.onMount();
49-
assert(
50-
parent is World3D,
51-
'Lights must be added to the root of the World3D',
52-
);
52+
world.addLight(_light);
53+
}
54+
55+
@override
56+
void onRemove() {
57+
world.removeLight(_light);
58+
super.onRemove();
59+
}
60+
61+
@override
62+
void update(double dt) {
63+
// NOTE: this ensures the matrix gets computed if need be, so that
64+
// the light position moves with it's ancestors.
65+
worldTransformMatrix;
66+
super.update(dt);
5367
}
5468
}

packages/flame_3d/lib/src/graphics/graphics_device.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class GraphicsDevice {
7676

7777
/// Must be set by the rendering pipeline before elements are bound.
7878
/// Can be accessed by elements in their bind method.
79-
final LightingInfo lightingInfo = LightingInfo();
79+
Iterable<Light> lights = [];
8080

8181
/// Begin a new rendering batch.
8282
///
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
export 'light/ambient_light.dart';
22
export 'light/light.dart';
33
export 'light/light_source.dart';
4-
export 'light/lighting_info.dart';
54
export 'light/point_light.dart';

packages/flame_3d/lib/src/resources/light/ambient_light.dart

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,4 @@ class AmbientLight extends LightSource {
77
super.color = const Color(0xFFFFFFFF),
88
super.intensity = 0.2,
99
});
10-
11-
void apply(Shader shader) {
12-
shader.setColor('AmbientLight.color', color);
13-
shader.setFloat('AmbientLight.intensity', intensity);
14-
}
1510
}

packages/flame_3d/lib/src/resources/light/light.dart

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,16 @@ import 'package:flame_3d/resources.dart';
1010
///
1111
/// {@endtemplate}
1212
class Light extends Resource<void> {
13-
final Transform3D transform;
13+
final Vector3 position;
14+
1415
final LightSource source;
1516

1617
/// {@macro light}
1718
Light({
18-
required this.transform,
19+
required this.position,
1920
required this.source,
2021
});
2122

2223
@override
2324
void createResource() {}
24-
25-
void apply(int index, Shader shader) {
26-
shader.setVector3('Lights.positions[$index]', transform.position);
27-
shader.setColor('Lights.colors[$index]', source.color);
28-
shader.setFloat('Lights.intensities[$index]', source.intensity);
29-
}
3025
}

packages/flame_3d/lib/src/resources/light/lighting_info.dart

Lines changed: 0 additions & 43 deletions
This file was deleted.

packages/flame_3d/lib/src/resources/material/spatial_material.dart

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,29 @@ class SpatialMaterial extends Material {
8282
}
8383

8484
void _applyLights(GraphicsDevice device) {
85-
device.lightingInfo.apply(fragmentShader);
85+
final lights = device.lights;
86+
87+
// Apply ambient light (at most one, fallback to default).
88+
final ambient =
89+
lights.map((e) => e.source).whereType<AmbientLight>().firstOrNull ??
90+
AmbientLight();
91+
fragmentShader
92+
..setColor('AmbientLight.color', ambient.color)
93+
..setFloat('AmbientLight.intensity', ambient.intensity);
94+
95+
// Apply point lights.
96+
final points = lights.where((e) => e.source is PointLight);
97+
98+
// NOTE: using floats because Android GLES does not support integer uniforms
99+
// Refer to https://github.com/flutter/engine/pull/55329
100+
fragmentShader.setFloat('Lights.numLights', points.length.toDouble());
101+
102+
for (final (index, light) in points.indexed) {
103+
fragmentShader
104+
..setVector3('Lights.positions[$index]', light.position)
105+
..setColor('Lights.colors[$index]', light.source.color)
106+
..setFloat('Lights.intensities[$index]', light.source.intensity);
107+
}
86108
}
87109

88110
static const _maxJoints = 16;

0 commit comments

Comments
 (0)