Flutter加载3D模型

4,255 阅读1分钟

思路

因为Flutter目前不支持3D图形API,所以考虑通过嵌入网页使用WebGL API来跨平台。 嵌入网页使用官方的webview_flutter插件,但是webview_flutter对加载本地网页没有提供便利的API, 通过Data URLs编码较大的3D模型在这种方案下也无法正常工作,最后依靠搭建本地http服务器来加载本地网页内容。

使用three.js加载3D模型

在项目根目录新建www文件夹,在pubspec.yaml里添加www文件夹:

flutter:
  # ...
  assets:
    - www/

在www文件夹里添加3d模型文件(如test.glb)、three.js和相关的GLTFLoader.js(可用于加载.glb文件)。新建index.js:

var scene = new THREE.Scene()
var camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 100)

var renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true })
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

camera.position.z = 80

var light = new THREE.PointLight()
light.position.copy(camera.position)
scene.add(light)

var ambientLight = new THREE.AmbientLight(0x999999)
scene.add(ambientLight)

var loader = new THREE.GLTFLoader()
loader.load("./test.glb", function (gltf) {
    scene.add(gltf.scene)
}, undefined, function (error) {
    console.error(error)
})

setInterval(() => renderer.render(scene, camera), 1000 / 60)

新建index.html:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>3D</title>
    <style>
        body {
            margin: 0;
        }

        canvas {
            display: block;
        }
    </style>
</head>

<body>
    <script src="./three.js"></script>
    <script src="./GLTFLoader.js"></script>
    <script src="./index.js"></script>
</body>

</html>

添加webview_flutter和local_assets_server插件

local_assets_server插件用于搭建本地静态资源服务器,运行以下命令来添加插件:

flutter pub add local_assets_server
flutter pub add webview_flutter

嵌入网页

可以把lib/main.dart修改成如下内容:

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:local_assets_server/local_assets_server.dart';
import 'package:webview_flutter/webview_flutter.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _isListening = false;
  String? _address;
  int? _port;

  @override
  initState() {
    _initServer();
    super.initState();
  }

  _initServer() async {
    final server = LocalAssetsServer(
      address: InternetAddress.loopbackIPv4,
      assetsBasePath: 'www',
      logger: DebugLogger(),
    );

    final address = await server.serve();

    setState(() {
      _address = address.address;
      _port = server.boundPort!;
      _isListening = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('3D'),
      ),
      body: _isListening
          ? WebView(
              debuggingEnabled: true,
              initialUrl: 'http://$_address:$_port',
              javascriptMode: JavascriptMode.unrestricted,
            )
          : Text('Loading...'),
    );
  }
}

安卓特别配置

打开android/app/src/main/AndroidManifest.xml,在application下添加android:usesCleartextTraffic="true"来允许使用http明文网络流量。

打开android/app/build.gradle,修改minSdkVersion为19,以满足webview_flutter插件的最低版本要求。