🎄

Flutter GPU初期プレビュー版で3Dモデルを回転・拡大縮小できるビューアを作る

2024/12/10に公開

はじめに

こんにちは!
Altive株式会社 Flutterアプリエンジニアの中村です。

Flutter3.24から使えるようになったFlutter GPUで3Dモデルぐるぐる回したり拡大縮小できるビューアを実装してみました。
3Dモデルは MaterialsVariantsShoe をお借りしています👟
https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/MaterialsVariantsShoe

主に参考にした記事はGetting started with Flutter GPUです🙏🏻
https://medium.com/flutter/getting-started-with-flutter-gpu-f33d497b7c11


こんな感じになります

プロジェクトの設定

メインチャンネルに切り替える

Flutter GPUを利用した3DレンダリングライブラリのFlutter Sceneを使って3Dモデルを表示させていきますが、Flutter GPUFlutter Sceneのどちらもプレビュー段階にあるためFlutterのメインチャンネルに切り替えてから使用する必要があります。

flutter channel main
flutter upgrade

ネイティブアセット機能を有効にする

Flutter Sceneは実験的なネイティブアセット機能に依存しているため、有効にする必要があります。

flutter config --enable-native-assets

モデルをインポートする

今回は冒頭で紹介したMaterialsVariantsShoeを使います。

curl -O https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Models/main/2.0/DamagedHelmet/glTF-Binary/MaterialsVariantsShoe.glb

Flutter Scene内で使用できる形式に変換するため、flutter_scene_importerを追加します。

flutter pub add flutter_scene_importer

このパッケージを使用すると、コマンドでモデルの形式を変換できるようになります。
inputoutputのパスは任意の場所に修正してください。

dart --enable-experiment=native-assets \
     run flutter_scene_importer:import \
     --input "path/to/my/source_model.glb" \
     --output "path/to/my/imported_model.model"

出力したモデルをアプリで使用できるように、pubspec.yamlassetにパスを追加します。

flutter:
  assets:
    - build/models/

3Dモデルをアプリに表示する

参考記事だと時間の経過に伴ってモデルをぐるぐる回す例が紹介されていますが、今回はGestureDetectorを使ってユーザーの操作で回転・拡大縮小できるビューアを作ってみました。

import 'dart:async';
import 'dart:math';

import 'package:flutter/material.dart';
import 'package:flutter_scene/scene.dart';
import 'package:vector_math/vector_math.dart';

class FlutterGpuPage extends StatefulWidget {
  const FlutterGpuPage({super.key});

  
  FlutterGpuPageState createState() => FlutterGpuPageState();
}

class FlutterGpuPageState extends State<FlutterGpuPage> {
  double hAngle = 1;
  double vAngle = 1;
  double radius = 1;

  final initResources = Scene.initializeStaticResources();
  final scene = Scene();

  Vector3 calculateCameraPosition() {
    final y = radius * sin(vAngle);
    final x = radius * cos(vAngle) * sin(hAngle);
    final z = radius * cos(vAngle) * cos(hAngle);
    return Vector3(x, y, z);
  }

  
  void initState() {
    super.initState();

    unawaited(
      // アセットからモデルを読み込む
      Node.fromAsset('assets/Shoe.model').then(scene.add),
    );
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Flutter GPU Demo'),
      ),
      body: FutureBuilder(
        // リソースの初期化処理
        future: initResources,
        builder: (context, snapShot) {
          if (snapShot.connectionState != ConnectionState.done) {
            return const Center(
              child: CircularProgressIndicator.adaptive(),
            );
          }

          return Center(
            child: SizedBox(
              height: 500,
              width: MediaQuery.sizeOf(context).width,
              child: GestureDetector(
                child: CustomPaint(
                  painter: ScenePainter(
                    scene: scene,
                    camera: PerspectiveCamera(
                      position: calculateCameraPosition(),
                    ),
                  ),
                ),
                onScaleUpdate: (details) {
                  setState(() {
                    radius = (radius / details.scale).clamp(0.5, 1.0);

                    final dx = details.focalPointDelta.dx / 100;
                    final dy = details.focalPointDelta.dy / 100;

                    hAngle += dx;
                    vAngle += dy;
                    vAngle = vAngle.clamp(-pi / 2, pi / 2);
                  });
                },
              ),
            ),
          );
        },
      ),
    );
  }
}

class ScenePainter extends CustomPainter {
  ScenePainter({required this.scene, required this.camera});

  Scene scene;
  Camera camera;

  
  void paint(Canvas canvas, Size size) {
    scene.render(camera, canvas, viewport: Offset.zero & size);
  }

  
  bool shouldRepaint(covariant CustomPainter oldDelegate) => true;
}

おわりに

この記事ではFlutter GPUFlutter Sceneを使って、3Dモデルを操作できるビューアの作成方法を紹介しました。
Flutterで3Dレンダリングがこんなにも簡単にできるのかと感動しました!

現状ではまだプレビュー段階ですが、今後正式リリースされれば3Dコンテンツの開発がさらに身近になりそうです。

自分でも3Dモデルを作れるようになって、いつかアプリで動かしてみたいです☺️

GitHubで編集を提案
Altiveエンジニアリングブログ

Discussion