如何在.Net Maui 中绘制最简单的 3D 对象

问题描述 投票:0回答:1

我想移植一个旧应用程序以在 .net maui 上运行以实现跨平台。

在 UI 中,我还需要一个 3d 视图,在其中我在 3d 空间中绘制一些线条和曲面。这里只有用户交互是旋转和平移视图以及缩放。数据本身是模拟结果,没有导入3D模型。

模型以非常低的更新率(30秒)更新。

我在 .net maui 中没有找到此类功能:是否有跨平台包?

如果不是,除了 maui 之外,是否还有其他跨平台 ui 方法适用于此范围?后端将是 C#

3d rendering maui
1个回答
0
投票

GraphicsView
上的 Microsoft Learn .NET MAUI 页面解释了如何将
GraphicsView
与自定义的
IDrawable
一起使用。通过
IDrawable::Draw(ICanvas canvas, RectF dirtyRect
,您可以直接用基本的线条和多边形填充
GraphicsView

更进一步,您可以获得

System.Numerics
NuGet 包,其中包含可用于操作 3D 坐标的矢量和矩阵运算。

对于简单的 3D 几何形状,其中面涂有纯色填充颜色,这里有一些类:

// Face.cs
namespace Toolkit.Maui.Shapes3D;

public class Face
{
    public Color Color { get; set; } = Colors.Red;
    public IList<int> Vertices { get; set; }
}
// Shape3D.cs
using System.Numerics;

namespace Toolkit.Maui.Shapes3D;

public enum RenderMode
{
    Solid,
    Wireframe
};

public class Shape3D
{
    public IList<Vector3> Points { get; set; }
    public IList<Face> Faces { get; set; }
    public Matrix4x4 Transform { get; set; } = Matrix4x4.Identity;
    public float[] HiddenFaceDashPattern { get; set; } = new float[] { 2, 10};

    public Point Project(Vector3 pt, Point Center)
    {
        Vector3 pt2 = Vector3.Transform(pt, Transform);
        return new Point(
            Math.Round(Center.X + pt2.X / (1 - pt2.Z / 800)),
            Math.Round(Center.Y + pt2.Y / (1 - pt2.Z / 800))
            );
    }

    public void Draw(ICanvas canvas, RectF dirtyRect, RenderMode Mode = RenderMode.Solid)
    {
        if (Points == null)
        {
            return;
        }

        if (Faces == null)
        {
            return;
        }

        List<Point> pts = new List<Point>(Points.Select(pt => Project(pt, dirtyRect.Center)));
        foreach (var f in Faces)
        {
            Point pt0 = pts[f.Vertices[0]];
            Point pt1 = pts[f.Vertices[1]];
            Point pt2 = pts[f.Vertices[2]];
            Point v1 = new Point(pt1.X - pt0.X, pt1.Y - pt0.Y);
            Point v2 = new Point(pt2.X - pt1.X, pt2.Y - pt1.Y);
            double vz = v1.X * v2.Y - v1.Y * v2.X;
            bool HiddenFace = (vz <= 0);

            if (Mode == RenderMode.Solid)
            {
                if (f.Color == Colors.Transparent) continue;
                if (HiddenFace) continue;
            }

            PathF path = new PathF();
            path.MoveTo((float)pts[f.Vertices[0]].X, (float)pts[f.Vertices[0]].Y);
            for (int i = 1; i < f.Vertices.Count; i++)
            {
                path.LineTo((float)pts[f.Vertices[i]].X, (float)pts[f.Vertices[i]].Y);
            }
            path.Close();

            if (Mode == RenderMode.Solid)
            {
                canvas.FillColor = f.Color;
                canvas.FillPath(path);
            }

            if (Mode == RenderMode.Wireframe)
            {
                canvas.StrokeColor = f.Color;
                canvas.StrokeSize = 1;
                canvas.StrokeDashPattern = HiddenFace ? HiddenFaceDashPattern : null;
                canvas.DrawPath(path);
            }
        }
    }
}

这是填充这些类的示例 ViewModel:

// MainViewModel.cs
using System.ComponentModel;
using System.Numerics;
using System.Windows.Input;

namespace Toolkit.Maui.Shapes3D.Sample;

public class MainViewModel : INotifyPropertyChanged
{
    public bool Wireframe { get; set; } = true;
    public RenderMode Mode => Wireframe ? RenderMode.Wireframe : RenderMode.Solid;

    public Shape3D Cube = new Shape3D()
    {
        Points = new List<Vector3>()
        {
            new Vector3() { X = -100, Y = -100, Z = -100 },
            new Vector3() { X = 100, Y = -100, Z = -100 },
            new Vector3() { X = -100, Y = 100, Z = -100 },
            new Vector3() { X = 100, Y = 100, Z = -100 },
            new Vector3() { X = -100, Y = -100, Z = 100 },
            new Vector3() { X = 100, Y = -100, Z = 100 },
            new Vector3() { X = -100, Y = 100, Z = 100 },
            new Vector3() { X = 100, Y = 100, Z = 100 }
        },

        Faces = new List<Face>()
        {
            new Face() { Vertices = new List<int> {1,0,2,3}, Color = Colors.Red },
            new Face() { Vertices = new List<int> {4,5,7,6}, Color = Colors.Orange },
            new Face() { Vertices = new List<int> {2,0,4,6}, Color = Colors.Yellow },
            new Face() { Vertices = new List<int> {1,3,7,5}, Color = Colors.White },
            new Face() { Vertices = new List<int> {0,1,5,4}, Color = Colors.Blue },
            new Face() { Vertices = new List<int> {3,2,6,7}, Color = Colors.Green }
        }
    };

    private IDispatcherTimer _timer;

    public EventHandler Frame;

    public Matrix4x4 Delta { get; set; } =
        Matrix4x4.Multiply(
            Matrix4x4.CreateRotationX((float)(0.3 * Math.PI / 180)),
            Matrix4x4.CreateRotationY((float)(0.5 * Math.PI / 180))
            );

    public MainViewModel()
    {
        ToggleRenderModeCommand = new Command(() => ToggleRenderMode());
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ToggleRenderModeCommand)));
        _timer = App.Current.Dispatcher.CreateTimer();
        _timer.Interval = TimeSpan.FromMilliseconds(10);
        _timer.Tick += (s, e) =>
        {
            Cube.Transform = Matrix4x4.Multiply(Cube.Transform, Delta);
            Frame?.Invoke(this, EventArgs.Empty);
        };
        _timer.Start();
    }

    public ICommand ToggleRenderModeCommand { get; set; }

    public void ToggleRenderMode()
    {
        Wireframe = !Wireframe;
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Wireframe)));
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Mode)));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

MainViewModel
被声明为单例,因此
MainPage
应用程序可以使用它,并且您的
MainDrawable
可以从中渲染。

// MainDrawable.cs
using Microsoft.Maui.Graphics;

namespace Toolkit.Maui.Shapes3D.Sample;

public class MainDrawable : IDrawable
{
    private MainViewModel _vm;
    private MainViewModel VM
        => _vm ??= ServiceHelper.GetService<MainViewModel>();


    public void Draw(ICanvas canvas, RectF dirtyRect)
    {
        VM.Cube.Draw(canvas, dirtyRect, VM.Mode);
    }
}
<!-- MainPage.xaml -->
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:Toolkit.Maui.Shapes3D.Sample"
             x:Class="Toolkit.Maui.Shapes3D.Sample.MainPage">
    
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:MainDrawable x:Key="drawable" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid RowDefinitions="*,Auto">
        <GraphicsView
            x:Name="graphicsView"
            Drawable="{StaticResource drawable}"
            BackgroundColor="Transparent"/>
        <Button
            Grid.Row="1"
            Text="Toggle Render Mode"
            Command="{Binding ToggleRenderModeCommand}" 
            HorizontalOptions="Center" />
    </Grid>
</ContentPage>

可以在此处找到上述内容的完整工作示例:https://github.com/stephenquan/Toolkit.Maui.Shapes3D

© www.soinside.com 2019 - 2024. All rights reserved.