当前位置: 首页 > news >正文

【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:

import QtQuick
import QtQuick3DNode {id: rootproperty bool _bLeftside: trueproperty double _dAngle_Arm: 0eulerRotation.z: _bLeftside ? -_dAngle_Arm :  _dAngle_Arm - 180PrincipledMaterial {id: _STL_BINARY_3objectName: "DefaultMaterial"}Model {id: _arm_bigsource: "meshes/arm_big.mesh"materials: [ _STL_BINARY_3 ]Model {id: _r1_smallsource: "meshes/arm_small.mesh"materials: [ _STL_BINARY_3 ]y: 0.163z: 0.04eulerRotation.z: root._bLeftside ? root._dAngle_Arm * 2 - 180 : 180 - root._dAngle_Arm * 2Model {id: _handsource: root._bLeftside ? "meshes/hand_left.mesh" : "meshes/hand_right.mesh"materials: [ _STL_BINARY_3 ]y: 0.163z: 0.03eulerRotation.z: root._bLeftside ? -root._dAngle_Arm : root._dAngle_Arm - 180}}}
}

Robot.qml:

import QtQuick
import QtQuick3DNode {id: rootproperty double _dZ: 0property double _dAngle_Theta: 0property double _dAngle_R0: 0property double _dAngle_R1: 0PrincipledMaterial {id: _STL_BINARY_3objectName: "DefaultMaterial"}Model {id: _body_basesource: "meshes/body_base.mesh"materials: [ _STL_BINARY_3 ]Model {id: _center_bottomsource: "meshes/center_bottom.mesh"materials: [ _STL_BINARY_3 ]z: 0.6 + root._dZ / 100eulerRotation.z: root._dAngle_ThetaModel {id: _center_topsource: "meshes/center_top.mesh"materials: [ _STL_BINARY_3 ]z: 0.05Arm_Big {id: _arm_lefty: 0.07_bLeftside: true_dAngle_Arm: root._dAngle_R0}Arm_Big {id: _arm_righty: -0.07_bLeftside: false_dAngle_Arm: root._dAngle_R1}}}}
}

main.qml:

import QtQuick
import QtQuick3D
import QtQuick.Controls
import QtQuick.Layouts
import "./"Item {implicitWidth: 800implicitHeight: 600RowLayout {anchors.fill: parentColumnLayout {Layout.fillHeight: true//sence rotationRowLayout {Label { text: "sence rota x" }Slider {id:sldr_sence_rota_xfrom: -360to: 360value: 0}Label { text: sldr_sence_rota_x.value }}RowLayout {Label { text: "sence rota y" }Slider {id:sldr_sence_rota_yfrom: -360to: 360value: 0}Label { text: sldr_sence_rota_y.value }}RowLayout {Label { text: "sence rota z" }Slider {id:sldr_sence_rota_zfrom: -360to: 360value: 0}Label { text: sldr_sence_rota_z.value }}//cameraRowLayout {Label { text: "camera x" }Slider {id:sldr_cmr_xfrom: -360to: 360value: 0}Label { text: sldr_cmr_x.value }}RowLayout {Label { text: "camera y" }Slider {id:sldr_cmr_yfrom: -360to: 360value: 0}Label { text: sldr_cmr_y.value }}RowLayout {Label { text: "camera z" }Slider {id:sldr_cmr_zfrom: -360to: 360value: 50}Label { text: sldr_cmr_z.value }}//bodyRowLayout {Label { text: "body x" }Slider {id:sldr_body_xfrom: -360to: 360value: 0}Label { text: sldr_body_x.value }}RowLayout {Label { text: "body y" }Slider {id:sldr_body_yfrom: -360to: 360value: 0}Label { text: sldr_body_y.value }}RowLayout {Label { text: "body z" }Slider {id:sldr_body_zfrom: -360to: 360value: 0}Label { text: sldr_body_z.value }}Slider {id:sldrYfrom: -360to: 360value: 0}//ArmColumnLayout {Label { text: "arm_z" }Slider {id:sldr_arm_zfrom: 0to: 20value: 0}Label { text: sldr_arm_z.value }Label { text: "arm_theta angle" }Slider {id:sldr_arm_angle_thetafrom: -360to: 360value: 0}Label { text: sldr_arm_angle_theta.value }Label { text: "arm_left angle" }Slider {id:sldr_arm_angle_leftfrom: -360to: 360value: 0}Label { text: sldr_arm_angle_left.value }Label { text: "arm_right angle" }Slider {id:sldr_arm_angle_rightfrom: -360to: 360value: 0}Label { text: sldr_arm_angle_right.value }}}View3D {Layout.fillHeight: trueLayout.fillWidth: truecamera: cameraPerspectiveCamera {id: camerax: sldr_cmr_x.valuey: sldr_cmr_y.valuez: sldr_cmr_z.value}Node {id: scenepivot.z: 0eulerRotation.x: sldr_sence_rota_x.valueeulerRotation.y: sldr_sence_rota_y.valueeulerRotation.z: sldr_sence_rota_z.valuePointLight {x: 760z: 770quadraticFade: 0brightness: 1}DirectionalLight {eulerRotation.z: 30eulerRotation.y: -165}DirectionalLight {y: 1000brightness: 0.4eulerRotation.z: -180eulerRotation.y: 90eulerRotation.x: -90}Robot {x: sldr_body_x.valuey: sldr_body_y.valuez: sldr_body_z.valuescale: Qt.vector3d(40, 40, 40)pivot.z: 0eulerRotation.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: sceneEnvironmentSceneEnvironment {id: sceneEnvironmentantialiasingQuality: SceneEnvironment.VeryHighantialiasingMode: SceneEnvironment.MSAA}}}
}

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

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

题外话:

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

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

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

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

结束:

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

本文完。

http://www.dtcms.com/a/405875.html

相关文章:

  • 解构IDP未来前景:去中心化金融的“阳谋”与以人为本的生态蓝图(解读)
  • 怎么做淘宝网站百度公司招聘官网最新招聘
  • 【国标36964解读】《软件工程软件开发成本度量规范》(GB/T36964-2018)解读
  • 在 Windows 11 上从零复现 3D Gaussian Splatting (3DGS)
  • 软件设计师软考备战:第五篇 软件工程与项目管理
  • 接口访问速度突然变慢,怎么排查
  • C++ IO 库全方位解析:从基础到实战
  • 从“手机拆修”看懂POD与非POD的区别
  • vc无法启动
  • SenseVoice微调
  • 【C++】: list介绍以及模拟实现
  • dlib 实战:人脸检测、关键点定位与疲劳检测的全流程实现
  • SpringBoot 整合机器学习框架 Weka 实战操作详解:MLOps 端到端流水线与知识图谱融合实践
  • 华为OD最新机试题A卷双机位-单词接龙-2025年
  • Python 爬虫(豆瓣top250)-享受爬取信息的快乐
  • Kafka选举机制深度解析:分布式系统中的民主与效率
  • 一文读懂费用分析:定义、分类与成本费用区别
  • 全国做网站找哪家好做宣传海报的网站
  • 【Linux】基础IO(3)
  • 【Redis学习】Redis中常见的全局命令、数据结构和内部编码
  • AI行业应用深度解析:从理论到实践
  • AI 伦理审查破局:从制度设计到实践落地的 2025 探索
  • RocketMQ面试问题与详细回答
  • 多传感器数据融合到base_link坐标系下
  • 阿里新开源Qwen3-Omni技术解析
  • Flink 流式分析事件时间、Watermark 与窗口
  • 解析前端框架 Axios 的设计理念与源码
  • 使用IOT-Tree消息流InfluxDB模块节点实现标签数据的时序数据库存储
  • 【深入理解JVM】垃圾回收相关概念与相关算法
  • 文档抽取技术:金融保险行业数字化转型的核心驱动力之一