【qml-10】Quick3D实现机器人渲染(mesh)记录

背景:

一个机器人示教器项目,最初希望做一个3D机器人模型动画展示,科研意义大于应用意义。从项目一开始心里真没底,只是知道qt的Quick示例中有3D机器人,要说看肯定是看不懂的,虽然能想到每个关节都类似独立的控件,都可以独立控制姿态,但也就是一说,真要实现光看示例是不够的。很多学习的动力,总得用得上才会去搞。

就如小时候问数学老师,哪怕最简单的面积和体积计算,当初古人是干啥就想起来研究这个了?老师说是因为生产过程中用到了,就想办法解决,久而久之就成了知识。实际咱们搞计算机是一样的道理。

下面记录干货,以便查阅。

首先qml是基础,不再赘述。qt的quick实际在很多qml开发场景中都用得着,qt官方的命名就是比较乱,个人认为quick可能更偏重于渲染相关。

3D基础:

任何在界面上的呈现,以前叫绘制、重绘,现在干脆叫渲染,我认为知道意思即可。

在界面上呈现3D总得有个控件,比如:View3D。

3D就像一个虚拟世界,这个世界就叫场景。比如:SceneEnvironment。

场景中得有光照,光照是分方向和距离的。比如:DirectionalLight。

再精彩的画面,也得用眼睛看。上述这些3D的场景和内容,总要通过那个控件显示到2D的显示器上,所以还要有个眼睛来指定看这些画面的方向和距离。比如:PerspectiveCamera。

在这个3D的世界中,还要划分模块,相当于子世界,用于设定相对特性。就像现实中,也得分不同的区域一样。这里就是各种Node以及其派生。我这次常用的就是Model。

到此先这样理解,去查手册会发现光照和相机也是node的派生,以后有兴趣再深究。

对于每个Model,手册里这样写的:

The Model item makes it possible to load a mesh and modify how its shaded, by adding materials to it. For a model to be renderable, it needs at least a mesh and a material.

简单说就是Model至少得有一个mesh和一个材质material。mesh就跟图片资源一样,它是一个立体的数据描述。材质是对着色的描述,如果要改变某物体的外观着色或者贴图,就要在材质里操作。

对材质的进一步定义就是纹理这一级了。

上面这些做基础就够了。大概知道3D是个啥情况即可。

可以幻想一下3D游戏里的场景,能对应上各种术语即可。

mesh:

想显示这个机器人,总得有真正的3D模型,比如SolidWorks和3Dmax制作的模型。那些东西生成的扩展名是stl之类的,不是mesh。

qt官方提供了转换工具,尤其qt6还提供了带界面的工具Balsam,这要是不看书不上网查,自己很难知道,尽管手册里有说明。因为一般不往那里想。除非逐字看手册,但这不是高效的学习方法,还是推荐看书。这个工具在咱们常用的path里,比如:D:\Qt6\6.9.1\mingw_64\bin\balsamui.exe,直接是个界面程序,不用研究命令行,可以批量转换。

balsamui转换后会生成mesh文件和一堆qml,实际我只用了mesh文件。最好有个mesh浏览器能一眼知道是什么零件,要不还要跟我一样一边做一边运行一下看效果。看qml不用总编译,使用qml预览工具即可。感兴趣可以自己做一个mesh预览工具。

预览mesh有时候可能一下看不到,往往都是因为光照、相机、缩放等不合适。比如你在桌子上放个苹果,然后手机总对着地板拍,这样咋也拍不到。或者苹果的显示单位和场景比例不协调,就如拿手机离老远拍芝麻,那没法看见。

所以最好把一些常用的参数做成可调整的,一旦运行起来,通过调整参数看实际需要的值是多少。

对于Quick系统,每个3D的初始相机拍摄位置都是从z轴往下俯视xy平面的,y轴向上,x轴向右。

下面先看一个demo。

demo:

我做好的预览界面如下:

可以看到搞了很多取值大的滑块来调整参数。省的看不到的时候没信心。

尤其调整角度的,我直接设置的正负360度,调就范围大些。

上图那个就不放动画了,可以升降,旋转,臂伸缩。

跟设计qml一样,尽量模块化处理,但也不要太琐碎。每个部件的坐标系都是相对于父级的。之前说过,每个部件都是个Model,也就是Node,想象成树结构即可。所以按模块封装很容易理解。

这个机器人我这样封装的:

最上面的手、小臂、大臂封装成Arm,通过参数指定左右手、伸缩角度。这部分大臂是跟节点,小臂是相对于大臂调整定位的,手也是。

再以最下面的基座为根,添加立柱,在立柱子节点添加臂底座,底座子节点再添加刚封装好的臂。

最后在场景qml中,也就是View3D的子节点中引用基座,一个完整的晶圆机器人就完成了。下面是qml代码:

Arm_Big.qml:

javascript 复制代码
import QtQuick
import QtQuick3D

Node {
    id: root

    property bool _bLeftside: true
    property double _dAngle_Arm: 0

    eulerRotation.z: _bLeftside ? -_dAngle_Arm :  _dAngle_Arm - 180

    PrincipledMaterial {
        id: _STL_BINARY_3
        objectName: "DefaultMaterial"
    }

    Model {
        id: _arm_big
        source: "meshes/arm_big.mesh"
        materials: [ _STL_BINARY_3 ]

        Model {
            id: _r1_small
            source: "meshes/arm_small.mesh"
            materials: [ _STL_BINARY_3 ]
            y: 0.163
            z: 0.04
            eulerRotation.z: root._bLeftside ? root._dAngle_Arm * 2 - 180 : 180 - root._dAngle_Arm * 2

            Model {
                id: _hand
                source: root._bLeftside ? "meshes/hand_left.mesh" : "meshes/hand_right.mesh"
                materials: [ _STL_BINARY_3 ]
                y: 0.163
                z: 0.03
                eulerRotation.z: root._bLeftside ? -root._dAngle_Arm : root._dAngle_Arm - 180
            }
        }
    }
}

Robot.qml:

javascript 复制代码
import QtQuick
import QtQuick3D

Node {
    id: root
    property double _dZ: 0
    property double _dAngle_Theta: 0
    property double _dAngle_R0: 0
    property double _dAngle_R1: 0

    PrincipledMaterial {
        id: _STL_BINARY_3
        objectName: "DefaultMaterial"
    }

    Model {
        id: _body_base
        source: "meshes/body_base.mesh"
        materials: [ _STL_BINARY_3 ]

        Model {
            id: _center_bottom
            source: "meshes/center_bottom.mesh"
            materials: [ _STL_BINARY_3 ]
            z: 0.6 + root._dZ / 100
            eulerRotation.z: root._dAngle_Theta

            Model {
                id: _center_top
                source: "meshes/center_top.mesh"
                materials: [ _STL_BINARY_3 ]
                z: 0.05

                Arm_Big {
                    id: _arm_left
                    y: 0.07
                    _bLeftside: true
                    _dAngle_Arm: root._dAngle_R0
                }

                Arm_Big {
                    id: _arm_right
                    y: -0.07
                    _bLeftside: false
                    _dAngle_Arm: root._dAngle_R1
                }
            }
        }
    }
}

main.qml:

javascript 复制代码
import QtQuick
import QtQuick3D
import QtQuick.Controls
import QtQuick.Layouts
import "./"

Item {
    implicitWidth: 800
    implicitHeight: 600

    RowLayout {
        anchors.fill: parent
        ColumnLayout {
            Layout.fillHeight: true

            //sence rotation
            RowLayout {
                Label { text: "sence rota x" }
                Slider {
                    id:sldr_sence_rota_x
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_sence_rota_x.value }
            }
            RowLayout {
                Label { text: "sence rota y" }
                Slider {
                    id:sldr_sence_rota_y
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_sence_rota_y.value }
            }
            RowLayout {
                Label { text: "sence rota z" }
                Slider {
                    id:sldr_sence_rota_z
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_sence_rota_z.value }
            }



            //camera
            RowLayout {
                Label { text: "camera x" }
                Slider {
                    id:sldr_cmr_x
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_cmr_x.value }
            }
            RowLayout {
                Label { text: "camera y" }
                Slider {
                    id:sldr_cmr_y
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_cmr_y.value }
            }
            RowLayout {
                Label { text: "camera z" }
                Slider {
                    id:sldr_cmr_z
                    from: -360
                    to: 360
                    value: 50
                }
                Label { text: sldr_cmr_z.value }
            }



            //body
            RowLayout {
                Label { text: "body x" }
                Slider {
                    id:sldr_body_x
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_body_x.value }
            }
            RowLayout {
                Label { text: "body y" }
                Slider {
                    id:sldr_body_y
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_body_y.value }
            }
            RowLayout {
                Label { text: "body z" }
                Slider {
                    id:sldr_body_z
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_body_z.value }
            }
            Slider {
                id:sldrY
                from: -360
                to: 360
                value: 0
            }


            //Arm
            ColumnLayout {
                Label { text: "arm_z" }
                Slider {
                    id:sldr_arm_z
                    from: 0
                    to: 20
                    value: 0
                }
                Label { text: sldr_arm_z.value }

                Label { text: "arm_theta angle" }
                Slider {
                    id:sldr_arm_angle_theta
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_arm_angle_theta.value }

                Label { text: "arm_left angle" }
                Slider {
                    id:sldr_arm_angle_left
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_arm_angle_left.value }

                Label { text: "arm_right angle" }
                Slider {
                    id:sldr_arm_angle_right
                    from: -360
                    to: 360
                    value: 0
                }
                Label { text: sldr_arm_angle_right.value }
            }

        }


        View3D {
            Layout.fillHeight: true
            Layout.fillWidth: true
            camera: camera

            PerspectiveCamera {
                id: camera
                x: sldr_cmr_x.value
                y: sldr_cmr_y.value
                z: sldr_cmr_z.value
            }

            Node {
                id: scene
                pivot.z: 0
                eulerRotation.x: sldr_sence_rota_x.value
                eulerRotation.y: sldr_sence_rota_y.value
                eulerRotation.z: sldr_sence_rota_z.value


                PointLight {
                    x: 760
                    z: 770
                    quadraticFade: 0
                    brightness: 1
                }

                DirectionalLight {
                    eulerRotation.z: 30
                    eulerRotation.y: -165
                }

                DirectionalLight {
                    y: 1000
                    brightness: 0.4
                    eulerRotation.z: -180
                    eulerRotation.y: 90
                    eulerRotation.x: -90
                }

                Robot {
                    x: sldr_body_x.value
                    y: sldr_body_y.value
                    z: sldr_body_z.value
                    scale: Qt.vector3d(40, 40, 40)
                    pivot.z: 0
                    eulerRotation.z: sldrY.value
                    _dZ: sldr_arm_z.value
                    _dAngle_Theta: sldr_arm_angle_theta.value
                    _dAngle_R0: sldr_arm_angle_left.value
                    _dAngle_R1: sldr_arm_angle_right.value
                }
            }

            environment: sceneEnvironment

            SceneEnvironment {
                id: sceneEnvironment
                antialiasingQuality: SceneEnvironment.VeryHigh
                antialiasingMode: SceneEnvironment.MSAA
            }
        }

    }
}

预览时使用带qt环境变量的命令行终端,运行qml main.qml即可。

可以看到,只要能接受qml参数,后面就不用说了。可以顺利嵌入qml项目。

题外话:

最初评估项目时,由于没接触过3D,有i两种思路:

一是看能否利用ros2系统自带的rviz,或许能够利用widget部件类,做成qml的c++扩展类,然后渲染到qml。是否可行不知道。

二是以quick示例为准,力争转换成mesh,也就是demo的方法。最终实现了。

或许有机会再想想第一种想法是否可行。

结束:

基础概念之前大概看过没实操,真正实践也就一个下午。

本文完。

相关推荐
科技圈快讯4 小时前
语音交互接待服务机器人深度推荐
机器人
不辞远_嵌入式4 小时前
分布式机器人多机协同巡检系统设计
分布式·机器人·无人机
JiaWen技术圈4 小时前
关于【机器人小脑】的快速入门介绍
单片机·嵌入式硬件·机器人·硬件架构
轩情吖9 小时前
Qt常用控件之QLabel(一)
开发语言·数据库·c++·qt·小程序·qlabel·桌面开发
Larry_Yanan12 小时前
QML学习笔记(四)QML新手入门其二:通过MouseArea让Rectangle实现鼠标三态
笔记·qt·ui
万俟淋曦13 小时前
【ROS2】通讯机制 Topic 常用命令行
人工智能·ai·机器人·ros·topic·ros2·具身智能
郝学胜-神的一滴14 小时前
QT与Spring Boot通信:实现HTTP请求的完整指南
开发语言·c++·spring boot·后端·qt·程序人生·http
起个名字费劲死了18 小时前
Pytorch Yolov11 OBB 旋转框检测+window部署+推理封装 留贴记录
c++·人工智能·pytorch·python·深度学习·yolo·机器人
k01k0119 小时前
ROS通信机制(一)
人工智能·机器人