使用 OpenTK 构建点云浏览程序

简介: C# 通过USB连接斑马打印机打印ZPL条码

使用 OpenTK 构建点云浏览程序

OpenTK 是一个强大的 .NET OpenGL 绑定库,非常适合用于开发点云浏览和可视化应用程序。

核心功能设计

一个基本的点云浏览器应包含以下功能:

  • 点云数据加载与解析
  • 3D 场景渲染
  • 相机控制(旋转、平移、缩放)
  • 点大小和颜色调整
  • 基本交互界面

实现步骤

1. 创建 OpenTK 窗口和基本结构

using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;

public class PointCloudViewer : GameWindow
{
   
    private int _vertexBufferObject;
    private int _vertexArrayObject;
    private int _shaderProgram;
    private Vector3[] _points;
    private Vector3[] _colors;
    private Matrix4 _view;
    private Matrix4 _projection;
    private float _pointSize = 2.0f;

    public PointCloudViewer() : base(GameWindowSettings.Default, 
        new NativeWindowSettings()
        {
   
            Size = new Vector2i(1200, 800),
            Title = "点云浏览器"
        })
    {
   
    }

    protected override void OnLoad()
    {
   
        base.OnLoad();
        GL.ClearColor(0.1f, 0.1f, 0.1f, 1.0f);
        LoadPointCloud("pointcloud.xyz"); // 加载点云数据
        SetupShaders();
        SetupCamera();
    }
}

2. 点云数据加载

private void LoadPointCloud(string filePath)
{
   
    try
    {
   
        var lines = File.ReadAllLines(filePath);
        _points = new Vector3[lines.Length];
        _colors = new Vector3[lines.Length];

        for (int i = 0; i < lines.Length; i++)
        {
   
            var values = lines[i].Split(' ');
            if (values.Length >= 3)
            {
   
                // 解析坐标
                _points[i] = new Vector3(
                    float.Parse(values[0]),
                    float.Parse(values[1]),
                    float.Parse(values[2]));

                // 如果有颜色信息则解析,否则使用默认颜色
                if (values.Length >= 6)
                {
   
                    _colors[i] = new Vector3(
                        float.Parse(values[3]),
                        float.Parse(values[4]),
                        float.Parse(values[5]));
                }
                else
                {
   
                    _colors[i] = new Vector3(0.8f, 0.8f, 0.8f); // 默认灰色
                }
            }
        }

        // 设置顶点缓冲
        _vertexBufferObject = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ArrayBuffer, _vertexBufferObject);

        // 创建包含位置和颜色的交错数组
        float[] vertexData = new float[_points.Length * 6];
        for (int i = 0; i < _points.Length; i++)
        {
   
            vertexData[i * 6] = _points[i].X;
            vertexData[i * 6 + 1] = _points[i].Y;
            vertexData[i * 6 + 2] = _points[i].Z;
            vertexData[i * 6 + 3] = _colors[i].X;
            vertexData[i * 6 + 4] = _colors[i].Y;
            vertexData[i * 6 + 5] = _colors[i].Z;
        }

        GL.BufferData(BufferTarget.ArrayBuffer, vertexData.Length * sizeof(float), 
                     vertexData, BufferUsageHint.StaticDraw);

        // 设置顶点数组对象
        _vertexArrayObject = GL.GenVertexArray();
        GL.BindVertexArray(_vertexArrayObject);

        // 位置属性
        GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 0);
        GL.EnableVertexAttribArray(0);

        // 颜色属性
        GL.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, 6 * sizeof(float), 3 * sizeof(float));
        GL.EnableVertexAttribArray(1);
    }
    catch (Exception ex)
    {
   
        Console.WriteLine($"加载点云失败: {ex.Message}");
    }
}

3. 着色器设置

顶点着色器 (shader.vert):

#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec3 aColor;

out vec3 fragColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;

void main()
{
    gl_Position = projection * view * model * vec4(aPosition, 1.0);
    fragColor = aColor;
    gl_PointSize = 2.0; // 点大小
}

片段着色器 (shader.frag):

#version 330 core
in vec3 fragColor;
out vec4 outputColor;

void main()
{
    outputColor = vec4(fragColor, 1.0);
}

着色器加载代码:

private void SetupShaders()
{
   
    // 编译顶点着色器
    var vertexShader = GL.CreateShader(ShaderType.VertexShader);
    GL.ShaderSource(vertexShader, File.ReadAllText("shader.vert"));
    GL.CompileShader(vertexShader);

    // 编译片段着色器
    var fragmentShader = GL.CreateShader(ShaderType.FragmentShader);
    GL.ShaderSource(fragmentShader, File.ReadAllText("shader.frag"));
    GL.CompileShader(fragmentShader);

    // 创建着色器程序
    _shaderProgram = GL.CreateProgram();
    GL.AttachShader(_shaderProgram, vertexShader);
    GL.AttachShader(_shaderProgram, fragmentShader);
    GL.LinkProgram(_shaderProgram);

    // 清理单个着色器对象
    GL.DeleteShader(vertexShader);
    GL.DeleteShader(fragmentShader);
}

4. 相机控制和渲染

private Vector3 _cameraPosition = new Vector3(0, 0, 5);
private Vector3 _cameraFront = -Vector3.UnitZ;
private Vector3 _cameraUp = Vector3.UnitY;
private float _cameraSpeed = 2.5f;
private float _yaw = -90f;
private float _pitch = 0f;

protected override void OnUpdateFrame(FrameEventArgs args)
{
   
    base.OnUpdateFrame(args);

    // 处理键盘输入
    var input = KeyboardState;
    if (input.IsKeyDown(Keys.Escape))
        Close();

    // 相机移动
    if (input.IsKeyDown(Keys.W))
        _cameraPosition += _cameraFront * _cameraSpeed * (float)args.Time;
    if (input.IsKeyDown(Keys.S))
        _cameraPosition -= _cameraFront * _cameraSpeed * (float)args.Time;
    if (input.IsKeyDown(Keys.A))
        _cameraPosition -= Vector3.Normalize(Vector3.Cross(_cameraFront, _cameraUp)) * 
                          _cameraSpeed * (float)args.Time;
    if (input.IsKeyDown(Keys.D))
        _cameraPosition += Vector3.Normalize(Vector3.Cross(_cameraFront, _cameraUp)) * 
                          _cameraSpeed * (float)args.Time;

    // 更新视图矩阵
    _view = Matrix4.LookAt(_cameraPosition, _cameraPosition + _cameraFront, _cameraUp);
}

protected override void OnRenderFrame(FrameEventArgs args)
{
   
    base.OnRenderFrame(args);
    GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

    GL.UseProgram(_shaderProgram);

    // 设置uniform
    var model = Matrix4.Identity;
    GL.UniformMatrix4(GL.GetUniformLocation(_shaderProgram, "model"), false, ref model);
    GL.UniformMatrix4(GL.GetUniformLocation(_shaderProgram, "view"), false, ref _view);
    GL.UniformMatrix4(GL.GetUniformLocation(_shaderProgram, "projection"), false, ref _projection);

    // 渲染点云
    GL.BindVertexArray(_vertexArrayObject);
    GL.DrawArrays(PrimitiveType.Points, 0, _points.Length);

    SwapBuffers();
}

protected override void OnResize(ResizeEventArgs e)
{
   
    base.OnResize(e);
    GL.Viewport(0, 0, Size.X, Size.Y);
    _projection = Matrix4.CreatePerspectiveFieldOfView(
        MathHelper.DegreesToRadians(45f), Size.X / (float)Size.Y, 0.1f, 1000f);
}

5. 鼠标控制实现

private bool _firstMove = true;
private Vector2 _lastPos;

protected override void OnMouseMove(MouseMoveEventArgs e)
{
   
    base.OnMouseMove(e);

    if (_firstMove)
    {
   
        _lastPos = new Vector2(e.X, e.Y);
        _firstMove = false;
    }
    else
    {
   
        var deltaX = e.X - _lastPos.X;
        var deltaY = e.Y - _lastPos.Y;
        _lastPos = new Vector2(e.X, e.Y);

        _yaw += deltaX * 0.1f;
        _pitch -= deltaY * 0.1f;

        // 限制俯仰角
        _pitch = MathHelper.Clamp(_pitch, -89.0f, 89.0f);

        // 计算新的相机方向
        _cameraFront.X = MathF.Cos(MathHelper.DegreesToRadians(_yaw)) * 
                         MathF.Cos(MathHelper.DegreesToRadians(_pitch));
        _cameraFront.Y = MathF.Sin(MathHelper.DegreesToRadians(_pitch));
        _cameraFront.Z = MathF.Sin(MathHelper.DegreesToRadians(_yaw)) * 
                         MathF.Cos(MathHelper.DegreesToRadians(_pitch));
        _cameraFront = Vector3.Normalize(_cameraFront);
    }
}

protected override void OnMouseWheel(MouseWheelEventArgs e)
{
   
    base.OnMouseWheel(e);
    _pointSize += e.OffsetY;
    _pointSize = MathHelper.Clamp(_pointSize, 1.0f, 10.0f);

    // 更新着色器中的点大小
    GL.UseProgram(_shaderProgram);
    GL.Uniform1(GL.GetUniformLocation(_shaderProgram, "pointSize"), _pointSize);
}

6. 高级功能扩展

// 点云采样(减少点数提高性能)
public void DownsamplePointCloud(int factor)
{
   
    if (factor <= 1) return;

    int newLength = _points.Length / factor;
    Vector3[] downsampledPoints = new Vector3[newLength];
    Vector3[] downsampledColors = new Vector3[newLength];

    for (int i = 0; i < newLength; i++)
    {
   
        downsampledPoints[i] = _points[i * factor];
        downsampledColors[i] = _colors[i * factor];
    }

    _points = downsampledPoints;
    _colors = downsampledColors;

    // 更新缓冲区
    UpdateBufferData();
}

// 按高度着色
public void ColorByHeight()
{
   
    // 找到最小和最大高度
    float minY = _points.Min(p => p.Y);
    float maxY = _points.Max(p => p.Y);
    float range = maxY - minY;

    for (int i = 0; i < _points.Length; i++)
    {
   
        float normalizedHeight = (_points[i].Y - minY) / range;
        _colors[i] = GetColorFromGradient(normalizedHeight);
    }

    UpdateBufferData();
}

private Vector3 GetColorFromGradient(float t)
{
   
    // 简单的蓝-绿-红渐变
    if (t < 0.5f)
    {
   
        return new Vector3(0, 2 * t, 1 - 2 * t);
    }
    else
    {
   
        return new Vector3(2 * (t - 0.5f), 2 * (1 - t), 0);
    }
}

参考代码 用OpenTK做的点云浏览程序 www.youwenfan.com/contentald/111845.html

优化建议

  1. 使用顶点缓冲对象(VBO)和顶点数组对象(VAO):如上所示,提高渲染性能
  2. 实现细节层次(LOD):根据距离动态调整点云密度
  3. 使用八叉树空间分区:加速点云查询和渲染
  4. 添加点云选择功能:实现点选或框选点云中的点
  5. 支持多种点云格式:如LAS、PLY、PCD等

运行程序

public static class Program
{
   
    public static void Main()
    {
   
        using (var viewer = new PointCloudViewer())
        {
   
            viewer.Run();
        }
    }
}
相关文章
|
安全 Java Maven
关于代码混淆,看这篇就够了
关于代码混淆,看这篇就够了
2204 4
|
29天前
|
XML 安全 测试技术
基于三层架构的C#网络配置管理系统(IP修改与XML持久化)
基于三层架构的C#网络配置管理系统(IP修改与XML持久化)
101 2
|
2月前
|
人工智能 数据可视化 Java
AI智能体的开发方法
本文系统梳理国内AI智能体开发全景:从“感知-决策-行动-记忆”认知闭环架构出发,对比Dify、Coze等低代码平台与LangGraph、AgentScope、Eino、Spring AI Alibaba等编程级框架;解析MCP协议、RAG技术栈等基础设施;并按MVP、企业级、极客定制三类场景给出选型建议。(239字)
|
7月前
|
XML 测试技术 API
利用C#开发ONVIF客户端和集成RTSP播放功能
利用C#开发ONVIF客户端和集成RTSP播放功能
4401 123
halcon算子模板匹配(一)基于形状的模板匹配
halcon算子模板匹配(一)基于形状的模板匹配
5377 0
|
机器学习/深度学习 人工智能 数据可视化
基于YOLOv8的X光安检图像智能检测系统:八类违禁品/可疑物精准识别与实战部署
本项目基于最新的YOLOv8深度学习模型,构建了一套轻量、高效、可视化的 X光安检图像智能检测系统,可精准识别电池、刀具、打火机等八类常见违禁品/可疑物,结合图形界面,支持一键识别与部署,大幅提升安检自动化水平。
Qt6学习笔记五(自定义对话框、QMessageBox、QColorDialog、QFileDialog、QFontDialog)
Qt6学习笔记五(自定义对话框、QMessageBox、QColorDialog、QFileDialog、QFontDialog)
837 0
|
Python
Matplotlib 教程 之 Matplotlib 网格线 3
本教程介绍如何使用 Matplotlib 的 `grid()` 方法自定义图表网格线。通过设置参数 `b`、`which`、`axis` 和 `**kwargs`,可以灵活控制网格线的显示与否及样式。示例展示了如何添加并设置网格线的颜色、样式和宽度,帮助你美化图表布局。
276 3
|
存储 缓存 监控
Linux 文件系统目录结构详解
本文介绍了Linux文件系统的目录结构,包括`/bin`、`/boot`、`/dev`、`/etc`、`/home`、`/lib`、`/media`、`/mnt`、`/opt`、`/proc`、`/root`、`/sbin`、`/tmp`、`/usr`和`/var`等目录的用途和重要性。每个目录都有其特定的功能,例如`/bin`存放基本用户命令,`/boot`存储启动相关文件,`/home`是用户主目录,`/lib`包含共享库,`/proc`提供进程信息,`/usr`存储用户程序资源,而`/var`则用于可变数据如日志文件。理解这些目录的用途有助于更好地管理和使用Linux系统。
Linux 文件系统目录结构详解
|
C# 开发者 Windows
WPF遇上Office:一场关于Word与Excel自动化操作的技术盛宴,从环境搭建到代码实战,看WPF如何玩转文档处理的那些事儿
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的重要组件,以其强大的图形界面和灵活的数据绑定功能著称。本文通过具体示例代码,介绍如何在 WPF 应用中实现 Word 和 Excel 文档的自动化操作,包括文档的读取、编辑和保存等。首先创建 WPF 项目并设计用户界面,然后在 `MainWindow.xaml.cs` 中编写逻辑代码,利用 `Microsoft.Office.Interop` 命名空间实现 Office 文档的自动化处理。文章还提供了注意事项,帮助开发者避免常见问题。
1211 0

热门文章

最新文章

下一篇
开通oss服务