From 5b2a7b42791e6f132e0bd8b918fdd1a2a7253b6a Mon Sep 17 00:00:00 2001 From: Luan Nico Date: Fri, 13 Sep 2024 21:32:37 +0200 Subject: [PATCH] feat: Support skeletal animation basics [flame_3d] (#3291) Support skeletal animation basics. Sadly we cannot support arrays yet - see [this PR](https://github.com/flame-engine/flame/pull/3282). --- .../shaders/spatial_material.shaderbundle | Bin 58920 -> 213816 bytes .../lib/src/graphics/graphics_device.dart | 5 ++ .../lib/src/graphics/joints_info.dart | 13 +++ .../resources/material/spatial_material.dart | 26 +++++- .../flame_3d/lib/src/resources/mesh/mesh.dart | 3 +- .../lib/src/resources/mesh/surface.dart | 6 +- .../lib/src/resources/mesh/vertex.dart | 29 ++++++- .../src/resources/shader/uniform_value.dart | 5 +- packages/flame_3d/pubspec.yaml | 1 + .../flame_3d/shaders/spatial_material.vert | 81 +++++++++++++++++- 10 files changed, 159 insertions(+), 10 deletions(-) create mode 100644 packages/flame_3d/lib/src/graphics/joints_info.dart diff --git a/packages/flame_3d/assets/shaders/spatial_material.shaderbundle b/packages/flame_3d/assets/shaders/spatial_material.shaderbundle index 016d3a9ed3328153593235dc3454d277d5a9a818..841e7a8b93b0013d288de8be30058d1a3240463b 100644 GIT binary patch literal 213816 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 delta 4230 zcmd5EoM@JA{=?{t}oyy-3lKpcWbte)MD|EE~fTJ!q z1Wijd2FgWruIdcb$|YTn`Ycc?lWQF11cG98t{PZZDS{%(b( zf1aa$8x6`R)|CZ8IRn_hp2oA^6l6>h7mmApLu)mq>>uhwL&sghqU(e@lOK{38+3RC z;e!oj;)EvLbkZd}9FO)lY49+@U@Yq1SDON5qJ5o9x#^UqJR_owb^ctRdgiL8obs_td4G$ejvfe! zw{&#>pIpl3U6tPumYZ5Nw)Sn5dQF-5SQEZ*%_Th2>Jq;Di6(qxmnPi1i-gVFDlJ+r zhpxGNeUX00`9j^ZX3M@dO?d<>D2w5?v;OtbToJjWIy*wL@0a?>-lLjO-;hh!%uvf) z!?KeDf{|!GrlXyAbcOHVRkfWVxeYTATN@JZ>gcw+dL*u>N4v_!!vike`(Hb%rL9yR z@6>dUbdqk;;Mb0$>bgqg@I5^gH`MF9%Vi%%ghMfcl?A>8bYRbqd|CO1@x|E#>yPHB zNJnIR1EzaW8b+SFnwM#=QU^N1a^R%qdgh{LGw?6>CZhO}TG1Mk$I;2l{*d^ijvoKF zF1(@Ub%x~|=p*mB=@LkGl!-^W@PngjbXS>}c$zn{7nveUwYHVWfz$db{ioH)!8fEI zH)-iwiVnQFJ76EHkxA-rT`6+(1AUdDD>$*c(*9kY%vHsE8q}5U;##Z0FzV_I4N@)6vOBkr+xddN>Mn@kft%gNHd~FcXRct6Y+lQ_|DDSo2grn zB1i?K3Q`RTL0*K^L)Jo;;JYvZD<2HncJmQ{@ zo6?7oNnokd#}6eBC)PWhW%T2G_u=8Jsdpf4*4I0XHfufBX5Ghb$R3Qf*$>(zK|Fio z*_oh0A_eM0`CN?kNrsSSF8;+}xHY5gk9UtgPb}o5!uGB_H*TYzu5Su#&W;6PsbGLL{~hQ|_7V!Ab9zkm(cwUB0Vaaon*?FU+0}VKaXKbL==XB6pfiV!^>U^_r~e)7 zLrgY=IQl(YE_C8J03PmH=A53HeaU@}lh<=ka6 zkoH5sF5g-#d$E>d-QVZf7(X%?bxWFUO<8i_9@zkT+h ig|f(w_(ZW+v1haJ*OpK|DPWTYAUQt%F!sJABmV(CTbi-} diff --git a/packages/flame_3d/lib/src/graphics/graphics_device.dart b/packages/flame_3d/lib/src/graphics/graphics_device.dart index a5e5117a5ee..83a24cb8f7e 100644 --- a/packages/flame_3d/lib/src/graphics/graphics_device.dart +++ b/packages/flame_3d/lib/src/graphics/graphics_device.dart @@ -3,6 +3,7 @@ import 'dart:ui'; import 'package:flame_3d/game.dart'; import 'package:flame_3d/resources.dart'; +import 'package:flame_3d/src/graphics/joints_info.dart'; import 'package:flutter_gpu/gpu.dart' as gpu; enum BlendState { @@ -48,6 +49,10 @@ class GraphicsDevice { Size _previousSize = Size.zero; + /// Must be set by the rendering pipeline before elements are bound. + /// Can be accessed by elements in their bind method. + final JointsInfo jointsInfo = JointsInfo(); + /// Must be set by the rendering pipeline before elements are bound. /// Can be accessed by elements in their bind method. final LightingInfo lightingInfo = LightingInfo(); diff --git a/packages/flame_3d/lib/src/graphics/joints_info.dart b/packages/flame_3d/lib/src/graphics/joints_info.dart new file mode 100644 index 00000000000..c21204b211c --- /dev/null +++ b/packages/flame_3d/lib/src/graphics/joints_info.dart @@ -0,0 +1,13 @@ +import 'package:flame_3d/core.dart'; + +class JointsInfo { + /// Joints per surface idx + Map> jointTransformsPerSurface = {}; + + /// Joints for the current surface + List jointTransforms = []; + + void setSurface(int surfaceIdx) { + jointTransforms = jointTransformsPerSurface[surfaceIdx] ?? []; + } +} diff --git a/packages/flame_3d/lib/src/resources/material/spatial_material.dart b/packages/flame_3d/lib/src/resources/material/spatial_material.dart index dd12426b6bb..4b9ed6ef28c 100644 --- a/packages/flame_3d/lib/src/resources/material/spatial_material.dart +++ b/packages/flame_3d/lib/src/resources/material/spatial_material.dart @@ -16,7 +16,15 @@ class SpatialMaterial extends Material { vertexShader: Shader( _library['TextureVertex']!, slots: [ - UniformSlot.value('VertexInfo', {'model', 'view', 'projection'}), + UniformSlot.value('VertexInfo', { + 'model', + 'view', + 'projection', + }), + UniformSlot.value( + 'JointMatrices', + List.generate(_maxJoints, (idx) => 'joint$idx').toSet(), + ), ], ), fragmentShader: Shader( @@ -56,6 +64,7 @@ class SpatialMaterial extends Material { @override void bind(GraphicsDevice device) { _bindVertexInfo(device); + _bindJointMatrices(device); _bindMaterial(device); _bindCamera(device); } @@ -67,6 +76,19 @@ class SpatialMaterial extends Material { ..setMatrix4('VertexInfo.projection', device.projection); } + void _bindJointMatrices(GraphicsDevice device) { + final jointTransforms = device.jointsInfo.jointTransforms; + if (jointTransforms.length > _maxJoints) { + throw Exception( + 'At most $_maxJoints joints per surface are supported;' + ' found ${jointTransforms.length}', + ); + } + for (final (idx, transform) in jointTransforms.indexed) { + vertexShader.setMatrix4('JointMatrices.joint$idx', transform); + } + } + void _bindMaterial(GraphicsDevice device) { _applyLights(device); fragmentShader @@ -89,4 +111,6 @@ class SpatialMaterial extends Material { static final _library = gpu.ShaderLibrary.fromAsset( 'packages/flame_3d/assets/shaders/spatial_material.shaderbundle', )!; + + static const _maxJoints = 16; } diff --git a/packages/flame_3d/lib/src/resources/mesh/mesh.dart b/packages/flame_3d/lib/src/resources/mesh/mesh.dart index 765b03f5acb..c2fa8b42b13 100644 --- a/packages/flame_3d/lib/src/resources/mesh/mesh.dart +++ b/packages/flame_3d/lib/src/resources/mesh/mesh.dart @@ -26,7 +26,8 @@ class Mesh extends Resource { int get vertexCount => _surfaces.fold(0, (p, e) => p + e.vertexCount); void bind(GraphicsDevice device) { - for (final surface in _surfaces) { + for (final (idx, surface) in _surfaces.indexed) { + device.jointsInfo.setSurface(idx); device.bindSurface(surface); } } diff --git a/packages/flame_3d/lib/src/resources/mesh/surface.dart b/packages/flame_3d/lib/src/resources/mesh/surface.dart index 3b4594300d9..b561768b47a 100644 --- a/packages/flame_3d/lib/src/resources/mesh/surface.dart +++ b/packages/flame_3d/lib/src/resources/mesh/surface.dart @@ -18,6 +18,7 @@ class Surface extends Resource { required List vertices, required List indices, this.material, + this.jointMap, /** * If `true`, the normals will be calculated if they are not provided. */ @@ -34,15 +35,16 @@ class Surface extends Resource { _vertices = Float32List.fromList( normalizedVertices.fold([], (p, v) => p..addAll(v.storage)), ).buffer; - _vertexCount = _vertices.lengthInBytes ~/ (normalizedVertices.length * 9); + _vertexCount = normalizedVertices.length; _indices = Uint16List.fromList(indices).buffer; - _indexCount = _indices.lengthInBytes ~/ 2; + _indexCount = indices.length; _calculateAabb(normalizedVertices); } Material? material; + Map? jointMap; Aabb3 get aabb => _aabb; late Aabb3 _aabb; diff --git a/packages/flame_3d/lib/src/resources/mesh/vertex.dart b/packages/flame_3d/lib/src/resources/mesh/vertex.dart index c1fd8fac805..a11e8801fda 100644 --- a/packages/flame_3d/lib/src/resources/mesh/vertex.dart +++ b/packages/flame_3d/lib/src/resources/mesh/vertex.dart @@ -18,14 +18,20 @@ class Vertex { required Vector2 texCoord, this.color = const Color(0xFFFFFFFF), Vector3? normal, + Vector4? joints, + Vector4? weights, }) : position = position.immutable, texCoord = texCoord.immutable, normal = normal?.immutable, + joints = joints?.immutable, + weights = weights?.immutable, _storage = Float32List.fromList([ ...position.storage, // 1, 2, 3 ...texCoord.storage, // 4, 5 ...color.storage, // 6, 7, 8, 9 ...(normal ?? Vector3.zero()).storage, // 10, 11, 12 + ...(joints ?? Vector4.zero()).storage, // 13, 14, 15, 16 + ...(weights ?? Vector4.zero()).storage, // 17, 18, 19, 20 ]); Float32List get storage => _storage; @@ -40,6 +46,12 @@ class Vertex { /// The normal vector of the vertex. final ImmutableVector3? normal; + /// The joints of the vertex. + final ImmutableVector4? joints; + + /// The weights of the vertex. + final ImmutableVector4? weights; + /// The color on the vertex. final Color color; @@ -49,16 +61,27 @@ class Vertex { position == other.position && texCoord == other.texCoord && normal == other.normal && - color == other.color; + color == other.color && + joints == other.joints && + weights == other.weights; @override - int get hashCode => Object.hashAll([position, texCoord, normal, color]); + int get hashCode => Object.hashAll([ + position, + texCoord, + normal, + color, + joints, + weights, + ]); Vertex copyWith({ Vector3? position, Vector2? texCoord, Vector3? normal, Color? color, + Vector4? joints, + Vector4? weights, }) { // TODO(wolfenrain): optimize this. return Vertex( @@ -66,6 +89,8 @@ class Vertex { texCoord: texCoord ?? this.texCoord.mutable, normal: normal ?? this.normal?.mutable, color: color ?? this.color, + joints: joints ?? this.joints?.mutable, + weights: weights ?? this.weights?.mutable, ); } diff --git a/packages/flame_3d/lib/src/resources/shader/uniform_value.dart b/packages/flame_3d/lib/src/resources/shader/uniform_value.dart index a138dfdf7f9..6b27f8d004c 100644 --- a/packages/flame_3d/lib/src/resources/shader/uniform_value.dart +++ b/packages/flame_3d/lib/src/resources/shader/uniform_value.dart @@ -3,6 +3,7 @@ import 'dart:typed_data'; import 'package:flame_3d/graphics.dart'; import 'package:flame_3d/resources.dart'; +import 'package:ordered_set/comparing.dart'; /// {@template uniform_value} /// Instance of a uniform value. Represented by a [ByteBuffer]. @@ -21,7 +22,9 @@ class UniformValue extends UniformInstance { if (super.resource == null) { var previousIndex = -1; - final data = _storage.entries.fold>([], (p, e) { + final entries = _storage.entries.toList() + ..sort(Comparing.on((c) => c.key)); + final data = entries.fold>([], (p, e) { if (previousIndex + 1 != e.key) { final field = slot.fields.indexed.firstWhere((e) => e.$1 == previousIndex + 1); diff --git a/packages/flame_3d/pubspec.yaml b/packages/flame_3d/pubspec.yaml index 425399a95cd..4649e69d328 100644 --- a/packages/flame_3d/pubspec.yaml +++ b/packages/flame_3d/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: flutter_gpu: sdk: flutter meta: ^1.12.0 + ordered_set: ^6.0.1 vector_math: ^2.1.4 dev_dependencies: diff --git a/packages/flame_3d/shaders/spatial_material.vert b/packages/flame_3d/shaders/spatial_material.vert index 2aecb6f08e9..f7dfc0b4966 100644 --- a/packages/flame_3d/shaders/spatial_material.vert +++ b/packages/flame_3d/shaders/spatial_material.vert @@ -4,6 +4,8 @@ in vec3 vertexPosition; in vec2 vertexTexCoord; in vec4 vertexColor; in vec3 vertexNormal; +in vec4 vertexJoints; +in vec4 vertexWeights; out vec2 fragTexCoord; out vec4 fragColor; @@ -16,18 +18,91 @@ uniform VertexInfo { mat4 projection; } vertex_info; +uniform JointMatrices { + mat4 joint0; + mat4 joint1; + mat4 joint2; + mat4 joint3; + mat4 joint4; + mat4 joint5; + mat4 joint6; + mat4 joint7; + mat4 joint8; + mat4 joint9; + mat4 joint10; + mat4 joint11; + mat4 joint12; + mat4 joint13; + mat4 joint14; + mat4 joint15; +} joints; + +mat4 jointMat(float jointIndex) { + if (jointIndex == 0.0) { + return joints.joint0; + } else if (jointIndex == 1.0) { + return joints.joint1; + } else if (jointIndex == 2.0) { + return joints.joint2; + } else if (jointIndex == 3.0) { + return joints.joint3; + } else if (jointIndex == 4.0) { + return joints.joint4; + } else if (jointIndex == 5.0) { + return joints.joint5; + } else if (jointIndex == 6.0) { + return joints.joint6; + } else if (jointIndex == 7.0) { + return joints.joint7; + } else if (jointIndex == 8.0) { + return joints.joint8; + } else if (jointIndex == 9.0) { + return joints.joint9; + } else if (jointIndex == 10.0) { + return joints.joint10; + } else if (jointIndex == 11.0) { + return joints.joint11; + } else if (jointIndex == 12.0) { + return joints.joint12; + } else if (jointIndex == 13.0) { + return joints.joint13; + } else if (jointIndex == 14.0) { + return joints.joint14; + } else if (jointIndex == 15.0) { + return joints.joint15; + } else { + return mat4(0.0); + } +} + +mat4 computeSkinMatrix() { + if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) { + // no weights, skip skinning + return mat4(1.0); + } + + return vertexWeights.x * jointMat(vertexJoints.x) + + vertexWeights.y * jointMat(vertexJoints.y) + + vertexWeights.z * jointMat(vertexJoints.z) + + vertexWeights.w * jointMat(vertexJoints.w); +} + void main() { + mat4 skinMatrix = computeSkinMatrix(); + vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz; + vec3 normal = normalize((skinMatrix * vec4(vertexNormal, 0.0)).xyz); + // Calculate the modelview projection matrix mat4 modelViewProjection = vertex_info.projection * vertex_info.view * vertex_info.model; // Transform the vertex position - gl_Position = modelViewProjection * vec4(vertexPosition, 1.0); + gl_Position = modelViewProjection * vec4(position, 1.0); // Pass the interpolated values to the fragment shader fragTexCoord = vertexTexCoord; fragColor = vertexColor; // Calculate the world-space position and normal - fragPosition = vec3(vertex_info.model * vec4(vertexPosition, 1.0)); - fragNormal = mat3(transpose(inverse(vertex_info.model))) * vertexNormal; + fragPosition = vec3(vertex_info.model * vec4(position, 1.0)); + fragNormal = mat3(transpose(inverse(vertex_info.model))) * normal; }