前言
Win10系统有一款非常优秀的3维模型查看工具3D viewer,功能十分强大。出于学习openGL的目的,采用Qt结合openGL实现3D查看器的部分功能,工程中涉及到的opengl相关代码基本上都是参考opengl中文网站(LearnOpenGL-CN)及网上的例子来写的。本文主要阐述工程中遇到的问题及解决方案,内容如下:1、效果及功能介绍;2、遇到的问题及解决方案;3、工程源码及说明。
一、效果及功能介绍
自制的3d查看器主要包括模型加载、修改背景颜色及动画等功能。动画效果如下图,工程中所有的3d模型均从Windows自带的3D查看器中的3D资源库中下载。模型格式都是glb格式,纹理数据直接包含在模型中,其他格式下的模型纹理数据可能用单独的文件存放,工程应该也能支持加载(实际没测试过)。代码主要三方库assimp(Releases · assimp/assimp (github.com))进行3d模型加载,通过三方库stb(项目概览 - stb - GitCode)进行图像加载。首先调用assimp库的GetEmbeddedTexture函数,若返回的aiTexture对象非空,表明模型包含纹理数据,调用stb库的stbi_load_from_memory函数从内存中加载纹理,否则通过stbi_load函数从指定路径中加载纹理。
arduino
const aiTexture* GetEmbeddedTexture(const char* filename) const
二、遇到的问题及解决方案
1、模型比较大时,图像显示不全以及有黑色锯齿
openGL的坐标系包括三个部分:模型矩阵、观察矩阵和投影矩阵,learnopengl网站也对此进行了详细权威的说明。模型的旋转、缩放及移动,一般都只涉及到前面两个矩阵即可。投影矩阵一般只在resizeGL函数中调用:
java
void perspective(float verticalAngle, float aspectRatio, float nearPlane, float farPlane)
第一个参数表示视野(Field of View)的意思,表明了观察空间的大小,对于一个真实的观察效果,其值经常设置为45°;第二个参数表示窗口宽高比,直接传入widget的宽高即可;第三和第四个参数设置了平截头体的近和远平面,教程上建议我们分别设置为0.1与100.0,实际上这两个参数最好不要写死,这两个参数控制了控制了视野的最小、大距离。如果加载的模型的尺寸很大,参数farPlane设置比较小,就会出现模型显示不全的问题;同理,参数nearPlane设置比较小,就会出现模型有黑色锯齿的问题,当然参数nearPlane设置太大时,会导致相机太靠近一个物体时会直接穿过去。因此nearPlane, farPlane要根据模型的尺寸来确定,调整后,工程能支持1~3000尺寸范围内的模型正确导入,包括滚轮的滚动系数的确定,应该也与模型尺寸有关系。确定最小、大视野距离的逻辑如下,感觉这里应该存在相应的一个数学公式。
ini
float maxPosition = 计算模型最大尺寸();
if (maxPosition < 5) {
m_camera.m_zNear = 0.01;
m_camera.m_zFar = maxPosition * 100;
}
else if (maxPosition < 100) {
m_camera.m_zNear = 0.05;
m_camera.m_zFar = maxPosition * 50;
}
else if (maxPosition < 1000) {
m_camera.m_zNear = 0.05;
m_camera.m_zFar = maxPosition * 30;
}
else if (maxPosition < 2000) {
m_camera.m_zNear = 0.07;
m_camera.m_zFar = maxPosition * 10;
}
else if (maxPosition < 3000) {
m_camera.m_zNear = 0.4;
m_camera.m_zFar = maxPosition * 10;
}
else {
m_camera.m_zNear = 0.5;
m_camera.m_zFar = maxPosition * 10;
}
//resizeGL函数
void OpenGLWindow::resizeGL(int w, int h)
{
qreal aspect = qreal(w) / qreal(h ? h : 1);
m_camera.m_projection.setToIdentity();
m_camera.m_projection.perspective(m_camera.m_fovy, aspect, m_camera.m_zNear, m_camera.m_zFar);
}
2 纹理坐标不对导致纹理贴图错乱
三方库stb中,有一个stbi_set_flip_vertically_on_load函数,用于翻转y轴坐标。在片段找色器语言代码中一般会对纹理坐标进行赋值操作,这两部分是相关联的,对应不上就会导致纹理错乱。
csharp
//stb
STBIDEF void stbi_set_flip_vertically_on_load(int flag_true_if_should_flip)
//片段找色器代码
#version 330 core
out vec4 FragColor;
in vec2 TexCoords;
uniform sampler2D texture_diffuse1;
void main()
{
FragColor = texture(texture_diffuse1, TexCoords);
}
3 帧率fps获取方式
不要在paintGL函数中,通过QPainter.drawText的方式直接将文本绘制在窗口上,这样会影响到opengl模型对象的显示,将文本绘制在QLabel(opengl窗口作为其父对象)上再show出来。帧率如果直接在paintGL函数中计算,可能会非常不准确,这里通过定时器的方式来获取帧率(间隔1s),但会损耗部分性能。
4 其他
1、代码中有一个宏START_INFO_CONSOLE,控制是否开启debug控制台,用于观察是否有报错日志,打开后会产生两个窗口,日志通过三方库spdlogl来实现。 2、工程中有一个Tree with Falling Leaves.glb的模型加载出来很奇怪,目前还不清楚。
三、工程源码及说明
工程源码路径:1995zyl/3D-viewer: This is a project based on qt opengl for viewing 3D models (github.com) 由于三方库只编译了Windows的release版,工程只能运行在Windows x64 RelWithDebInfo/Release下。本地只需要安装cmake工具与visual studio,不需要Qt环境,qt相关依赖以三方库的形式上传到该工程的third_libray目录了。cmake版本为3.23左右,vs版本为vs2022左右,其他见工程readme文件。