1

WinUI(WASDK)使用HelixToolkit加载3D模型并进行项目实践 - 绿荫阿广

 1 year ago
source link: https://www.cnblogs.com/GreenShade/p/17462143.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

本人之前开发了一个叫电子脑壳的上位机应用,给稚晖君ElectronBot开源机器人提供一些功能,但是由于是结合硬件才能使用的软件,如果拥有硬件的人员太少,就会导致我的软件没什么人用,于是我就想着能不能将机器人硬件的模型加载到软件里,这样用户就可以不使用硬件也可以使用我的软件了。于是就有了在WinUI(WASDK)里使用3D模型的需求。

先来个B站复刻机器人的开箱视频吧。(如果感觉无聊可以直接拖到代码讲解部分)

B站视频演示链接

库选择的纠结过程

在选择库的过程中其实并不是一帆风顺,因为WinUI(WASDK)是个比较新的框架,框架本身也没有提供3D模型加载的功能,于是我就在想到底选择什么样的办法加载,之前有看到一些UWP加载模型的demo,但是基本上就是封装的c++的库,对于我这c++垃圾的人来说还是很痛苦的,我的要求就是不能有c++代码。

要满足模型的加载,模型的旋转变换平移,模型材质的更换,在经过筛选以后,备选方案有three.js,HelixToolkit。

  • three.js

    GitHub Copilot: three.js 是一个基于 JavaScript 的 3D 图形库,它使用 WebGL 技术在浏览器中渲染 3D 图形。它提供了一组易于使用的 API,使用户可以创建高度可定制的 3D 场景,包括模型、纹理、光源和相机等部分。此外,three.js 还支持动画、物理模拟和粒子系统等功能,使用户可以创建逼真的 3D 动画效果。three.js 可以在多种浏览器和设备上运行,并且有一个庞大的社区支持和贡献。

    由于要和前端框架进行互操作,所以这个我也不是优先考虑了。

  • HelixToolkit(基于SharpDX再次封装的)

所用框架和库介绍

1. WASDK

这个框架是微软最新的UI框架,我主要是用来开发程序的主体,做一些交互和功能的承载,本质上和wpf,uwp这类程序没什么太大的区别,区别就是一些工具链的不同。

2. HelixToolkit

GitHub Copilot: HelixToolkit是一个面向.NET开发人员的3D图形库。它提供了一组控件和辅助类,用于在WPF和WinForms应用程序中创建和渲染3D场景。HelixToolkit支持多种3D文件格式,包括OBJ、STL和FBX,并包括照明、材质、纹理和动画等功能。它还支持高级渲染技术,如阴影、反射和透明度。HelixToolkit是开源的,可以从GitHub下载。

1. Demo项目介绍

demo项目地址

项目结构如下图:

project

=>加载器加载模型=>将模型加载到模型组=>初始化相机=>进行数据到控件的绑定

2. 核心代码讲解

  • SharpDX的坐标系是左手坐标系

    GitHub Copilot: DirectX使用左手坐标系来描述3D空间中的对象位置和方向。在左手坐标系中,x轴向右,y轴向上,z轴向外(屏幕外)。这与右手坐标系的z轴方向相反。左手坐标系在计算机图形学中广泛使用,因为它与人眼的观察方式相符合。

model1
  • 3D场景下的模型变换(平移 旋转 缩放)

模型加载完成以后,就需要让模型动起来,这样才能算是完整的过程了。

核心旋转代码如下:

core-code

场景是将模型绕手臂点进行旋转,需要将手臂模型组移动到原点,旋转完成之后再移动到原来的位置。

3. 实际项目使用

下载电子脑壳源码

braincase

动作序列以及表情帧数据处理实际代码如下:

 private void Instance_ModelActionFrame(object? sender, Verdure.ElectronBot.Core.Models.ModelActionFrame e)
    {
        BodyModel.HxTransform3D = _bodyMt * Matrix.RotationY(MathUtil.DegreesToRadians((e.J6)));

        Material = new DiffuseMaterial()
        {
            EnableUnLit = false,
            DiffuseMap = LoadTextureByStream(e.FrameStream)
        };


        var nodeList = HeadModel.GroupNode;

        foreach (var itemMode in nodeList.Items)
        {
            if (itemMode.Name == "Head3.obj")
            {
                foreach (var node in itemMode.Traverse())
                {
                    if (node is MeshNode meshNode)
                    {
                        meshNode.Material = Material;
                    }
                }
            }
        }

        var rightList = RightShoulderBoundingBox.GetCorners();

        var rightAverage = new SharpDX.Vector3(
            (rightList[1].X + rightList[5].X) / 2f,
            ((rightList[1].Y + rightList[5].Y) / 2f) - 8f,
            (rightList[1].Z + rightList[5].Z) / 2f);

        var leftList = LeftShoulderBoundingBox.GetCorners();

        var leftAverage = new SharpDX.Vector3(
            (leftList[0].X + leftList[4].X) / 2f,
            ((leftList[0].Y + leftList[4].Y) / 2f) - 8f,
            (leftList[0].Z + leftList[4].Z) / 2f);

        var translationMatrix = Matrix.Translation(-rightAverage.X, -rightAverage.Y, -rightAverage.Z);

        var tr2 = _rightArmMt * translationMatrix;

        var tr3 = tr2 * Matrix.RotationZ(MathUtil.DegreesToRadians(-(e.J2)));
        var tr4 = tr3 * Matrix.RotationX(MathUtil.DegreesToRadians(-(e.J3)));

        var tr5 = tr4 * Matrix.Translation(rightAverage.X, rightAverage.Y, rightAverage.Z);


        var tr6 = tr5 * Matrix.RotationY(MathUtil.DegreesToRadians((e.J6)));

        RightArmModel.HxTransform3D = tr6;


        var leftMatrix = Matrix.Translation(-leftAverage.X, -leftAverage.Y, -leftAverage.Z);

        var leftTr2 = _leftArmMt * leftMatrix;

        var leftTr3 = leftTr2 * Matrix.RotationZ(MathUtil.DegreesToRadians((e.J4)));
        var leftTr4 = leftTr3 * Matrix.RotationX(MathUtil.DegreesToRadians(-(e.J5)));

        var leftTr5 = leftTr4 * Matrix.Translation(leftAverage.X, leftAverage.Y, leftAverage.Z);


        var leftTr6 = leftTr5 * Matrix.RotationY(MathUtil.DegreesToRadians((e.J6)));

        LeftArmModel.HxTransform3D = leftTr6;

        var headMatrix = Matrix.Translation(-HeadModelCentroidPoint.X, -HeadModelCentroidPoint.Y, -HeadModelCentroidPoint.Z);

        var headTr2 = _headMt * headMatrix;

        var headTr3 = headTr2 * Matrix.RotationX(MathUtil.DegreesToRadians(-(e.J1)));

        var headTr4 = headTr3 * Matrix.Translation(HeadModelCentroidPoint.X, HeadModelCentroidPoint.Y, HeadModelCentroidPoint.Z);


        var headTr5 = headTr4 * Matrix.RotationY(MathUtil.DegreesToRadians((e.J6)));

        HeadModel.HxTransform3D = headTr5;
    }

最终效果如下:

ElectronBot-model

通过模型加载的这个过程的学习,最大的感悟就是编程开发其实只是运用工具实现自己的想法,这个过程中我们对于工具的使用可能比较熟悉了,但是如果我们的领域知识不够丰富,那基本上也是做不了什么的,所以学习一些东西的时候我们的相关知识也要进行学习才好。

我也是在这个过程中才学习了什么是左手坐标系,以及4x4矩阵的变换和相机视角。这些东西都是和框架无关的知识,假如我们都学精通了,用什么框架实现我们的想法其实都不是问题了。

总结,还是要努力学习呀。

参考推荐文档项目如下

demo地址

电子脑壳

WASDK文档地址

ElectronBot

HelixToolkit


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK