Flutter高级加载器插件three_js_advanced_loaders的使用
Flutter高级加载器插件three_js_advanced_loaders的使用
这是一个允许用户将gltf、glb或fbx文件添加到项目的three_js模型加载器。

此项目是由@mrdoob创建的three.js和three_dart的dart版本,并由@wasabia进行了dart转换。
开始使用
在pubspec.yaml
文件中添加此依赖项以及three_js_math、three_js_core、three_js_animations和three_js_core_loaders。
late Scene scene;
void init() {
scene = Scene();
scene.background = Color.fromHex32(0xf0f0f0);
final loader = GLTFLoader();
final gltf = await loader.fromAsset('assets/${fileName}.glb');
mesh = gltf!.scene.children[0];
mesh.scale.setValues(1.5, 1.5, 1.5);
scene.add(mesh);
}
使用方法
此项目是一个更高级的three_js模型加载器。
示例代码
以下是完整的示例代码:
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:three_js_core/three_js_core.dart' as three;
import 'package:three_js_animations/three_js_animations.dart';
import 'package:three_js_helpers/three_js_helpers.dart';
import 'package:three_js_advanced_loaders/three_js_advanced_loaders.dart';
import 'package:three_js_controls/three_js_controls.dart';
import 'package:three_js_math/three_js_math.dart' as tmath;
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
),
home: const WebglAnimationMultiple(),
);
}
}
class WebglAnimationMultiple extends StatefulWidget {
const WebglAnimationMultiple({super.key});
@override
createState() => _State();
}
class _State extends State<WebglAnimationMultiple> {
late three.ThreeJS threeJs;
@override
void initState() {
threeJs = three.ThreeJS(
settings: three.Settings(
useSourceTexture: true
),
onSetupComplete: (){setState(() {});},
setup: setup
);
super.initState();
}
@override
void dispose() {
threeJs.dispose();
controls.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return threeJs.build();
}
late OrbitControls controls;
late List<Map<String, dynamic>> models;
late List<Map<String, dynamic>> units;
final List<AnimationMixer> mixers = []; // 所有场景中动画的three.AnimationMixer对象列表
int numLoadedModels = 0;
Future<void> setup() async {
models = [
{"name": "Soldier"},
{"name": "Parrot"},
];
// 定义我们想放置在场景中的模型实例,包括位置、缩放和要播放的动画名称
units = [
{
"modelName": "Soldier", // 将使用models/gltf/Soldier.glb中的3D模型
"meshName": "vanguard_Mesh", // 要动画的主要网格名称
"position": {"x": 0, "y": 0, "z": 0}, // 在场景中的放置位置
"scale": 1, // 单位的缩放比例。1.0表示原始大小,0.1表示“缩小10倍”
"animationName": "Idle" // 要运行的动画名称
},
{
"modelName": "Soldier",
"meshName": "vanguard_Mesh",
"position": {"x": 3, "y": 0, "z": 0},
"scale": 2,
"animationName": "Walk"
},
{
"modelName": "Soldier",
"meshName": "vanguard_Mesh",
"position": {"x": 1, "y": 0, "z": 0},
"scale": 1,
"animationName": "Run"
},
{
"modelName": "Parrot",
"meshName": "mesh_0",
"position": {"x": -4, "y": 0, "z": 0},
"rotation": {"x": 0, "y": math.pi, "z": 0},
"scale": 0.01,
"animationName": "parrot_A_"
},
{
"modelName": "Parrot",
"meshName": "mesh_0",
"position": {"x": -2, "y": 0, "z": 0},
"rotation": {"x": 0, "y": math.pi / 2, "z": 0},
"scale": 0.02,
"animationName": null
},
];
setup2();
loadModels();
}
void setup2() {
threeJs.camera = three.PerspectiveCamera(45, threeJs.width / threeJs.height, 1, 10000);
threeJs.camera.position.setValues(3, 6, -10);
threeJs.camera.lookAt(tmath.Vector3(0, 1, 0));
threeJs.scene = three.Scene();
threeJs.scene.background = tmath.Color.fromHex32(0xa0a0a0);
threeJs.scene.fog = three.Fog(0xa0a0a0, 10, 22);
final hemiLight = three.HemisphereLight(0xffffff, 0x444444);
hemiLight.position.setValues(0, 20, 0);
threeJs.scene.add(hemiLight);
final dirLight = three.DirectionalLight(0xffffff);
dirLight.position.setValues(-3, 10, -10);
dirLight.castShadow = true;
dirLight.shadow!.camera!.top = 10;
dirLight.shadow!.camera!.bottom = -10;
dirLight.shadow!.camera!.left = -10;
dirLight.shadow!.camera!.right = 10;
dirLight.shadow!.camera!.near = 0.1;
dirLight.shadow!.camera!.far = 40;
threeJs.scene.add(dirLight);
controls = OrbitControls(threeJs.camera, threeJs.globalKey);
// 地面
final groundMesh = three.Mesh(three.PlaneGeometry(40, 40), three.MeshPhongMaterial.fromMap({"color": 0x999999, "depthWrite": false}));
groundMesh.rotation.x = -math.pi / 2;
groundMesh.receiveShadow = true;
threeJs.scene.add(groundMesh);
}
void loadModels() {
for (int i = 0; i < models.length; ++i) {
final m = models[i];
loadGltfModel(m, () {
++numLoadedModels;
if (numLoadedModels == models.length) {
three.console.info("所有模型已加载,现在可以实例化单位...");
instantiateUnits();
}
});
}
}
void instantiateUnits() {
int numSuccess = 0;
for (int i = 0; i < units.length; ++i) {
final u = units[i];
final model = getModelByName(u["modelName"]);
if (model != null) {
final clonedScene = SkeletonUtils.clone(model["scene"]);
if (clonedScene != null) {
// three.Scene克隆成功,找到一个网格并启动动画
final clonedMesh = clonedScene.getObjectByName(u["meshName"]);
if (clonedMesh != null) {
final mixer = startAnimation(
clonedMesh,
List<AnimationClip>.from(model["animations"]),
u["animationName"]);
// 保存动画混合器,将在动画循环中使用
mixers.add(mixer);
numSuccess++;
}
threeJs.scene.add(clonedScene);
if (u["position"] != null) {
clonedScene.position.setValues(
u["position"]["x"].toDouble(), u["position"]["y"].toDouble(), u["position"]["z"].toDouble());
}
if (u["scale"] != null) {
clonedScene.scale.setValues(u["scale"].toDouble(), u["scale"].toDouble(), u["scale"].toDouble());
}
if (u["rotation"] != null) {
clonedScene.rotation.x = u["rotation"]["x"].toDouble();
clonedScene.rotation.y = u["rotation"]["y"].toDouble();
clonedScene.rotation.z = u["rotation"]["z"].toDouble();
}
}
} else {
three.console.info("无法找到模型 ${u["modelName"]}");
}
}
three.console.info(" 成功实例化了 $numSuccess 个单位 ");
threeJs.addAnimationEvent((dt){
for (int i = 0; i < mixers.length; ++i) {
mixers[i].update(dt);
}
controls.update();
});
}
AnimationMixer startAnimation(
three.Object3D skinnedMesh,
List<AnimationClip> animations,
String animationName
) {
final mixer = AnimationMixer(skinnedMesh);
final clip = AnimationClip.findByName(animations, animationName);
if (clip != null) {
final action = mixer.clipAction(clip);
action!.play();
}
return mixer;
}
Map<String, dynamic>? getModelByName(String name) {
for (int i = 0; i < models.length; ++i) {
if (models[i]["name"] == name) {
return models[i];
}
}
return null;
}
void loadGltfModel(model, onLoaded) {
final loader = GLTFLoader();
final modelName = "assets/${model["name"]}.gltf";
loader.fromAsset(modelName).then((gltf) {
final scene = gltf!.scene;
model["animations"] = gltf.animations;
model["scene"] = scene;
gltf.scene.traverse((object) {
if (object is three.Mesh) {
object.castShadow = true;
}
});
three.console.info("完成加载模型 ${model["name"]} ");
onLoaded();
});
}
}
更多关于Flutter高级加载器插件three_js_advanced_loaders的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter高级加载器插件three_js_advanced_loaders的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter项目中使用three_js_advanced_loaders
插件的示例代码。这个插件允许你在Flutter应用中使用Three.js及其高级加载器来加载和处理3D模型。
首先,你需要确保你的Flutter项目中已经包含了three_js_advanced_loaders
插件。如果还没有添加,可以在pubspec.yaml
文件中添加依赖:
dependencies:
flutter:
sdk: flutter
three_js_advanced_loaders: ^最新版本号 # 替换为实际的最新版本号
然后运行flutter pub get
来安装依赖。
以下是一个基本的示例代码,展示如何在Flutter中使用three_js_advanced_loaders
来加载并显示一个GLTF格式的3D模型。
import 'package:flutter/material.dart';
import 'package:three_js_advanced_loaders/three_js_advanced_loaders.dart';
import 'dart:html' as html;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('Three.js Advanced Loaders Example'),
),
body: WebViewExample(),
),
);
}
}
class WebViewExample extends StatefulWidget {
@override
_WebViewExampleState createState() => _WebViewExampleState();
}
class _WebViewExampleState extends State<WebViewExample> {
late html.Element _webViewContainer;
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(
child: LayoutBuilder(
builder: (BuildContext context, BoxConstraints constraints) {
_webViewContainer = html.div();
_webViewContainer.style.width = '${constraints.maxWidth}px';
_webViewContainer.style.height = '${constraints.maxHeight}px';
html.document.body?.append(_webViewContainer);
// Initialize Three.js and load the model
_initThreeJS();
return Container(); // Placeholder to take up space
},
),
),
],
);
}
void _initThreeJS() {
// Create the Three.js scene, camera, and renderer
final scene = THREE.Scene();
final camera = THREE.PerspectiveCamera(75, _webViewContainer.clientWidth / _webViewContainer.clientHeight, 0.1, 1000);
final renderer = THREE.WebGLRenderer({ antialias: true });
renderer.setSize(_webViewContainer.clientWidth, _webViewContainer.clientHeight);
_webViewContainer.append(renderer.domElement);
// Set the camera position
camera.position.z = 5;
// Create a directional light
final light = THREE.DirectionalLight(0xffffff, 1);
light.position.set(5, 10, 7.5);
scene.add(light);
// Create a helper for the camera (optional, for debugging)
final cameraHelper = THREE.CameraHelper(camera, 0.1);
scene.add(cameraHelper);
// Load the GLTF model using the GLTFLoader
final loader = THREE.GLTFLoader();
loader.load('path/to/your/model.glb', (gltf) {
scene.add(gltf.scene);
animate();
}, undefined, (error) {
console.error(error);
});
// Animation loop
void animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
// Start the animation loop
animate();
}
}
注意:
-
由于Flutter的Web视图限制,上述代码需要在Flutter Web环境中运行。对于移动或桌面平台,你需要使用其他方法(如
platform_view
或原生插件)来集成Three.js。 -
path/to/your/model.glb
需要替换为你实际的GLTF模型文件路径。 -
上述代码使用了
LayoutBuilder
来动态调整Web视图的尺寸,确保Three.js渲染器可以正确调整大小。 -
这是一个非常基础的示例,实际项目中可能需要更多的错误处理和优化。
希望这个示例能帮助你在Flutter项目中集成和使用three_js_advanced_loaders
插件!