本人之前開發了一個叫電子腦殼的上位機應用,給稚暉君ElectronBot開源機器人提供一些功能,但是由於是結合硬體才能使用的軟體,如果擁有硬體的人員太少,就會導致我的軟體沒什麼人用,於是我就想著能不能將機器人硬體的模型載入到軟體裡,這樣使用者就可以不使用硬體也可以使用我的軟體了。於是就有了在WinUI(WASDK)裡使用3D模型的需求。
先來個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再次封裝的)
這個框架是微軟最新的UI框架,我主要是用來開發程式的主體,做一些互動和功能的承載,本質上和wpf,uwp這類程式沒什麼太大的區別,區別就是一些工具鏈的不同。
GitHub Copilot: HelixToolkit是一個面向.NET開發人員的3D圖形庫。它提供了一組控制元件和輔助類,用於在WPF和WinForms應用程式中建立和渲染3D場景。HelixToolkit支援多種3D檔案格式,包括OBJ、STL和FBX,幷包括照明、材質、紋理和動畫等功能。它還支援高階渲染技術,如陰影、反射和透明度。HelixToolkit是開源的,可以從GitHub下載。
專案結構如下圖:
處理過程
=>載入器載入模型=>將模型載入到模型組=>初始化相機=>進行資料到控制元件的繫結
預備知識
SharpDX的座標系是左手座標系
GitHub Copilot: DirectX使用左手座標系來描述3D空間中的物件位置和方向。在左手座標系中,x軸向右,y軸向上,z軸向外(螢幕外)。這與右手座標系的z軸方向相反。左手座標系在計算機圖學中廣泛使用,因為它與人眼的觀察方式相符合。
模型載入完成以後,就需要讓模型動起來,這樣才能算是完整的過程了。
核心旋轉程式碼如下:
場景是將模型繞手臂點進行旋轉,需要將手臂模型組移動到原點,旋轉完成之後再移動到原來的位置。
下載電子腦殼原始碼
動作序列以及表情幀資料處理實際程式碼如下:
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;
}
最終效果如下:
通過模型載入的這個過程的學習,最大的感悟就是程式設計開發其實只是運用工具實現自己的想法,這個過程中我們對於工具的使用可能比較熟悉了,但是如果我們的領域知識不夠豐富,那基本上也是做不了什麼的,所以學習一些東西的時候我們的相關知識也要進行學習才好。
我也是在這個過程中才學習了什麼是左手座標系,以及4x4矩陣的變換和相機視角。這些東西都是和框架無關的知識,假如我們都學精通了,用什麼框架實現我們的想法其實都不是問題了。
總結,還是要努力學習呀。