如何将HelixToolkit.SharpDX用于渲染3D模型?

摘要:HelixToolkit.SharpDX 是 HelixToolkit 生态中基于 DirectX(DX) 底层能力封装的 .NET 开源 3D 可视化库;DirectX 是微软为 Windows 平台开发的底层多媒体 API,可高效调用显
HelixToolkit.SharpDX 是 HelixToolkit 生态中基于 DirectX(DX) 底层能力封装的 .NET 开源 3D 可视化库;DirectX 是微软为 Windows 平台开发的底层多媒体 API,可高效调用显卡、声卡等硬件实现高性能图形渲染,而该库基于此能力,兼容 .NET Framework/.NET Core/.NET 5+ 全平台,专为 Windows 桌面应用提供低门槛、高性能的 3D 渲染,完美适配机械臂可视化、点云处理、设备仿真等工业开发场景; 一、NuGet 包管理器中下载相关包 NuGet 依赖:安装 HelixToolkit.Wpf 和HelixToolkit.SharpDX.Core.Wpf 二、引入HelixToolkit.SharpDX xmlns:hx="http://helix-toolkit.org/wpf/SharpDX" 三、实现代码 MainWindow.xaml <Window x:Class="HelixToolkit.SharpDX.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:hx="http://helix-toolkit.org/wpf/SharpDX" xmlns:local="clr-namespace:HelixToolkit.SharpDX" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:prism="http://prismlibrary.com/" xmlns:ui="http://schemas.inkore.net/lib/ui/wpf/modern" xmlns:vm="clr-namespace:HelixToolkit.SharpDX.ViewModels" Title="MainWindow" Width="800" Height="450" prism:ViewModelLocator.AutoWireViewModel="True" ui:TitleBar.Height="36" ui:WindowHelper.SystemBackdropType="Mica" ui:WindowHelper.UseModernWindowStyle="True" mc:Ignorable="d"> <Grid> <hx:Viewport3DX BackgroundColor="Black" Camera="{Binding Camera}" EffectsManager="{Binding EffectsManager}" IsRotationEnabled="True" IsShadowMappingEnabled="True" RotateAroundMouseDownPoint="True" ShowCoordinateSystem="True" ShowFrameRate="True" ShowViewCube="True" ZoomAroundMouseDownPoint="True" ZoomExtentsWhenLoaded="True"> <!-- 视口输入绑定:定义鼠标和键盘操作 --> <hx:Viewport3DX.InputBindings> <!-- Ctrl+E快捷键:缩放至整个模型 --> <KeyBinding Command="hx:ViewportCommands.ZoomExtents" Gesture="Control+E" /> <!-- 鼠标右键:旋转视图 --> <MouseBinding Command="hx:ViewportCommands.Rotate" Gesture="RightClick" /> <!-- 鼠标中键:缩放视图 --> <MouseBinding Command="hx:ViewportCommands.Zoom" Gesture="MiddleClick" /> <!-- 鼠标左键:平移视图 --> <MouseBinding Command="hx:ViewportCommands.Pan" Gesture="LeftClick" /> </hx:Viewport3DX.InputBindings> <!-- 阴影贴图:定义阴影的渲染参数 --> <hx:ShadowMap3D OrthoWidth="200" /> <!-- 环境光:基础照明 --> <hx:AmbientLight3D Color="White" /> <!-- 平行光:方向性光源,光线方向向量 --> <hx:DirectionalLight3D Direction="50, -200, -100" /> <!-- 批处理网格模型:高效渲染多个几何体 --> <hx:BatchedMeshGeometryModel3D x:Name="batchedMesh" BatchedGeometries="{Binding BatchedMeshes}" BatchedMaterials="{Binding BatchedMaterials}" CullMode="Back" IsThrowingShadow="True" Material="{Binding MainMaterial}" Mouse3DDown="BatchedMeshGeometryModel3D_Mouse3DDown" Transform="{Binding BatchedTransform}" /> <!-- 网格几何体模型:用于高亮显示选中的零件 --> <hx:MeshGeometryModel3D CullMode="Back" DepthBias="-100" Geometry="{Binding SelectedGeometry}" IsHitTestVisible="False" IsThrowingShadow="False" Material="{Binding SelectedMaterial}" Transform="{Binding SelectedTransform}" /> <!-- 轴平面网格:显示参考网格地面 --> <hx:AxisPlaneGridModel3D AutoSpacing="False" GridSpacing="2" RenderShadowMap="true" Offset="-20" /> </hx:Viewport3DX> <!-- 控制面板 --> <StackPanel Orientation="Vertical"> <!-- 线框图显示开关 --> <CheckBox IsChecked="{Binding ElementName=batchedMesh, Path=RenderWireframe}">线框图</CheckBox> </StackPanel> </Grid> </Window> MainWindow.xaml.cs /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); Closed += (s, e) => (DataContext as IDisposable)?.Dispose(); } private void BatchedMeshGeometryModel3D_Mouse3DDown(object? sender, HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs e) { if (DataContext is MainWindowViewModel viewModel) { viewModel.SelectedGeometry = e.HitTestResult?.Geometry; } } } MainWindowViewModel using HelixToolkit; using HelixToolkit.SharpDX; using HelixToolkit.SharpDX.Core; using HelixToolkit.SharpDX.Core.Model; using HelixToolkit.Wpf.SharpDX; using SharpDX; using System; using System.Collections.Generic; using System.Linq; using System.Numerics; using System.Threading; using System.Threading.Tasks; // WPF 的 3D 类型别名 using Media3D = System.Windows.Media.Media3D; using Point3D = System.Windows.Media.Media3D.Point3D; using Vector3D = System.Windows.Media.Media3D.Vector3D; namespace HelixToolkit.SharpDX.ViewModels { public class MainWindowViewModel : BindableBase { private IList<BatchedMeshGeometryConfig>? batchedMeshes; /// <summary> /// 批处理⽹格配置列表 /// 包含多个⽹格⼏何体及其变换信息 /// </summary> public IList<BatchedMeshGeometryConfig>? BatchedMeshes { get { return batchedMeshes; } set { SetProperty(ref batchedMeshes, value); } } private IList<Material>? batchedMaterials; /// <summary> /// 批处理材质列表 /// 对应每个⽹格使⽤的材质 /// </summary> public IList<Material>? BatchedMaterials { get { return batchedMaterials; } set { SetProperty(ref batchedMaterials, value); } } /// <summary> /// 批处理模型的整体变换 /// 所有模型统⼀缩放为原来的 0.1 倍 /// </summary> public Media3D.Transform3D BatchedTransform { get; } = new Media3D.ScaleTransform3D(0.1, 0.1, 0.1); private Media3D.MatrixTransform3D selectedTransform; /// <summary> /// 更新选中模型的变换矩阵 /// </summary> public Media3D.MatrixTransform3D SelectedTransform { get { return selectedTransform; } set { SetProperty(ref selectedTransform, value); } } private Geometry3D? selectedGeometry; /// <summary> /// 当前选中的⼏何体 /// </summary> public Geometry3D? SelectedGeometry { get { return selectedGeometry; } set { if (SetProperty(ref selectedGeometry, value)) { OnSelectedGeometryChanged(value); } } } /// <summary> /// 当选中的⼏何体改变时的部分⽅法 /// </summary> public void OnSelectedGeometryChanged(Geometry3D? value) { // 更新选中模型的变换矩阵 SelectedTransform = new Media3D.MatrixTransform3D( // 从批处理⽹格中查找选中⼏何体的变换 BatchedMeshes! .Where(x => x.Geometry == value) // 过滤出选中的⼏何体 .Select(x => x.ModelTransform) // 获取其变换矩阵 .First() // 取第⼀个 .ToMatrix3D() * BatchedTransform.Value); // 转换为 WPF 矩阵并应⽤整体缩放 } /// <summary> /// 主要模型的材质(⽩⾊) /// </summary> public Material MainMaterial { get; } = PhongMaterials.White; /// <summary> /// 选中模型的⾼亮材质(黄⾊发光) /// </summary> public Material SelectedMaterial { get; } = new PhongMaterial() { EmissiveColor = Color.Red }; /// <summary> /// 地板模型(⽤于展⽰阴影) /// </summary> public Geometry3D FloorModel { private set; get; } /// <summary> /// 地板材质(珍珠效果) /// </summary> public Material FloorMaterial { private set; get; } = PhongMaterials.Pearl; /// <summary> /// 同步上下⽂,⽤于跨线程更新 UI /// </summary> private readonly SynchronizationContext? context = SynchronizationContext.Current; private DefaultEffectsManager effectsManager; /// <summary> /// 初始化特效管理器 /// </summary> public DefaultEffectsManager EffectsManager { get { return effectsManager; } set { SetProperty(ref effectsManager, value); } } private PerspectiveCamera? camera; /// <summary> /// 初始化相机 /// </summary> public PerspectiveCamera? Camera { get { return camera; } set { SetProperty(ref camera, value); } } /// <summary> /// 构造函数 /// </summary> public MainWindowViewModel() { // 初始化特效管理器 EffectsManager = new DefaultEffectsManager(); // 设置相机:位置(0,0,200),看向原点,上⽅向为Y轴 Camera = new PerspectiveCamera() { Position = new Point3D(0, 0, 200), LookDirection = new Vector3D(0, 0, -200), UpDirection = new Vector3D(0, 1, 0), FarPlaneDistance = 1000 }; // 在后台线程加载模型(避免阻塞UI) Task.Run(LoadModels); } /// <summary> /// 后台加载模型的主要⽅法 /// </summary> private void LoadModels() { // 从⽂件加载 3DS 模型 // var models = Load3ds("3DModel/TaJia.STL"); // var models = Load3ds("3DModel/Car.3DS"); var models = LoadMultipleStlFiles(); // 为每个模型创建唯一的材质键(用文件名或索引) Dictionary<string, int> materialDict = new(); // 改用string作为key int count = 0; // 遍历所有模型,为每个模型分配唯一的材质索引 for (int i = 0; i < models.Count; i++) { var model = models[i]; if (model.Geometry is null) continue; // 用模型名称或索引作为唯一标识 string key = model.Name ?? $"Part_{i}"; if (!materialDict.ContainsKey(key)) { materialDict.Add(key, count++); } } // 准备批处理网格列表 var modelList = new List<BatchedMeshGeometryConfig>(models.Count); // 遍历所有模型,构建批处理配置 for (int i = 0; i < models.Count; i++) { var model = models[i]; if (model.Geometry is null) continue; model.Geometry.UpdateOctree(); string key = model.Name ?? $"Part_{i}"; int materialIndex = materialDict[key]; var transform = model.Transform?.FirstOrDefault() ?? Matrix.Identity; modelList.Add(new BatchedMeshGeometryConfig( model.Geometry, transform, materialIndex)); } // 创建材质数组(所有材质可以相同) Material[] materials = new Material[count]; for (int i = 0; i < count; i++) { materials[i] = PhongMaterials.White; } // 切换回UI线程 context?.Post((o) => { BatchedMeshes = modelList; BatchedMaterials = materials; }, null); } /// <summary> /// 关节到对应模型文件的映射 /// </summary> private static readonly Dictionary<string, string> JointModelFileMap = new() { {"base_link", "base_link.STL"}, {"base_move_joint", "base_move_Link.STL"}, {"arm_round_joint", "arm_round_Link.STL"}, {"arm_move_joint", "arm_move_Link.STL"}, {"forearm_round_joint", "forearm_round_Link.STL"}, {"forearm_move_joint", "forearm_move_Link.STL"}, {"terminal_round_joint", "terminal_round_Link.STL"}, {"terminal_end_joint_01", "terminal_end_Link_01.STL"}, {"terminal_end_joint_02", "terminal_end_Link_02.STL"}, {"terminal_end_joint_03", "terminal_end_Link_03.STL"} }; public List<Object3D> LoadMultipleStlFiles() { var allObjects = new List<Object3D>(); foreach (var kvp in JointModelFileMap) { // 为每个文件单独创建Reader var reader = new StLReader(); var objects = reader.Read($"3DModel/{kvp.Value}"); if (objects != null && objects.Count > 0) { // 如果返回多个,只取第一个,或者按文件名区分 foreach (var obj in objects) { // 给每个Object3D设置一个唯一标识 obj.Name = kvp.Key; // 用关节名称作为标识 allObjects.Add(obj); } } } return allObjects; } /// <summary> /// 根据⽂件扩展名加载不同格式的 3D 模型 /// </summary> /// <param name="path">模型⽂件路径</param> /// <returns>Object3D 对象列表</returns> public List<Object3D> Load3ds(string path) { // 处理 OBJ 格式 if (path.EndsWith(".obj", StringComparison.CurrentCultureIgnoreCase)) { var reader = new ObjReader(); var list = reader.Read(path); return list ?? new List<Object3D>(); } // 处理 3DS 格式 else if (path.EndsWith(".3ds", StringComparison.CurrentCultureIgnoreCase)) { var reader = new StudioReader(); var list = reader.Read(path); return list ?? new List<Object3D>(); } // 处理 STL 格式 else if (path.EndsWith(".stl", StringComparison.CurrentCultureIgnoreCase)) { var reader = new StLReader(); var list = reader.Read(path); return list ?? new List<Object3D>(); } else { // 不⽀持的格式返回空列表 return new List<Object3D>(); } } } } 四、效果图