首页 > 基础资料 博客日记
基于HelixToolkit.SharpDX 渲染3D模型
2026-04-12 23:30:02基础资料围观1次
文章基于HelixToolkit.SharpDX 渲染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">
<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>();
}
}
}
}

文章来源:https://www.cnblogs.com/dosswy/p/19856444
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!
标签:

