基于HelixToolkit.SharpDX 渲染3D模型

一、NuGet 包管理器中下载相关包

NuGet 依赖:安装 HelixToolkit.WpfHelixToolkit.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">
    <Window.DataContext>
        <vm:MainWindowViewModel x:Name="viewModel" />
    </Window.DataContext>
    <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)
     {
         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>();
            }
        }
    }
}

四、效果图

相关推荐
晓纪同学1 天前
WPF-03 第一个WPF程序
大数据·hadoop·wpf
光电大美美-见合八方中国芯1 天前
用于无色波分复用光网络的 10.7 Gb/s 反射式电吸收调制器与半导体光放大器单片集成
网络·后端·ai·云计算·wpf·信息与通信·模块测试
晓纪同学1 天前
WPF-02体系结构
wpf
晓纪同学1 天前
WPF-01概述
wpf
海盗12342 天前
OxyPlot 在 WPF 中的使用
.net·wpf
晓纪同学2 天前
WPF-04 XAML概述
wpf
△曉風殘月〆2 天前
如何在WPF中捕获窗口外的事件
wpf
爱吃烤鸡翅的酸菜鱼4 天前
Java 事件发布-订阅机制全解析:从原生实现到主流中间件
java·中间件·wpf·事件·发布订阅
武藤一雄4 天前
WPF中ViewModel之间的5种通讯方式
开发语言·前端·microsoft·c#·wpf