You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中model_viewer_plus修改3D纹理后AR模式不生效的解决方案咨询

解决方案:Flutter AR模式下GLB模型纹理同步更新问题

核心原因

Flutter中AR场景的渲染上下文与普通3D渲染上下文通常是分离的,AR模式下的模型实例是独立于普通模式的副本,导致纹理变更无法自动同步。而React Web中Three.js的XR模式共享同一渲染上下文和模型实例,因此纹理修改能直接生效。

可行实现方案

1. 共享纹理资源实例

维护全局的纹理对象,普通模式和AR模式都引用该实例,更新时直接修改该实例的数据源,避免重复加载。

2. 手动同步AR场景中的模型纹理

进入AR模式后,获取AR场景内的模型节点,将共享纹理绑定到其材质上;每次纹理更新时,同步更新AR模型的材质并触发渲染刷新。

3. 强制AR渲染上下文刷新

纹理更新后,通知AR会话重新渲染模型,确保变更立即生效。


Flutter 实现代码示例

import 'package:flutter/material.dart';
import 'package:three_dart/three_dart.dart';
import 'package:ar_flutter_plugin/ar_flutter_plugin.dart';
import 'package:ar_flutter_plugin/datatypes/config_planedetection.dart';
import 'package:ar_flutter_plugin/datatypes/node_types.dart';
import 'package:ar_flutter_plugin/managers/ar_object_manager.dart';
import 'package:ar_flutter_plugin/managers/ar_session_manager.dart';
import 'package:ar_flutter_plugin/models/ar_node.dart';

class TileARViewer extends StatefulWidget {
  @override
  _TileARViewerState createState() => _TileARViewerState();
}

class _TileARViewerState extends State<TileARViewer> {
  late ARSessionManager _arSessionManager;
  late ARObjectManager _arObjectManager;
  late Mesh _tileMesh;
  late Texture _currentTexture;
  bool _isARMode = false;
  ARNode? _arTileNode;

  // 初始化GLB模型与默认纹理
  Future<void> _initTileModel() async {
    final gltfLoader = GLTFLoader();
    final gltf = await gltfLoader.loadAsync('assets/tile.glb');
    _tileMesh = gltf.scene.children.first as Mesh;

    final texLoader = TextureLoader();
    _currentTexture = await texLoader.loadAsync('assets/default_tile.png');
    (_tileMesh.material as MeshStandardMaterial).map = _currentTexture;
  }

  // 统一纹理更新方法
  Future<void> _updateTexture(String texturePath) async {
    // 更新共享纹理实例
    final texLoader = TextureLoader();
    _currentTexture = await texLoader.loadAsync(texturePath);
    
    // 更新普通模式下的模型纹理
    (_tileMesh.material as MeshStandardMaterial).map = _currentTexture;
    
    // 同步到AR模式下的模型
    if (_isARMode && _arTileNode != null) {
      final arMesh = _arTileNode!.transformableNode!.children.first as Mesh;
      (arMesh.material as MeshStandardMaterial).map = _currentTexture;
      // 强制AR会话刷新节点
      _arSessionManager.updateNodes();
    }
    setState(() {});
  }

  // 初始化AR会话并添加模型
  Future<void> _initARSession() async {
    _arSessionManager = ARSessionManager(
      onARSessionStarted: () {},
      onARSessionEnded: () {},
    );
    _arObjectManager = await _arSessionManager.addObjectManager(
      ARObjectManagerConfig(
        planeDetectionConfig: PlaneDetectionConfig.horizontalAndVertical,
      ),
    );
    
    _arTileNode = ARNode(
      type: NodeType.localGLTF,
      uri: 'assets/tile.glb',
      name: 'tile_ar_node',
    );
    await _arObjectManager.addNode(_arTileNode!);
    // 同步当前纹理到AR模型
    await _updateTexture(_currentTexture.image.src);
  }

  @override
  void initState() {
    super.initState();
    _initTileModel();
  }

  @override
  void dispose() {
    if (_isARMode) _arSessionManager.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('瓷砖3D查看器')),
      body: Column(
        children: [
          // 纹理选择按钮组
          Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              ElevatedButton(
                onPressed: () => _updateTexture('assets/texture1.png'),
                child: const Text('纹理1'),
              ),
              const SizedBox(width: 16),
              ElevatedButton(
                onPressed: () => _updateTexture('assets/texture2.png'),
                child: const Text('纹理2'),
              ),
            ],
          ),
          // AR模式切换按钮
          ElevatedButton(
            onPressed: () async {
              setState(() => _isARMode = !_isARMode);
              if (_isARMode) {
                await _initARSession();
              } else {
                _arSessionManager.dispose();
                _arTileNode = null;
              }
            },
            child: Text(_isARMode ? '退出AR' : '进入AR'),
          ),
          // 渲染区域
          Expanded(
            child: _isARMode
                ? ARView(onARViewCreated: (manager) => _arSessionManager = manager)
                : ThreeDartViewer(
                    onViewerCreated: (viewer) {
                      viewer.scene.add(_tileMesh);
                      viewer.camera.position.set(0, 0, 5);
                      viewer.render();
                    },
                  ),
          ),
        ],
      ),
    );
  }
}

React Web 实现代码示例(对比参考)

import React, { useRef, useState, useEffect } from 'react';
import { GLTFLoader, TextureLoader } from 'three/examples/jsm/loaders';
import * as THREE from 'three';
import { ARButton } from 'three/examples/jsm/webxr/ARButton';

function TileViewer() {
  const containerRef = useRef(null);
  const [currentTexture, setCurrentTexture] = useState(null);
  const sceneRef = useRef(new THREE.Scene());
  const cameraRef = useRef(new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 1000));
  const rendererRef = useRef(new THREE.WebGLRenderer({ antialias: true, alpha: true }));
  const tileMeshRef = useRef(null);

  useEffect(() => {
    // 加载GLB模型
    const gltfLoader = new GLTFLoader();
    gltfLoader.load('/tile.glb', (gltf) => {
      tileMeshRef.current = gltf.scene.children[0];
      sceneRef.current.add(tileMeshRef.current);
      // 加载默认纹理
      const texLoader = new TextureLoader();
      texLoader.load('/default_tile.png', (texture) => {
        setCurrentTexture(texture);
        tileMeshRef.current.material.map = texture;
      });
    });

    // 配置渲染器与XR支持
    const renderer = rendererRef.current;
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.xr.enabled = true;
    containerRef.current.appendChild(renderer.domElement);
    containerRef.current.appendChild(ARButton.createButton(renderer));

    // 渲染循环
    renderer.setAnimationLoop(() => renderer.render(sceneRef.current, cameraRef.current));

    return () => {
      containerRef.current.removeChild(renderer.domElement);
      renderer.dispose();
    };
  }, []);

  // 更新纹理方法
  const updateTexture = (texturePath) => {
    const texLoader = new TextureLoader();
    texLoader.load(texturePath, (texture) => {
      setCurrentTexture(texture);
      tileMeshRef.current.material.map = texture;
      tileMeshRef.current.material.needsUpdate = true; // 标记材质需要更新
    });
  };

  return (
    <div ref={containerRef}>
      <div style={{ position: 'absolute', top: 16, left: 16, zIndex: 1 }}>
        <button onClick={() => updateTexture('/texture1.png')} style={{ marginRight: 8 }}>纹理1</button>
        <button onClick={() => updateTexture('/texture2.png')}>纹理2</button>
      </div>
    </div>
  );
}

export default TileViewer;

关键差异说明

  • React Web中,Three.js的XR模式与普通模式共享同一渲染上下文和模型实例,仅需设置material.needsUpdate = true即可让纹理变更同步到AR场景。
  • Flutter中,AR插件会创建独立的模型实例和渲染上下文,必须手动将共享纹理绑定到AR模型的材质,并调用AR会话的刷新方法确保变更生效。

额外注意事项

  • 确保纹理尺寸为2的幂次(如256x256、512x512),避免AR渲染时出现纹理异常。
  • 复用纹理实例,避免频繁创建新的Texture对象导致内存泄漏。
  • 若使用其他AR库(如flutter_ar_core),核心逻辑一致:找到AR模型节点并更新其材质纹理。

内容的提问来源于stack exchange,提问作者Hari Prasath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 05:25:57