こんにちは、虎の穴ラボの吉岡です。
今回はFlutter 3.24で追加された Flutter GPU
の公式サンプルを参考に簡単な3DCGビュアーを作ってみようと思います。
Flutter GPUはまだ初期プレビュー版のため iOS, Android, MacOSにのみ対応しています。
そのため今回はMacOSアプリとして実行します。
事前準備
Flutter GPUを使うためにはFlutterの最新版3.24を使用し、main(master)チャンネルに切り替える必要があります。
そのため普段stableチャンネル等を使用している場合はmainに切り替えてFlutterをアップグレードをしてください。
$ flutter channel main $ flutter upgrade $ flutter --version Flutter 3.24.0-1.0.pre.539 ・channel main・ ~~~
プロジェクト作成
今回使用するプロジェクトを作成して、必要な設定の反映し、パッケージの追加を行っていきます
$ flutter create my_3d_app $ flutter config --enable-native-assets $ flutter pub add flutter_scene vector_math
有効にした native-assets
はDartでネイティブライブラリなどの自動ビルドや自動インポートをサポートする機能です。
今回は3Dモデルの自動インポートをするために使用します。
パッケージは2つ追加しています。
- flutter_scene: Flutterで3Dを扱うためのライブラリです。
- vector_math: ベルトルの計算などをするためのライブラリです。
3Dモデルの準備
今回表示する3DモデルはglTF-Sample-AssetsのDamagedHelmetを使用させていただきます。
3DモデルのファイルをFlutterのプロジェクトルートへ置きます。
$ curl -O https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/DamagedHelmet/glTF-Binary/DamagedHelmet.glb
また、glb
形式はFlutter上で扱えないため、model
ファイルに変換します。
コマンドでファイルを変換する方法もありますが今回は native_assets_cli
のライブラリを使いhookで自動変換されるようにしていきます。
プロジェクトの中にhook
ディレクトリを作成しその中に build.dart
ファイルを用意します。
import 'package:native_assets_cli/native_assets_cli.dart'; import 'package:flutter_scene_importer/build_hooks.dart'; void main(List<String> args) { build(args, (config, output) async { buildModels(buildConfig: config, inputFilePaths: [ 'DamagedHelmet.glb', // プロジェクトルートに置いた3Dモデルのファイル ]); }); }
これでFlutterを実行、ビルドする際に自動で.model形式のファイルを生成してくれるようになります。
変換されたファイルをアプリの中で使用するためにpubspec.yaml
へassetsの読み込みに追加します.
flutter: assets: - build/models/
実装
今回はサンプルを参考に、3Dモデルを自由に横方向に回して表示できるビュアーを作ります。
lib/main.dart
アプリ起動時の読み込みと基本的な表示、操作周りはmain.dartにまとめています。
import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_scene/camera.dart'; import 'package:flutter_scene/node.dart'; import 'package:flutter_scene/scene.dart'; import 'package:my_3d_app/scene_painter.dart'; import 'package:vector_math/vector_math.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override MyAppState createState() => MyAppState(); } class MyAppState extends State<MyApp> with SingleTickerProviderStateMixin { double horizontal = 10.0; // 横(x軸)の入力を受けるstate Scene scene = Scene(); @override void initState() { // 3Dモデルの読み込み、hookで自動ビルドされた .modelのファイルを読み込む Node.fromAsset('build/models/DamagedHelmet.model').then((model) { model.name = 'Helmet'; // 読み込んだ3DモデルをSceneへ追加する scene.add(model); }); super.initState(); } @override Widget build(BuildContext context) { // 読み込んだ3Dモデルを持つSceneを使用する // CustomPainterを継承したクラス(後記)のオブジェクトを生成する // ユーザのマウス入力があった際は3Dモデルの表示を自動で更新する final painter = ScenePainter( scene: scene, camera: PerspectiveCamera( position: Vector3(sin(horizontal) * 3, 2, cos(horizontal) * 3), target: Vector3(0, 0, 0), ), ); return MaterialApp( title: 'My 3D app', // ユーザのマウス(画面タップ)操作を検知するために // GestureDetector Widgetを使用する home: GestureDetector( // 表示は上で作ったpainterを渡したCustomPaint Widgetが行う child: CustomPaint(painter: painter), // 今回は横(x軸)方向の入力だけ受けるため // onHorizontalDragUpdateのコールバックに処理を記述する onHorizontalDragUpdate: (DragUpdateDetails details) { // 入力された方向に3Dモデルを回転させるために // x軸の入力を加算する(そのままでは入力量が大きいため 1/10にしておく) setState(() { horizontal += details.delta.dx / 10; }); }, ), ); } }
lib/scene_painter.dart
今回3Dを表示するために使用する CustomPaint
を継承したクラスを用意します。
ロードした3Dモデルを持つ Scene
を持つのと shouldRepaint
を trueにすることで再描画を有効にします。
import 'package:flutter/material.dart'; import 'package:flutter_scene/camera.dart'; import 'package:flutter_scene/scene.dart'; class ScenePainter extends CustomPainter { ScenePainter({required this.scene, required this.camera}); Scene scene; Camera camera; @override void paint(Canvas canvas, Size size) { scene.render(camera, canvas, viewport: Offset.zero & size); } // 入力情報が更新されたら描画も更新するために trueを返す @override bool shouldRepaint(covariant CustomPainter oldDelegate) => true; }
実行
これで3D表示する準備はできたので後は実行するだけです!
$ flutter run -d macos --enable-impeller
実行結果
マウスをクリックした状態で左右に動かすことで連動して表示されている3Dモデルも左右に動く様に表示されました!
まとめ
これだけのコードで3Dモデルを表示できるようになりました。
今まではFlutterを使う場合はそれぞれSwiftやKotlinなどネイティブのコードを書く必要があったのが、
Flutter(Dart)のみで解決できるようになったのでとても難易度が下がった気がします。
まだ実験的な機能ではありますがこれからWebなどへの対応などアップデートを楽しみにして待っていましょう。
採用情報
虎の穴ラボでは一緒に働く仲間を募集中です!
この記事を読んで、興味を持っていただけた方はぜひ弊社の採用情報をご覧ください。
toranoana-lab.co.jp