Skip to content

Commit 12927e4

Browse files
authored
feat: Support skeletal animation basics [flame_3d] (#3291)
Support skeletal animation basics. Sadly we cannot support arrays yet - see [this PR](#3282).
1 parent 9cb9527 commit 12927e4

File tree

10 files changed

+159
-10
lines changed

10 files changed

+159
-10
lines changed
151 KB
Binary file not shown.

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:ui';
33

44
import 'package:flame_3d/game.dart';
55
import 'package:flame_3d/resources.dart';
6+
import 'package:flame_3d/src/graphics/joints_info.dart';
67
import 'package:flutter_gpu/gpu.dart' as gpu;
78

89
enum BlendState {
@@ -48,6 +49,10 @@ class GraphicsDevice {
4849

4950
Size _previousSize = Size.zero;
5051

52+
/// Must be set by the rendering pipeline before elements are bound.
53+
/// Can be accessed by elements in their bind method.
54+
final JointsInfo jointsInfo = JointsInfo();
55+
5156
/// Must be set by the rendering pipeline before elements are bound.
5257
/// Can be accessed by elements in their bind method.
5358
final LightingInfo lightingInfo = LightingInfo();
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import 'package:flame_3d/core.dart';
2+
3+
class JointsInfo {
4+
/// Joints per surface idx
5+
Map<int, List<Matrix4>> jointTransformsPerSurface = {};
6+
7+
/// Joints for the current surface
8+
List<Matrix4> jointTransforms = [];
9+
10+
void setSurface(int surfaceIdx) {
11+
jointTransforms = jointTransformsPerSurface[surfaceIdx] ?? [];
12+
}
13+
}

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

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,15 @@ class SpatialMaterial extends Material {
1616
vertexShader: Shader(
1717
_library['TextureVertex']!,
1818
slots: [
19-
UniformSlot.value('VertexInfo', {'model', 'view', 'projection'}),
19+
UniformSlot.value('VertexInfo', {
20+
'model',
21+
'view',
22+
'projection',
23+
}),
24+
UniformSlot.value(
25+
'JointMatrices',
26+
List.generate(_maxJoints, (idx) => 'joint$idx').toSet(),
27+
),
2028
],
2129
),
2230
fragmentShader: Shader(
@@ -56,6 +64,7 @@ class SpatialMaterial extends Material {
5664
@override
5765
void bind(GraphicsDevice device) {
5866
_bindVertexInfo(device);
67+
_bindJointMatrices(device);
5968
_bindMaterial(device);
6069
_bindCamera(device);
6170
}
@@ -67,6 +76,19 @@ class SpatialMaterial extends Material {
6776
..setMatrix4('VertexInfo.projection', device.projection);
6877
}
6978

79+
void _bindJointMatrices(GraphicsDevice device) {
80+
final jointTransforms = device.jointsInfo.jointTransforms;
81+
if (jointTransforms.length > _maxJoints) {
82+
throw Exception(
83+
'At most $_maxJoints joints per surface are supported;'
84+
' found ${jointTransforms.length}',
85+
);
86+
}
87+
for (final (idx, transform) in jointTransforms.indexed) {
88+
vertexShader.setMatrix4('JointMatrices.joint$idx', transform);
89+
}
90+
}
91+
7092
void _bindMaterial(GraphicsDevice device) {
7193
_applyLights(device);
7294
fragmentShader
@@ -89,4 +111,6 @@ class SpatialMaterial extends Material {
89111
static final _library = gpu.ShaderLibrary.fromAsset(
90112
'packages/flame_3d/assets/shaders/spatial_material.shaderbundle',
91113
)!;
114+
115+
static const _maxJoints = 16;
92116
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ class Mesh extends Resource<void> {
2626
int get vertexCount => _surfaces.fold(0, (p, e) => p + e.vertexCount);
2727

2828
void bind(GraphicsDevice device) {
29-
for (final surface in _surfaces) {
29+
for (final (idx, surface) in _surfaces.indexed) {
30+
device.jointsInfo.setSurface(idx);
3031
device.bindSurface(surface);
3132
}
3233
}

packages/flame_3d/lib/src/resources/mesh/surface.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Surface extends Resource<gpu.DeviceBuffer?> {
1818
required List<Vertex> vertices,
1919
required List<int> indices,
2020
this.material,
21+
this.jointMap,
2122
/**
2223
* If `true`, the normals will be calculated if they are not provided.
2324
*/
@@ -34,15 +35,16 @@ class Surface extends Resource<gpu.DeviceBuffer?> {
3435
_vertices = Float32List.fromList(
3536
normalizedVertices.fold([], (p, v) => p..addAll(v.storage)),
3637
).buffer;
37-
_vertexCount = _vertices.lengthInBytes ~/ (normalizedVertices.length * 9);
38+
_vertexCount = normalizedVertices.length;
3839

3940
_indices = Uint16List.fromList(indices).buffer;
40-
_indexCount = _indices.lengthInBytes ~/ 2;
41+
_indexCount = indices.length;
4142

4243
_calculateAabb(normalizedVertices);
4344
}
4445

4546
Material? material;
47+
Map<int, int>? jointMap;
4648

4749
Aabb3 get aabb => _aabb;
4850
late Aabb3 _aabb;

packages/flame_3d/lib/src/resources/mesh/vertex.dart

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,20 @@ class Vertex {
1818
required Vector2 texCoord,
1919
this.color = const Color(0xFFFFFFFF),
2020
Vector3? normal,
21+
Vector4? joints,
22+
Vector4? weights,
2123
}) : position = position.immutable,
2224
texCoord = texCoord.immutable,
2325
normal = normal?.immutable,
26+
joints = joints?.immutable,
27+
weights = weights?.immutable,
2428
_storage = Float32List.fromList([
2529
...position.storage, // 1, 2, 3
2630
...texCoord.storage, // 4, 5
2731
...color.storage, // 6, 7, 8, 9
2832
...(normal ?? Vector3.zero()).storage, // 10, 11, 12
33+
...(joints ?? Vector4.zero()).storage, // 13, 14, 15, 16
34+
...(weights ?? Vector4.zero()).storage, // 17, 18, 19, 20
2935
]);
3036

3137
Float32List get storage => _storage;
@@ -40,6 +46,12 @@ class Vertex {
4046
/// The normal vector of the vertex.
4147
final ImmutableVector3? normal;
4248

49+
/// The joints of the vertex.
50+
final ImmutableVector4? joints;
51+
52+
/// The weights of the vertex.
53+
final ImmutableVector4? weights;
54+
4355
/// The color on the vertex.
4456
final Color color;
4557

@@ -49,23 +61,36 @@ class Vertex {
4961
position == other.position &&
5062
texCoord == other.texCoord &&
5163
normal == other.normal &&
52-
color == other.color;
64+
color == other.color &&
65+
joints == other.joints &&
66+
weights == other.weights;
5367

5468
@override
55-
int get hashCode => Object.hashAll([position, texCoord, normal, color]);
69+
int get hashCode => Object.hashAll([
70+
position,
71+
texCoord,
72+
normal,
73+
color,
74+
joints,
75+
weights,
76+
]);
5677

5778
Vertex copyWith({
5879
Vector3? position,
5980
Vector2? texCoord,
6081
Vector3? normal,
6182
Color? color,
83+
Vector4? joints,
84+
Vector4? weights,
6285
}) {
6386
// TODO(wolfenrain): optimize this.
6487
return Vertex(
6588
position: position ?? this.position.mutable,
6689
texCoord: texCoord ?? this.texCoord.mutable,
6790
normal: normal ?? this.normal?.mutable,
6891
color: color ?? this.color,
92+
joints: joints ?? this.joints?.mutable,
93+
weights: weights ?? this.weights?.mutable,
6994
);
7095
}
7196

packages/flame_3d/lib/src/resources/shader/uniform_value.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'dart:typed_data';
33

44
import 'package:flame_3d/graphics.dart';
55
import 'package:flame_3d/resources.dart';
6+
import 'package:ordered_set/comparing.dart';
67

78
/// {@template uniform_value}
89
/// Instance of a uniform value. Represented by a [ByteBuffer].
@@ -21,7 +22,9 @@ class UniformValue extends UniformInstance<ByteBuffer> {
2122
if (super.resource == null) {
2223
var previousIndex = -1;
2324

24-
final data = _storage.entries.fold<List<double>>([], (p, e) {
25+
final entries = _storage.entries.toList()
26+
..sort(Comparing.on((c) => c.key));
27+
final data = entries.fold<List<double>>([], (p, e) {
2528
if (previousIndex + 1 != e.key) {
2629
final field =
2730
slot.fields.indexed.firstWhere((e) => e.$1 == previousIndex + 1);

packages/flame_3d/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ dependencies:
1818
flutter_gpu:
1919
sdk: flutter
2020
meta: ^1.12.0
21+
ordered_set: ^6.0.1
2122
vector_math: ^2.1.4
2223

2324
dev_dependencies:

packages/flame_3d/shaders/spatial_material.vert

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ in vec3 vertexPosition;
44
in vec2 vertexTexCoord;
55
in vec4 vertexColor;
66
in vec3 vertexNormal;
7+
in vec4 vertexJoints;
8+
in vec4 vertexWeights;
79

810
out vec2 fragTexCoord;
911
out vec4 fragColor;
@@ -16,18 +18,91 @@ uniform VertexInfo {
1618
mat4 projection;
1719
} vertex_info;
1820

21+
uniform JointMatrices {
22+
mat4 joint0;
23+
mat4 joint1;
24+
mat4 joint2;
25+
mat4 joint3;
26+
mat4 joint4;
27+
mat4 joint5;
28+
mat4 joint6;
29+
mat4 joint7;
30+
mat4 joint8;
31+
mat4 joint9;
32+
mat4 joint10;
33+
mat4 joint11;
34+
mat4 joint12;
35+
mat4 joint13;
36+
mat4 joint14;
37+
mat4 joint15;
38+
} joints;
39+
40+
mat4 jointMat(float jointIndex) {
41+
if (jointIndex == 0.0) {
42+
return joints.joint0;
43+
} else if (jointIndex == 1.0) {
44+
return joints.joint1;
45+
} else if (jointIndex == 2.0) {
46+
return joints.joint2;
47+
} else if (jointIndex == 3.0) {
48+
return joints.joint3;
49+
} else if (jointIndex == 4.0) {
50+
return joints.joint4;
51+
} else if (jointIndex == 5.0) {
52+
return joints.joint5;
53+
} else if (jointIndex == 6.0) {
54+
return joints.joint6;
55+
} else if (jointIndex == 7.0) {
56+
return joints.joint7;
57+
} else if (jointIndex == 8.0) {
58+
return joints.joint8;
59+
} else if (jointIndex == 9.0) {
60+
return joints.joint9;
61+
} else if (jointIndex == 10.0) {
62+
return joints.joint10;
63+
} else if (jointIndex == 11.0) {
64+
return joints.joint11;
65+
} else if (jointIndex == 12.0) {
66+
return joints.joint12;
67+
} else if (jointIndex == 13.0) {
68+
return joints.joint13;
69+
} else if (jointIndex == 14.0) {
70+
return joints.joint14;
71+
} else if (jointIndex == 15.0) {
72+
return joints.joint15;
73+
} else {
74+
return mat4(0.0);
75+
}
76+
}
77+
78+
mat4 computeSkinMatrix() {
79+
if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) {
80+
// no weights, skip skinning
81+
return mat4(1.0);
82+
}
83+
84+
return vertexWeights.x * jointMat(vertexJoints.x) +
85+
vertexWeights.y * jointMat(vertexJoints.y) +
86+
vertexWeights.z * jointMat(vertexJoints.z) +
87+
vertexWeights.w * jointMat(vertexJoints.w);
88+
}
89+
1990
void main() {
91+
mat4 skinMatrix = computeSkinMatrix();
92+
vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz;
93+
vec3 normal = normalize((skinMatrix * vec4(vertexNormal, 0.0)).xyz);
94+
2095
// Calculate the modelview projection matrix
2196
mat4 modelViewProjection = vertex_info.projection * vertex_info.view * vertex_info.model;
2297

2398
// Transform the vertex position
24-
gl_Position = modelViewProjection * vec4(vertexPosition, 1.0);
99+
gl_Position = modelViewProjection * vec4(position, 1.0);
25100

26101
// Pass the interpolated values to the fragment shader
27102
fragTexCoord = vertexTexCoord;
28103
fragColor = vertexColor;
29104

30105
// Calculate the world-space position and normal
31-
fragPosition = vec3(vertex_info.model * vec4(vertexPosition, 1.0));
32-
fragNormal = mat3(transpose(inverse(vertex_info.model))) * vertexNormal;
106+
fragPosition = vec3(vertex_info.model * vec4(position, 1.0));
107+
fragNormal = mat3(transpose(inverse(vertex_info.model))) * normal;
33108
}

0 commit comments

Comments
 (0)