Skip to content

Commit

Permalink
feat: More Lights! [flame_3d] (#3250)
Browse files Browse the repository at this point in the history
More Lights! More fun!

I am still trying to get arrays to work (not even the fancy SSBOs, just
plain fixed arrays).

In the meanwhile this puts lights as separate objects.

This supports:

* point lights
* ambient lights
* colors
* intensity


![image](https://github.com/user-attachments/assets/a2f75a8a-9c64-42d1-bbe5-bdf58fa7df69)

---------

Co-authored-by: Jochum van der Ploeg <[email protected]>
  • Loading branch information
luanpotter and wolfenrain authored Aug 15, 2024
1 parent 4a21a11 commit 9694579
Show file tree
Hide file tree
Showing 20 changed files with 332 additions and 70 deletions.
1 change: 1 addition & 0 deletions .github/.cspell/people_usernames.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@ tavian # tavianator.com
videon # github.com/markvideon
wolfenrain # github.com/wolfenrain
xaha # github.com/xvrh
luan # github.com/luanpotter
Binary file modified packages/flame_3d/assets/shaders/spatial_material.shaderbundle
Binary file not shown.
Binary file not shown.
37 changes: 36 additions & 1 deletion packages/flame_3d/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,43 @@ class ExampleGame3D extends FlameGame<World3D>
@override
FutureOr<void> onLoad() async {
world.addAll([
LightComponent.ambient(
intensity: 1.0,
),
RotatingLight(),

LightComponent.point(
position: Vector3(0, 0.1, 0),
color: const Color(0xFFFF00FF),
),
MeshComponent(
mesh: SphereMesh(
radius: 0.05,
material: SpatialMaterial(
albedoTexture: ColorTexture(
const Color(0xFFFF00FF),
),
),
),
position: Vector3(0, 0.1, 0),
),

LightComponent.point(
position: Vector3(-2, 3, 2),
color: const Color(0xFFFF2255),
),
MeshComponent(
mesh: SphereMesh(
radius: 0.05,
material: SpatialMaterial(
albedoTexture: ColorTexture(
const Color(0xFFFF2255),
),
),
),
position: Vector3(-2, 4, 2),
),

// Add a player box
PlayerBox(),

Expand All @@ -50,7 +85,7 @@ class ExampleGame3D extends FlameGame<World3D>
mesh: SphereMesh(
radius: 1,
material: SpatialMaterial(
albedoTexture: ColorTexture(Colors.purple),
albedoTexture: ColorTexture(Colors.green),
),
),
),
Expand Down
5 changes: 4 additions & 1 deletion packages/flame_3d/example/lib/rotating_light.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import 'dart:math';
import 'dart:ui';

import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';

class RotatingLight extends LightComponent {
RotatingLight()
: super.spot(
: super.point(
position: Vector3.zero(),
color: const Color(0xFF00FF00),
intensity: 20.0,
);

@override
Expand Down
3 changes: 2 additions & 1 deletion packages/flame_3d/lib/src/camera/world_3d.dart
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@ class World3D extends flame.World with flame.HasGameReference {
image.dispose();
}

// TODO(luan): consider making this a fixed-size array later
void _prepareDevice() {
device.lights = lights;
device.lightingInfo.lights = lights;
}

// TODO(wolfenrain): this is only here for testing purposes
Expand Down
21 changes: 19 additions & 2 deletions packages/flame_3d/lib/src/components/light_component.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:ui';

import 'package:flame_3d/camera.dart';
import 'package:flame_3d/components.dart';
import 'package:flame_3d/game.dart';
Expand All @@ -10,13 +12,28 @@ class LightComponent extends Component3D {
super.position,
});

LightComponent.spot({
LightComponent.point({
Vector3? position,
Color color = const Color(0xFFFFFFFF),
double intensity = 1.0,
}) : this(
source: SpotLight(),
source: PointLight(
color: color,
intensity: intensity,
),
position: position,
);

LightComponent.ambient({
Color color = const Color(0xFFFFFFFF),
double intensity = 0.2,
}) : this(
source: AmbientLight(
color: color,
intensity: intensity,
),
);

final LightSource source;

late final Light _light = Light(
Expand Down
8 changes: 6 additions & 2 deletions packages/flame_3d/lib/src/extensions/color.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import 'dart:ui';

extension ColorExtension on Color {
/// Returns a Float32List that represents the color as a vector.
Float32List get storage =>
Float32List.fromList([red / 255, green / 255, blue / 255, opacity]);
Float32List get storage => Float32List.fromList([
opacity,
red.toDouble() / 255,
green.toDouble() / 255,
blue.toDouble() / 255,
]);
}
2 changes: 1 addition & 1 deletion packages/flame_3d/lib/src/graphics/graphics_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class GraphicsDevice {

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

/// Begin a new rendering batch.
///
Expand Down
4 changes: 3 additions & 1 deletion packages/flame_3d/lib/src/resources/light.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export 'light/ambient_light.dart';
export 'light/light.dart';
export 'light/light_source.dart';
export 'light/spot_light.dart';
export 'light/lighting_info.dart';
export 'light/point_light.dart';
15 changes: 15 additions & 0 deletions packages/flame_3d/lib/src/resources/light/ambient_light.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'dart:ui' show Color;

import 'package:flame_3d/resources.dart';

class AmbientLight extends LightSource {
AmbientLight({
super.color = const Color(0xFFFFFFFF),
super.intensity = 0.2,
});

void apply(Shader shader) {
shader.setColor('AmbientLight.color', color);
shader.setFloat('AmbientLight.intensity', intensity);
}
}
10 changes: 4 additions & 6 deletions packages/flame_3d/lib/src/resources/light/light.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,9 @@ class Light extends Resource<void> {
required this.source,
}) : super(null);

void apply(Shader shader) {
shader.setVector3('Light.position', transform.position);
// apply additional parameters
source.apply(shader);
void apply(int index, Shader shader) {
shader.setVector3('Light$index.position', transform.position);
shader.setColor('Light$index.color', source.color);
shader.setFloat('Light$index.intensity', source.intensity);
}

static UniformSlot shaderSlot = UniformSlot.value('Light', {'position'});
}
14 changes: 11 additions & 3 deletions packages/flame_3d/lib/src/resources/light/light_source.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import 'dart:ui' show Color;

import 'package:flame_3d/resources.dart';

/// Describes the properties of a light source.
/// There are three types of light sources: directional, point, and spot.
/// Currently only [SpotLight] is implemented.
/// There are three types of light sources: point, directional, and spot.
/// Currently only [PointLight] is implemented.
abstract class LightSource {
void apply(Shader shader);
final Color color;
final double intensity;

LightSource({
required this.color,
required this.intensity,
});
}
48 changes: 48 additions & 0 deletions packages/flame_3d/lib/src/resources/light/lighting_info.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import 'package:flame_3d/resources.dart';

class LightingInfo {
Iterable<Light> lights = [];

void apply(Shader shader) {
_applyAmbientLight(shader);
_applyPointLights(shader);
}

void _applyAmbientLight(Shader shader) {
final ambient = _extractAmbientLight(lights);
ambient.apply(shader);
}

void _applyPointLights(Shader shader) {
final pointLights = lights.where((e) => e.source is PointLight);
final numLights = pointLights.length;
if (numLights > 3) {
// temporary, until we support dynamic arrays
throw Exception('At most 3 point lights are allowed');
}

shader.setUint('LightsInfo.numLights', numLights);
for (final (idx, light) in pointLights.indexed) {
light.apply(idx, shader);
}
}

AmbientLight _extractAmbientLight(Iterable<Light> lights) {
final ambient = lights.where((e) => e.source is AmbientLight);
if (ambient.isEmpty) {
return AmbientLight();
}
if (ambient.length > 1) {
throw Exception('At most one ambient light is allowed');
}
return ambient.first.source as AmbientLight;
}

static List<UniformSlot> shaderSlots = [
UniformSlot.value('AmbientLight', {'color', 'intensity'}),
UniformSlot.value('LightsInfo', {'numLights'}),
UniformSlot.value('Light0', {'position', 'color', 'intensity'}),
UniformSlot.value('Light1', {'position', 'color', 'intensity'}),
UniformSlot.value('Light2', {'position', 'color', 'intensity'}),
];
}
9 changes: 9 additions & 0 deletions packages/flame_3d/lib/src/resources/light/point_light.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:flame_3d/resources.dart';

/// A point light that emits light in all directions equally.
class PointLight extends LightSource {
PointLight({
required super.color,
required super.intensity,
});
}
11 changes: 0 additions & 11 deletions packages/flame_3d/lib/src/resources/light/spot_light.dart

This file was deleted.

17 changes: 4 additions & 13 deletions packages/flame_3d/lib/src/resources/material/spatial_material.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ class SpatialMaterial extends Material {
SpatialMaterial({
Texture? albedoTexture,
Color albedoColor = const Color(0xFFFFFFFF),
this.metallic = 0,
this.metallicSpecular = 0.5,
this.roughness = 1.0,
this.metallic = 0.8,
this.roughness = 0.6,
}) : albedoTexture = albedoTexture ?? Texture.standard,
super(
vertexShader: Shader(
Expand All @@ -27,10 +26,9 @@ class SpatialMaterial extends Material {
UniformSlot.value('Material', {
'albedoColor',
'metallic',
'metallicSpecular',
'roughness',
}),
Light.shaderSlot,
...LightingInfo.shaderSlots,
UniformSlot.value('Camera', {'position'}),
],
),
Expand All @@ -53,8 +51,6 @@ class SpatialMaterial extends Material {

double metallic;

double metallicSpecular;

double roughness;

@override
Expand All @@ -77,7 +73,6 @@ class SpatialMaterial extends Material {
..setTexture('albedoTexture', albedoTexture)
..setVector3('Material.albedoColor', _albedoCache)
..setFloat('Material.metallic', metallic)
..setFloat('Material.metallicSpecular', metallicSpecular)
..setFloat('Material.roughness', roughness);
}

Expand All @@ -88,11 +83,7 @@ class SpatialMaterial extends Material {
}

void _applyLights(GraphicsDevice device) {
final light = device.lights.firstOrNull;
if (light == null) {
return;
}
light.apply(fragmentShader);
device.lightingInfo.apply(fragmentShader);
}

static final _library = gpu.ShaderLibrary.fromAsset(
Expand Down
5 changes: 2 additions & 3 deletions packages/flame_3d/lib/src/resources/mesh/vertex.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,8 @@ class Vertex {
_storage = Float32List.fromList([
...position.storage, // 1, 2, 3
...texCoord.storage, // 4, 5
...color.storage, // 6,7,8
// TODO(wolfenrain): fix normals not working properly
...(normal ?? Vector3.zero()).storage, // 9, 10, 11
...color.storage, // 6, 7, 8, 9
...(normal ?? Vector3.zero()).storage, // 10, 11, 12
]);

Float32List get storage => _storage;
Expand Down
17 changes: 16 additions & 1 deletion packages/flame_3d/lib/src/resources/shader/shader.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import 'dart:collection';
import 'dart:typed_data';
import 'dart:ui';

import 'package:flame_3d/game.dart';
import 'package:flame_3d/graphics.dart';
Expand Down Expand Up @@ -36,8 +38,15 @@ class Shader extends Resource<gpu.Shader> {
/// Set a [Vector4] at the given [key] on the buffer.
void setVector4(String key, Vector4 vector) => _setValue(key, vector.storage);

/// Set an [int] (encoded as uint) at the given [key] on the buffer.
void setUint(String key, int value) {
_setValue(key, _encodeUint32(value, Endian.little));
}

/// Set a [double] at the given [key] on the buffer.
void setFloat(String key, double value) => _setValue(key, [value]);
void setFloat(String key, double value) {
_setValue(key, [value]);
}

/// Set a [Matrix2] at the given [key] on the buffer.
void setMatrix2(String key, Matrix2 matrix) => _setValue(key, matrix.storage);
Expand All @@ -48,6 +57,8 @@ class Shader extends Resource<gpu.Shader> {
/// Set a [Matrix4] at the given [key] on the buffer.
void setMatrix4(String key, Matrix4 matrix) => _setValue(key, matrix.storage);

void setColor(String key, Color color) => _setValue(key, color.storage);

void bind(GraphicsDevice device) {
for (final slot in _slots) {
_instances[slot.name]?.bind(device);
Expand Down Expand Up @@ -91,4 +102,8 @@ class Shader extends Resource<gpu.Shader> {

return (_instances[keys.first], keys.elementAtOrNull(1)) as (T, String?);
}

static Float32List _encodeUint32(int value, Endian endian) {
return (ByteData(16)..setUint32(0, value, endian)).buffer.asFloat32List();
}
}
Loading

0 comments on commit 9694579

Please sign in to comment.