基于 HelixToolkit.SharpDX 渲染ply点云

HelixToolkit.SharpDXHelixToolkit 生态中基于 DirectX(DX) 底层能力封装的 .NET 开源 3D 可视化库;DirectX 是微软为 Windows 平台开发的底层多媒体 API,可高效调用显卡、声卡等硬件实现高性能图形渲染,而该库基于此能力,兼容 .NET Framework/.NET Core/.NET 5+ 全平台,专为 Windows 桌面应用提供低门槛、高性能的 3D 渲染,完美适配机械臂可视化、点云处理、设备仿真等工业开发场景;

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

NuGet 依赖:安装 HelixToolkit.WpfHelixToolkit.SharpDX.Core.Wpf

image

二、引入HelixToolkit.SharpDX

xmlns:hx="http://helix-toolkit.org/wpf/SharpDX"

三、示例工程文件

MainWindow.xaml

<Window
    x:Class="HelixToolkit.DX.PointCloud.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.DX.PointCloud"
    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.DX.PointCloud.ViewModels"
    prism:ViewModelLocator.AutoWireViewModel="True"
    ui:WindowHelper.SystemBackdropType="Mica"
    ui:WindowHelper.UseModernWindowStyle="True"
    mc:Ignorable="d">
    <Grid>
        <hx:Viewport3DX
            BackgroundColor="Black"
            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:Viewport3DX.Camera>
                <hx:PerspectiveCamera
                    LookDirection="0,0,-10"
                    Position="0,0,10"
                    UpDirection="0,1,0" />
            </hx:Viewport3DX.Camera>

            <!--  阴影贴图:定义阴影的渲染参数  -->
            <hx:ShadowMap3D OrthoWidth="200" />
            <!--  环境光:基础照明  -->
            <hx:AmbientLight3D Color="White" />
            <!--  平行光:方向性光源,光线方向向量  -->
            <hx:DirectionalLight3D Direction="100, -100, -150" />

            <hx:PointGeometryModel3D
                Figure="Ellipse"
                Geometry="{Binding BatchedGeometry}"
                Size="1,1"
                Color="White" />

        </hx:Viewport3DX>
        <!--  简单的加载按钮  -->
        <Button
            Margin="20"
            Padding="10,5"
            HorizontalAlignment="Left"
            VerticalAlignment="Top"
            Command="{Binding LoadPlyCommand}"
            Content="Load PLY File" />
    </Grid>
</Window>

MainWindowViewModel

using HelixToolkit.SharpDX.Core;
using HelixToolkit.SharpDX.Core.Core;
using HelixToolkit.Wpf.SharpDX;
using Microsoft.Win32;
using SharpDX;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera;

namespace HelixToolkit.DX.PointCloud.ViewModels
{
    public class MainWindowViewModel : BindableBase, IDisposable
    {

        /// <summary>
        /// 消息对话框实例
        /// </summary>
        private readonly IDialogHelper _dialogHelper;

        public MainWindowViewModel(IContainerExtension container)
        {
            _dialogHelper = container.Resolve<IDialogHelper>();
        }

        private DefaultEffectsManager _effectsManager = new();
        /// <summary>
        /// 特效管理器
        /// 管理渲染管线、着色器等
        /// </summary>
        public DefaultEffectsManager EffectsManager
        {
            get => _effectsManager;
            set => SetProperty(ref _effectsManager, value);
        }

        private PointGeometry3D? _batchedGeometry;
        /// <summary>
        /// 点云几何体
        /// 定义3D场景中的点云数据
        /// </summary>
        public PointGeometry3D? BatchedGeometry
        {
            get => _batchedGeometry;
            set => SetProperty(ref _batchedGeometry, value);
        }

        /// <summary>
        /// 选择PLY文件并加载点云事件
        /// </summary>
        public ICommand LoadPlyCommand => new DelegateCommand(SelectPlyFile);

        /// <summary>
        /// 选择PLY文件并加载点云
        /// </summary>
        private void SelectPlyFile()
        {
            var openFileDialog = new OpenFileDialog
            {
                Filter = "PLY files (*.ply)|*.ply|All files (*.*)|*.*",
                Title = "Select a PLY file"
            };

            if (openFileDialog.ShowDialog() == true)
            {
                try
                {
                    LoadPlyFile(openFileDialog.FileName);
                }
                catch (Exception ex)
                {
                    _dialogHelper.ShowMessageAsync("报错", $"Error loading file:\n{ex.Message}");
                }
            }
        }

        /// <summary>
        /// 加载PLY文件并创建点云几何体
        /// </summary>
        /// <param name="filePath"></param>
        private void LoadPlyFile(string filePath)
        {
            // 解析PLY,获取位置和颜色(所有点都包含颜色)
            var (positions, colors) = ParsePly(filePath);

            if (positions.Count == 0)
            {
                _dialogHelper.ShowMessageAsync("报错", "No points found in the file.");
                return;
            }

            // 创建几何体,设置位置和颜色
            var geometry = new PointGeometry3D
            {
                Positions = new Vector3Collection(positions),
                Colors = new Color4Collection(colors)   // colors 一定存在且数量匹配
            };

            // 创建点云模型
            var pointModel = new PointGeometryModel3D
            {
                Geometry = geometry,
                Size = new Size(1, 1),          // 点大小设大一点便于观察
                Figure = PointFigure.Ellipse,
                Color = Colors.White,
            };

            // 添加到视口
            BatchedGeometry = geometry;
            _dialogHelper.ShowMessageAsync("成功", $"成功加载 {positions.Count} 个点。");
        }

        /// <summary>
        /// 解析ASCII PLY文件,提取顶点位置和颜色。
        /// 假设文件包含:x y z red green blue (red/green/blue为uchar 0-255)
        /// </summary>
        private (List<Vector3> positions, List<Color4> colors) ParsePly(string filePath)
        {
            var positions = new List<Vector3>();
            var colors = new List<Color4>();
            bool readingData = false;

            foreach (string line in File.ReadLines(filePath))
            {
                string trimmed = line.Trim();
                if (string.IsNullOrEmpty(trimmed))
                    continue;

                if (!readingData)
                {
                    // 查找头部结束标志
                    if (trimmed == "end_header")
                    {
                        readingData = true;
                    }
                    continue;
                }

                // 解析数据行,每行至少6个值:x y z r g b
                string[] parts = trimmed.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries);
                if (parts.Length < 6)
                    continue;   // 不符合要求,跳过(但你的数据应该都符合)

                try
                {
                    float x = float.Parse(parts[0], CultureInfo.InvariantCulture);
                    float y = float.Parse(parts[1], CultureInfo.InvariantCulture);
                    float z = float.Parse(parts[2], CultureInfo.InvariantCulture);
                    positions.Add(new Vector3(x, y, z));

                    // 解析RGB值(0-255),转换为0-1范围的float
                    byte r = byte.Parse(parts[3], CultureInfo.InvariantCulture);
                    byte g = byte.Parse(parts[4], CultureInfo.InvariantCulture);
                    byte b = byte.Parse(parts[5], CultureInfo.InvariantCulture);
                    colors.Add(new Color4(r / 255.0f, g / 255.0f, b / 255.0f, 1.0f));
                }
                catch (Exception ex)
                {
                    // 如果某行解析失败,可以选择跳过或报错。这里简单跳过并输出警告 
                    _dialogHelper.ShowMessageAsync("警告", $"Skipping line due to error:\n{ex.Message}");
                    continue;
                }
            }

            // 确保颜色数量与顶点数量一致(正常情况下应该一致)
            if (positions.Count != colors.Count)
                _dialogHelper.ShowMessageAsync("报错", $"Parsed {positions.Count} positions but {colors.Count} colors.");

            return (positions, colors);
        }


        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            GC.SuppressFinalize(this);
        }
    }
}

四、效果图

image

posted @ 2026-04-12 23:55  笺上知微  阅读(11)  评论(0)    收藏  举报