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

如何在.NET MAUI应用中离线集成交互式3D模型(无需Evergine/WebView)

适用于.NET MAUI的离线3D模型集成方案

针对你的需求,推荐两个无需新增独立项目、支持离线运行的方案,均能实现旋转、缩放、车门交互等功能,适配你现有的.glb/.gltf模型格式。


方案1:SharpGLTF + MAUI GraphicsView(轻量易用)

SharpGLTF是一个.NET生态的3D模型加载库,可直接嵌入MAUI项目,无需额外依赖,适合中等复杂度的3D展示需求。

步骤1:安装依赖

通过NuGet安装以下包:

Install-Package SharpGLTF.Core
Install-Package SharpGLTF.Toolkit

步骤2:配置模型资源

将你的.glb/.gltf模型文件添加到MAUI项目的Resources/Raw目录,设置生成操作为MauiAsset,确保文件能被离线访问。

步骤3:自定义3D交互视图

创建继承自GraphicsView的自定义视图,实现模型加载、渲染和交互:

using Microsoft.Maui.Graphics;
using SharpGLTF.Scenes;
using SharpGLTF.Materials;
using System.Numerics;

namespace CarMonitorApp;

public class Car3DView : GraphicsView
{
    private SceneModel? _carScene;
    private float _rotX = 0f;
    private float _rotY = 0f;
    private float _scale = 1.0f;
    private PointF _lastTouchPos;

    public Car3DView()
    {
        LoadCarModel();
        AddGestureRecognizers();
    }

    private void LoadCarModel()
    {
        // 从项目资源加载模型
        var modelPath = "Resources/Raw/car_model.glb";
        using var stream = FileSystem.OpenAppPackageFileAsync(modelPath).Result;
        _carScene = SceneModel.Load(stream);
    }

    private void AddGestureRecognizers()
    {
        // 单指旋转
        var panGesture = new PanGestureRecognizer();
        panGesture.PanUpdated += (s, e) =>
        {
            if (e.StatusType == GestureStatus.Running)
            {
                _rotY += (e.TotalX - _lastTouchPos.X) * 0.5f;
                _rotX += (e.TotalY - _lastTouchPos.Y) * 0.5f;
                _lastTouchPos = new PointF(e.TotalX, e.TotalY);
                Invalidate();
            }
            else if (e.StatusType == GestureStatus.Started)
            {
                _lastTouchPos = new PointF(e.TotalX, e.TotalY);
            }
        };

        // 双指缩放
        var pinchGesture = new PinchGestureRecognizer();
        pinchGesture.PinchUpdated += (s, e) =>
        {
            if (e.StatusType == GestureStatus.Running)
            {
                _scale *= e.Scale;
                _scale = Math.Clamp(_scale, 0.5f, 3.0f);
                Invalidate();
            }
        };

        GestureRecognizers.Add(panGesture);
        GestureRecognizers.Add(pinchGesture);
    }

    protected override void Draw(ICanvas canvas, RectF dirtyRect)
    {
        base.Draw(canvas, dirtyRect);
        if (_carScene == null) return;

        // 设置渲染中心
        canvas.SaveState();
        canvas.Translate(dirtyRect.Center.X, dirtyRect.Center.Y);
        canvas.Scale(_scale);
        canvas.RotateX(_rotX);
        canvas.RotateY(_rotY);

        // 遍历模型节点渲染(简化版,可扩展支持纹理、光照)
        foreach (var node in _carScene.RootNode.VisualChildren)
        {
            if (node.Mesh == null) continue;
            foreach (var primitive in node.Mesh.Primitives)
            {
                var material = primitive.Material;
                canvas.FillColor = Color.FromRgb(
                    material.BaseColorFactor.R,
                    material.BaseColorFactor.G,
                    material.BaseColorFactor.B
                );

                // 获取顶点数据并绘制三角形
                var positions = primitive.VertexAccessors["POSITION"].AsVector3Array();
                for (int i = 0; i < positions.Count; i += 3)
                {
                    canvas.DrawPolygon(
                        new PointF(positions[i].X, positions[i].Y),
                        new PointF(positions[i+1].X, positions[i+1].Y),
                        new PointF(positions[i+2].X, positions[i+2].Y)
                    );
                }
            }
        }

        canvas.RestoreState();
    }

    // 车门切换方法(需匹配模型中车门节点的名称)
    public void ToggleDoor(string doorNodeName)
    {
        if (_carScene == null) return;
        var doorNode = _carScene.RootNode.FindNode(doorNodeName);
        if (doorNode == null) return;

        // 切换车门旋转角度(0° ↔ 90°)
        var currentRot = doorNode.LocalTransform.Rotation;
        var newYaw = currentRot.Y < MathHelper.DegreesToRadians(90) 
            ? MathHelper.DegreesToRadians(90) 
            : 0f;
        doorNode.LocalTransform = doorNode.LocalTransform.WithRotation(
            Quaternion.FromYawPitchRoll(newYaw, 0f, 0f)
        );
        Invalidate();
    }
}

步骤4:在XAML中使用视图

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:CarMonitorApp"
             x:Class="CarMonitorApp.MainPage">

    <Grid>
        <local:Car3DView x:Name="Car3DDisplay" />
        <Button Text="切换左前门" 
                Clicked="OnToggleDoorClicked"
                HorizontalOptions="Center"
                VerticalOptions="End"
                Margin="20"/>
    </Grid>
</ContentPage>

后台代码绑定按钮事件

namespace CarMonitorApp;

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();
    }

    private void OnToggleDoorClicked(object sender, EventArgs e)
    {
        // 替换为你模型中左前门节点的实际名称
        Car3DDisplay.ToggleDoor("FrontLeftDoor");
    }
}

方案2:OpenTK.Maui(复杂3D渲染)

如果需要更专业的3D效果(如光照、纹理、阴影),可以使用OpenTK的MAUI绑定,它提供完整的OpenGL渲染能力,同样无需额外项目。

核心步骤

  1. 安装NuGet包:OpenTK.Maui
  2. 创建继承自OpenGLView的自定义视图,在OnLoad中加载模型、编译着色器,在OnRenderFrame中完成渲染
  3. 手势识别和车门交互逻辑与方案1类似,通过修改模型节点变换实现

注意事项

  • 模型节点名称需与Blender等建模工具中定义的名称一致,可通过SharpGLTF的调试工具查看节点层级
  • 可根据需求优化渲染逻辑,比如添加纹理映射、光照计算
  • 确保模型文件的生成操作设置正确,避免离线加载失败

内容的提问来源于stack exchange,提问作者Ishan Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 21:27:08