Qt中使用OpenGL(七)

优化

开启多重采样

为了解决模型边缘出现锯齿的问题,可以开启多重采样。在 OpenGLWidget 类初始化的时候,通过格式设置多重采样

1
2
3
auto newFormat = this->format();
newFormat.setSamples(16);
this->setFormat(newFormat);

背面剪裁

OpenGL 在绘制的时候,是会同时绘制正面和背面,而背面用户是看不到的,所以为了节省资源,可以开启背面裁剪,不对背面进行绘制。

如何判断正面或者背面?

​ OpenGL 中,一个面的顶点是逆时针排列的那一面就是正面,顺时针排列的那一面就是背面。

1
glEnable(GL_CULL_FACE);

在 Qt 窗口中绘制 UI

窗口中的 UI 一般是 2D 的,所以可以使用 QPainter 函数在 paintGL 函数中进行绘制。在绘制 2D 图形之前,必须关闭深度测试及背面裁剪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void TreeOpenGLWidget::paintGL()
{
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
m_projection.setToIdentity();
m_projection.perspective(m_camera.Zoom, (float)width() / height(), 0.1, 100);
for(auto dice : m_models) {
dice->setProjection(m_projection);
dice->paint();
}

glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
QPainter _painter(this);
auto _rect = this->rect();
_painter.setPen(Qt::green);
_painter.drawLine(_rect.center() + QPoint{ 0, 5 }, _rect.center() + QPoint{ 0, 15 });
_painter.drawLine(_rect.center() + QPoint{ 0, -5 }, _rect.center() + QPoint{ 0, -15 });
_painter.drawLine(_rect.center() + QPoint{ 5, 0 }, _rect.center() + QPoint{ 15, 0 });
_painter.drawLine(_rect.center() + QPoint{ -5, 0 }, _rect.center() + QPoint{ -15, 0 });
_painter.drawText(QPoint{5, 20}, QString(u8"摄像机位置:(%1,%2,%3)").arg(m_camera.Position.x(), 0, 'f', 3).arg(m_camera.Position.y(), 0, 'f', 3).arg(m_camera.Position.z(), 0, 'f', 3));
_painter.drawText(QPoint{5, 45}, QString(u8"摄像机角度:(%1,%2)").arg(m_camera.Yaw, 0, 'f', 3).arg(m_camera.Pitch, 0, 'f', 3));
}

线框模式

线框模式让你可以清楚的看到你的模型面的组成方法

1
2
3
4
// 开启线框
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// 关闭线框
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

画一个球

画球的思路类似于地球仪,经线和纬线将地球仪分割成一个个矩形(在南北极失效)。所以只要按照经纬线绘制矩形,把经纬线的交点对应到三维空间中的点即可。

使用一个指向北极的向量(0,1,0)为基准,首先让它沿着x轴旋转,再沿着y轴旋转,x轴旋转10°,默认我们的向量指向北极,转10°就意味着靠近赤道了10°,此时,向量指向的地方就是就是北纬80°,东经0°。

开启两个循环,让这个向量在地球上遍历一遍所有的经纬度,它就自然而然的和地球仪上的经纬度交点对上了,也就知道每个交点的三维坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
QVector3D _top{ 0,1,0 };
float _step = 10;
QVector<QVector<QVector3D>> _vertexMatrix;

for (float _yaw = 0; _yaw <= 180; _yaw += _step)
{
_vertexMatrix << QVector<QVector3D>();
m_col = 0;
for (float _pitch = 0; _pitch < 360; _pitch += _step)
{
QMatrix4x4 _mat;
_mat.setToIdentity();
_mat.rotate(_yaw, 1, 0, 0);
_mat.rotate(-_pitch, 0, 1, 0);
auto _p = _top * _mat;
_vertexMatrix[m_row] << _p;
++m_col;
}
++m_row;
}

注意:

  1. 在北极和南极的时候也遍历了,虽然最终所有所有的结果都是(0,1,0)和(0,-1,0)但是从和地图对应的角度上来说,这才是正确的对应关系。
  2. pitch旋转的时候使用了负角度,这是因为希望遍历结果可以和世界地图的坐标一样的从左到右,从上到下,使用负角度就可以实现这个要求

然后就根据结果创建球体的顶点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for (int y = 0; y < m_row-1; ++y)
{
for (int x = 0; x < m_col; ++x)
{
auto _p0 = _vertexMatrix[y][x];
auto _p1 = _vertexMatrix[y + 1][x];
int _nextX = x + 1;
if (_nextX == m_col)
{
_nextX = 0;
}
auto _p2 = _vertexMatrix[y + 1][_nextX];
auto _p3 = _vertexMatrix[y][_nextX];
m_vertices << Vertex{ { _p0.x(), _p0.y(), _p0.z() }, {(float)x / m_col, (float)y / m_row} }
<< Vertex{ { _p1.x(), _p1.y(), _p1.z() }, {(float)x / m_col, (float)(y + 1) / m_row} }
<< Vertex{ { _p2.x(), _p2.y(), _p2.z() }, {(float)(x + 1) / m_col, (float)(y + 1) / m_row} }
<< Vertex{ { _p3.x(), _p3.y(), _p3.z() }, {(float)(x + 1) / m_col, (float)y / m_row} };
}
}

按照左上角、左下角、右下角、右上角的顺序提取出四个顶点以及他们的纹理坐标,水平方向上的最后一个点就是第一个点,构成回环。

然后加载纹理

1
2
3
4
auto _texture = new QOpenGLTexture(QImage("/path/to/world-map.jpg"));
_texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear);
_texture->setMagnificationFilter(QOpenGLTexture::Linear);
setTexture(_texture);

加载 Shader

  • earth.vert
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#version 450 core
in vec3 vPos;
in vec2 vTexture;
out vec2 TexCoords;

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

void main()
{
TexCoords = vTexture;

gl_Position = projection * view * model * vec4(vPos, 1.0);
}
  • earth.frag
1
2
3
4
5
6
7
8
9
10
11
#version 450 core
out vec4 FragColor;
in vec2 TexCoords;

uniform vec3 lightColor;
uniform sampler2D Texture;

void main()
{
FragColor = vec4(texture(Texture, TexCoords).rgb * lightColor, 1.0);
}
  • 加载进来
1
2
3
4
auto _program = new QOpenGLShaderProgram();
_program->addShaderFromSourceFile(QOpenGLShader::Vertex, ":/shaders/earth.vert");
_program->addShaderFromSourceFile(QOpenGLShader::Fragment, ":/shaders/earth.frag");
setShaderProgram(_program);

初始化函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void LightModel::init()
{
initializeOpenGLFunctions();
if (!m_vao.isCreated()) {
m_vao.create();
}
if (!m_vbo.isCreated()) {
m_vbo.create();
}
if (!m_program->isLinked()) {
m_program->link();
}
if (m_vertexCount < m_vertices.count()) {
if (m_vertexBuffer) {
delete[] m_vertexBuffer;
}
m_vertexBuffer = new float[m_vertices.count() * 5];
m_vertexCount = m_vertices.count();
int _offset = 0;
for (auto &vertex : m_vertices) {
m_vertexBuffer[_offset] = vertex.pos.x();
_offset++;
m_vertexBuffer[_offset] = vertex.pos.y();
_offset++;
m_vertexBuffer[_offset] = vertex.pos.z();
_offset++;
m_vertexBuffer[_offset] = vertex.texture.x();
_offset++;
m_vertexBuffer[_offset] = vertex.texture.y();
_offset++;
}
}
m_vao.bind();
m_vbo.bind();
m_vbo.allocate(m_vertexBuffer, sizeof (float) * m_vertices.count() * 5);
m_program->bind();
m_program->setAttributeBuffer("vPos", GL_FLOAT, 0 * sizeof(float), 3, 5 * sizeof(float));
m_program->enableAttributeArray("vPos");
m_program->setAttributeBuffer("vTexture", GL_FLOAT, 3 * sizeof(float), 2, 5 * sizeof(float));
m_program->enableAttributeArray("vTexture");
m_program->release();
m_vbo.release();
m_vao.release();
}

然后进行绘制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void LightModel::paint()
{
for(auto index : m_textures.keys()) {
m_textures[index]->bind(index);
}
m_vao.bind();
m_program->bind();
// 绑定变换矩阵
m_program->setUniformValue("projection", m_projection);
m_program->setUniformValue("view", m_camera->GetViewMatrix());
m_program->setUniformValue("model", model());
m_program->setUniformValue("lightColor", m_light->color().redF(), m_light->color().greenF(), m_light->color().blueF());
int _index = 0;
for(int i = 0; i < m_col * (m_row - 1); i++) {
glDrawArrays(GL_TRIANGLE_FAN, _index, 4);
_index += 4;
}
m_program->release();
m_vao.release();
for(auto texture : m_textures) {
texture->release();
}
}

绘制的成品如下

image-20220429153858862