优化
开启多重采样
为了解决模型边缘出现锯齿的问题,可以开启多重采样。在 OpenGLWidget 类初始化的时候,通过格式设置多重采样
| auto newFormat = this->format(); newFormat.setSamples(16); this->setFormat(newFormat);
|
背面剪裁
OpenGL 在绘制的时候,是会同时绘制正面和背面,而背面用户是看不到的,所以为了节省资源,可以开启背面裁剪,不对背面进行绘制。
如何判断正面或者背面?
OpenGL 中,一个面的顶点是逆时针排列的那一面就是正面,顺时针排列的那一面就是背面。
在 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)); }
|
线框模式
线框模式让你可以清楚的看到你的模型面的组成方法
| 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; }
|
注意:
- 在北极和南极的时候也遍历了,虽然最终所有所有的结果都是(0,1,0)和(0,-1,0)但是从和地图对应的角度上来说,这才是正确的对应关系。
- 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} }; } }
|
按照左上角、左下角、右下角、右上角的顺序提取出四个顶点以及他们的纹理坐标,水平方向上的最后一个点就是第一个点,构成回环。
然后加载纹理
| auto _texture = new QOpenGLTexture(QImage("/path/to/world-map.jpg")); _texture->setMinificationFilter(QOpenGLTexture::LinearMipMapLinear); _texture->setMagnificationFilter(QOpenGLTexture::Linear); setTexture(_texture);
|
加载 Shader
| #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); }
|
| #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); }
|
| 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(); } }
|
绘制的成品如下
