diff --git a/packages/flame_3d/assets/shaders/spatial_material.shaderbundle b/packages/flame_3d/assets/shaders/spatial_material.shaderbundle index 99f87218c79..759cf057518 100644 Binary files a/packages/flame_3d/assets/shaders/spatial_material.shaderbundle and b/packages/flame_3d/assets/shaders/spatial_material.shaderbundle differ diff --git a/packages/flame_3d/assets/shaders/unlit_material.shaderbundle b/packages/flame_3d/assets/shaders/unlit_material.shaderbundle index 58a5a8a3e1e..9db23b4d989 100644 Binary files a/packages/flame_3d/assets/shaders/unlit_material.shaderbundle and b/packages/flame_3d/assets/shaders/unlit_material.shaderbundle differ diff --git a/packages/flame_3d/bin/build_shaders.dart b/packages/flame_3d/bin/build_shaders.dart index 7bb7d3beeeb..741ae949b4a 100644 --- a/packages/flame_3d/bin/build_shaders.dart +++ b/packages/flame_3d/bin/build_shaders.dart @@ -1,5 +1,6 @@ import 'dart:convert'; import 'dart:io'; +import 'dart:isolate'; /// Bundle a shader ('name'.frag & 'name'.vert) into a single shader bundle and /// store it in the assets directory. @@ -8,23 +9,68 @@ import 'dart:io'; /// Flutter might support auto-bundling themselves but until then we have to /// do it manually. /// -/// Note: this script should be run from the root of the package: -/// packages/flame_3d +/// Run from the package root whose shaders are being built. When invoked +/// from a consumer package via `dart run flame_3d:build_shaders`, the +/// consumer's own `shaders/` is bundled. Every Dart dependency that ships a +/// top-level `shaders/` directory is added to impellerc's include path under +/// its package name, so shaders can `#include ` against +/// any of them. `` resolves to the engine builtins. void main(List arguments) async { final root = Directory.current; final assets = Directory.fromUri(root.uri.resolve('assets/shaders')); final shaders = Directory.fromUri(root.uri.resolve('shaders')); + final packageShaderDirs = await _resolvePackageShaderDirs(); - await compute(assets, shaders); + await compute(assets, shaders, packageShaderDirs); if (arguments.contains('watch')) { stdout.writeln('Running in watch mode'); shaders.watch(recursive: true).listen((event) { - compute(assets, shaders); + compute(assets, shaders, packageShaderDirs); }); } } -Future compute(Directory assets, Directory shaders) async { +/// Returns every Dart dependency's top-level `shaders/` directory, so an +/// `#include ` can resolve to +/// `/shaders/pkg_name/foo.glsl`. +Future> _resolvePackageShaderDirs() async { + final configUri = await Isolate.packageConfig; + if (configUri == null) { + throw Exception( + 'Unable to locate package_config.json. Run `dart pub get` first.', + ); + } + + final configFile = File.fromUri(configUri); + final config = + jsonDecode(configFile.readAsStringSync()) as Map; + final packages = (config['packages'] as List).cast>(); + final result = []; + for (final package in packages) { + final name = package['name'] as String; + if (name == 'flutter') { + // `flutter` ships no shader chunks; its includes come from the engine. + continue; + } + + final rootUriRaw = package['rootUri'] as String; + final rootUri = configUri.resolve( + rootUriRaw.endsWith('/') ? rootUriRaw : '$rootUriRaw/', + ); + + final shaderDir = Directory.fromUri(rootUri.resolve('shaders/')); + if (shaderDir.existsSync()) { + result.add(shaderDir); + } + } + return result; +} + +Future compute( + Directory assets, + Directory shaders, + List packageShaderDirs, +) async { // Delete all the bundled shaders so we can replace them with new ones. if (assets.existsSync()) { assets.deleteSync(recursive: true); @@ -44,6 +90,9 @@ Future compute(Directory assets, Directory shaders) async { .map((f) => f.path.split(Platform.pathSeparator).last.split('.').first) .toSet(); + final impellerC = await findImpellerC(); + final engineShaderLib = impellerC.resolve('./shader_lib/').toFilePath(); + for (final name in uniqueShaders) { final bundle = { 'TextureFragment': { @@ -57,14 +106,17 @@ Future compute(Directory assets, Directory shaders) async { }; stdout.writeln('Computing shader "$name"'); - final impellerC = await findImpellerC(); final result = await Process.run(impellerC.toFilePath(), [ '--sl=${assets.path}${Platform.pathSeparator}$name.shaderbundle', '--shader-bundle=${jsonEncode(bundle)}', + '--include=${shaders.path}', + for (final dir in packageShaderDirs) '--include=${dir.path}', + '--include=$engineShaderLib', ]); if (result.exitCode != 0) { - return stderr.writeln(result.stderr); + stderr.writeln('Failed to compile shader "$name":\n${result.stderr}'); + exitCode = 1; } } } @@ -126,7 +178,7 @@ Future findImpellerC() async { // ignore: do_not_use_environment const impellercEnvVar = String.fromEnvironment('IMPELLERC'); if (impellercEnvVar != '') { - if (!doesFileExist(impellercEnvVar)) { + if (!File(impellercEnvVar).existsSync()) { throw Exception( 'IMPELLERC environment variable is set, ' "but it doesn't point to a valid file!", @@ -147,7 +199,7 @@ Future findImpellerC() async { final tried = []; for (final variant in _impellercLocations) { final impellercPath = engineArtifactsDir.resolve(variant); - if (doesFileExist(impellercPath.toFilePath())) { + if (File(impellercPath.toFilePath()).existsSync()) { found = impellercPath; break; } @@ -161,7 +213,3 @@ Future findImpellerC() async { return found; } - -bool doesFileExist(String path) { - return File(path).existsSync(); -} diff --git a/packages/flame_3d/lib/src/resources/material/unlit_material.dart b/packages/flame_3d/lib/src/resources/material/unlit_material.dart index 806a5c59e82..bd999cf557e 100644 --- a/packages/flame_3d/lib/src/resources/material/unlit_material.dart +++ b/packages/flame_3d/lib/src/resources/material/unlit_material.dart @@ -24,7 +24,7 @@ class UnlitMaterial extends Material { super( vertexShader: VertexShader.fromAsset( 'packages/flame_3d/assets/shaders/unlit_material.shaderbundle', - slots: ['VertexInfo'], + slots: ['VertexInfo', 'JointMatrices'], ), fragmentShader: FragmentShader.fromAsset( 'packages/flame_3d/assets/shaders/unlit_material.shaderbundle', @@ -45,8 +45,21 @@ class UnlitMaterial extends Material { ..setMatrix4('VertexInfo.view', context.view) ..setMatrix4('VertexInfo.projection', context.projection); + final jointTransforms = context.jointsInfo.jointTransforms; + if (jointTransforms.length > _maxJoints) { + throw Exception( + 'At most $_maxJoints joints per surface are supported;' + ' found ${jointTransforms.length}', + ); + } + for (final (index, transform) in jointTransforms.indexed) { + vertexShader.setMatrix4('JointMatrices.joints[$index]', transform); + } + fragmentShader ..setTexture('albedoTexture', albedoTexture) ..setColor('Material.albedoColor', albedoColor); } + + static const _maxJoints = 16; } diff --git a/packages/flame_3d/shaders/flame_3d/skinning.glsl b/packages/flame_3d/shaders/flame_3d/skinning.glsl new file mode 100644 index 00000000000..9c595f2ea09 --- /dev/null +++ b/packages/flame_3d/shaders/flame_3d/skinning.glsl @@ -0,0 +1,22 @@ +#ifndef FLAME_SKINNING_GLSL_ +#define FLAME_SKINNING_GLSL_ + +in vec4 vertexJoints; +in vec4 vertexWeights; + +uniform JointMatrices { + mat4 joints[16]; +} jointMatrices; + +mat4 computeSkinMatrix() { + if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) { + return mat4(1.0); + } + + return vertexWeights.x * jointMatrices.joints[int(vertexJoints.x)] + + vertexWeights.y * jointMatrices.joints[int(vertexJoints.y)] + + vertexWeights.z * jointMatrices.joints[int(vertexJoints.z)] + + vertexWeights.w * jointMatrices.joints[int(vertexJoints.w)]; +} + +#endif diff --git a/packages/flame_3d/shaders/spatial_material.vert b/packages/flame_3d/shaders/spatial_material.vert index 4337d802a22..0de19118f13 100644 --- a/packages/flame_3d/shaders/spatial_material.vert +++ b/packages/flame_3d/shaders/spatial_material.vert @@ -4,8 +4,8 @@ in vec3 vertexPosition; in vec2 vertexTexCoord; in vec4 vertexColor; in vec3 vertexNormal; -in vec4 vertexJoints; -in vec4 vertexWeights; + +#include out vec2 fragTexCoord; out vec4 fragColor; @@ -18,21 +18,6 @@ uniform VertexInfo { mat4 projection; } vertex_info; -uniform JointMatrices { - mat4 joints[16]; -} jointMatrices; - -mat4 computeSkinMatrix() { - if (vertexWeights.x == 0.0 && vertexWeights.y == 0.0 && vertexWeights.z == 0.0 && vertexWeights.w == 0.0) { - return mat4(1.0); - } - - return vertexWeights.x * jointMatrices.joints[int(vertexJoints.x)] + - vertexWeights.y * jointMatrices.joints[int(vertexJoints.y)] + - vertexWeights.z * jointMatrices.joints[int(vertexJoints.z)] + - vertexWeights.w * jointMatrices.joints[int(vertexJoints.w)]; -} - void main() { mat4 skinMatrix = computeSkinMatrix(); vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz; diff --git a/packages/flame_3d/shaders/unlit_material.vert b/packages/flame_3d/shaders/unlit_material.vert index bc6fb7dad98..c59c6d076b9 100644 --- a/packages/flame_3d/shaders/unlit_material.vert +++ b/packages/flame_3d/shaders/unlit_material.vert @@ -4,8 +4,8 @@ in vec3 vertexPosition; in vec2 vertexTexCoord; in vec4 vertexColor; in vec3 vertexNormal; -in vec4 vertexJoints; -in vec4 vertexWeights; + +#include out vec2 fragTexCoord; out vec4 fragColor; @@ -19,14 +19,15 @@ uniform VertexInfo { } vertex_info; void main() { - mat4 mvp = vertex_info.projection * vertex_info.view * vertex_info.model; - gl_Position = mvp * vec4(vertexPosition, 1.0); + mat4 skinMatrix = computeSkinMatrix(); + vec3 position = (skinMatrix * vec4(vertexPosition, 1.0)).xyz; + vec3 normal = normalize((skinMatrix * vec4(vertexNormal, 0.0)).xyz); + + mat4 modelViewProjection = vertex_info.projection * vertex_info.view * vertex_info.model; + gl_Position = modelViewProjection * vec4(position, 1.0); fragTexCoord = vertexTexCoord; fragColor = vertexColor; - - // Pass through all vertex attributes so the compiler doesn't strip them, - // which would break the vertex buffer layout. - fragPosition = vertexPosition + vertexJoints.xyz * vertexWeights.x; - fragNormal = vertexNormal; + fragPosition = vec3(vertex_info.model * vec4(position, 1.0)); + fragNormal = mat3(transpose(inverse(vertex_info.model))) * normal; }