使用QOpenGLWidget绘制3D曲面图
前面两篇文章分别演示了单独在Visual Studio 2015和Qt5.12中,编译运行仓库代码,因为个人经常在Qt环境下开发学习,恰好Qt GUI本身也有对原生opengl的适配和封装,可以使Qt窗口资源直接支持OpenGL的绘制操作,可以拜托对其他第三方库的依赖,因此用起来比较方便,下面将使用QtOpenGL对前面的代码进行“翻译”整理。
1. 环境
- Windows10, VS2015, Qt5.12.12
2.主要内容
- 继承QOpenGLWidget,QOpenGLFunctions_4_5_core,直接获得OpenGL接口支持;
- 对鼠标按压,移动,滚轮事件等进行重写,实现缩放,旋转等交互效果;
- 利用定时器事件,实现模拟动画效果;
3. 主要代码
- 自定义类直接继承QOpenGLWidget,QOpenGLFunctions_4_5_core,重写initializeGL(),paintGL(),resizeGL();
void MyGLWidget::initializeGL()
{
initializeOpenGLFunctions();
glViewport(0, 0, rect().width(), rect().height());
glEnable(GL_DEPTH_TEST);
m_surfacePlotter.generateSurfacePlot(1.0f); // 产生初始数据,用于随后在显存上创建空间
//! SURFACE PLOT
// generate surface plot VAO and VBO and EBO
glGenVertexArrays(1, &m_surfacePlotVAO);
glGenBuffers(1, &m_surfacePlotVBO);
glGenBuffers(1, &m_surfacePlotEBO);
glBindVertexArray(m_surfacePlotVAO);
// set VBO data
glBindBuffer(GL_ARRAY_BUFFER, m_surfacePlotVBO);
glBufferData(GL_ARRAY_BUFFER, m_surfacePlotter.getNumElements()*sizeof(float), m_surfacePlotter.getVertices(), GL_DYNAMIC_DRAW);
// set EBO data
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_surfacePlotEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, m_surfacePlotter.getNumIndices()*sizeof(uint32_t), m_surfacePlotter.getIndices(), GL_DYNAMIC_DRAW);
// vertices attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
//! CUBE
// generate cube VAO and VBO and EBO
glGenVertexArrays(1, &m_cubeVAO);
glGenBuffers(1, &m_cubeVBO);
glGenBuffers(1, &m_cubeEBO);
glBindVertexArray(m_cubeVAO);
// set VBO data
glBindBuffer(GL_ARRAY_BUFFER, m_cubeVBO);
glBufferData(GL_ARRAY_BUFFER, 24*sizeof(float), m_surfacePlotter.getCubeVertices(), GL_STATIC_DRAW);
// set EBO data
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, m_cubeEBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 24*sizeof(uint32_t), m_surfacePlotter.getCubeIndices(), GL_STATIC_DRAW);
// vertices attributes
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, (void*)0);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
//! init shaders
const char* vertexShaderSource
= "#version 450 core\n"
"layout (location = 0) in vec3 pos;\n"
"out vec3 fragPos;\n"
"uniform mat4 model;\n"
"uniform mat4 view;"
"uniform mat4 projection;\n"
"void main() {\n"
" gl_Position = projection * view * model * vec4(pos, 1.0);\n"
"fragPos = pos;\n"
"}\n";
const char *fragmentShaderSource
= "#version 450 core\n"
"in vec3 fragPos;\n"
"out vec4 FragColor;\n"
"uniform float zMin;\n"
"uniform float zRange;\n"
"vec4 getColor(float z) {\n"
"float startRed = 0.1;\n"
"float endRed = 1.0;\n"
"float startGreen = 0.3;\n"
"float endGreen = 0.2;\n"
"float startBlue = 0.7;\n"
"float endBlue = 0.0;\n"
"float percentFade = (z-zMin)/zRange;\n"
"float diffRed = endRed - startRed;\n"
"float diffGreen = endGreen - startGreen;\n"
"float diffBlue = endBlue - startBlue;\n"
"diffRed = (diffRed * percentFade) + startRed;\n"
"diffGreen = (diffGreen * percentFade) + startGreen;\n"
"diffBlue = (diffBlue * percentFade) + startBlue;\n"
"return vec4(diffRed, diffGreen, diffBlue, 1.0);\n"
"}\n"
"void main() {\n"
"float contrast = 0.6;\n"
" FragColor = getColor(fragPos.z);\n"
"}\n";
const char* cubeFragmentShaderSource
= "#version 450 core\n"
"in vec3 fragPos;\n"
"out vec4 FragColor;\n"
"void main() {\n"
" FragColor = vec4(0.7, 0.7, 0.7, 1.0);\n"
"}\n";
m_vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(m_vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(m_vertexShader);
checkCompileErrors(m_vertexShader, "VERTEX");
m_surfaceShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(m_surfaceShader, 1, &fragmentShaderSource, NULL);
glCompileShader(m_surfaceShader);
checkCompileErrors(m_surfaceShader,"FRAGMENT");
m_whiteShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(m_whiteShader, 1, &cubeFragmentShaderSource, NULL);
glCompileShader(m_whiteShader);
checkCompileErrors(m_whiteShader, "FRAGMENT");
m_vertexShaderProgram = glCreateProgram();
glAttachShader(m_vertexShaderProgram, m_vertexShader);
glAttachShader(m_vertexShaderProgram, m_surfaceShader);
glLinkProgram(m_vertexShaderProgram);
checkCompileErrors(m_vertexShaderProgram, "PROGRAM");
m_cubeShaderProgram = glCreateProgram();
glAttachShader(m_cubeShaderProgram, m_vertexShader);
glAttachShader(m_cubeShaderProgram, m_whiteShader);
glLinkProgram(m_cubeShaderProgram);
checkCompileErrors(m_cubeShaderProgram, "PROGRAM");
glDeleteShader(m_vertexShader);
glDeleteShader(m_surfaceShader);
glDeleteShader(m_whiteShader);
}
void MyGLWidget::paintGL()
{
m_clearColor = {0.05f, 0.18f, 0.25f, 1.0f};
glClearColor(m_clearColor.r, m_clearColor.g, m_clearColor.b, m_clearColor.alpha);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_surfacePlotter.generateSurfacePlot(1.0f); // 这里保证,每次绘图前,z轴最小值和范围都一致,保证视觉上统一
int zRange = m_surfacePlotter.getZRange();
QMatrix4x4 viewMatrix;
viewMatrix.setToIdentity();
viewMatrix.lookAt(QVector3D(0.0f, 0.0f, 30.0f), QVector3D(0.0f, 0.0f, 30.0f) + QVector3D(0.0f, 0.0f, -0.5f), QVector3D(1.0f, 0.0f, 0.0f));
QMatrix4x4 projectionMatrix;
projectionMatrix.setToIdentity();
projectionMatrix.perspective(/*45.0f*/mZoom, (float)(rect().width())/(rect().height()), 0.1f, 99999.0f);
QMatrix4x4 defaultModelMatrix;
defaultModelMatrix.setToIdentity();
defaultModelMatrix.translate(0.0f, 0.0f, -0.5f);
defaultModelMatrix.rotate(45.0f, QVector3D(0.0f, 1.0f, 0.0f));
glUseProgram(m_vertexShaderProgram);
glUniform1f(glGetUniformLocation(m_vertexShaderProgram, "zRange"), (zRange == 0) ? 1.0f : zRange);
glUniform1f(glGetUniformLocation(m_vertexShaderProgram, "zMin"), m_surfacePlotter.getZMin());
glUniformMatrix4fv(glGetUniformLocation(m_vertexShaderProgram, "view"), 1, GL_FALSE, viewMatrix.data());
glUniformMatrix4fv(glGetUniformLocation(m_vertexShaderProgram, "projection"), 1, GL_FALSE, projectionMatrix.data());
glUniformMatrix4fv(glGetUniformLocation(m_vertexShaderProgram, "model"), 1, GL_FALSE, (defaultModelMatrix*mModelMatrix).data());
glUseProgram(m_cubeShaderProgram);
glUniformMatrix4fv(glGetUniformLocation(m_cubeShaderProgram, "view"), 1, GL_FALSE, viewMatrix.data());
glUniformMatrix4fv(glGetUniformLocation(m_cubeShaderProgram, "projection"), 1, GL_FALSE, projectionMatrix.data());
glUniformMatrix4fv(glGetUniformLocation(m_cubeShaderProgram, "model"), 1, GL_FALSE, (defaultModelMatrix*mModelMatrix).data());
m_surfacePlotter.generateSurfacePlot(static_cast<float>((elapsedSec%100000)/1000.0)); // 产生新的, 随时间变化的周期性数据,
glUseProgram(m_vertexShaderProgram);
glBindVertexArray(m_surfacePlotVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_surfacePlotVBO);
glBufferData(GL_ARRAY_BUFFER, m_surfacePlotter.getNumElements()*sizeof(float), m_surfacePlotter.getVertices(), GL_DYNAMIC_DRAW);
glDrawElements(GL_LINES, m_surfacePlotter.getNumIndices(),GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
glUseProgram(m_cubeShaderProgram);
glBindVertexArray(m_cubeVAO);
glBindBuffer(GL_ARRAY_BUFFER, m_cubeVBO);
glBufferData(GL_ARRAY_BUFFER, 24*sizeof(float), m_surfacePlotter.getCubeVertices(), GL_STATIC_DRAW);
glDrawElements(GL_LINES, 24, GL_UNSIGNED_INT, 0);
glBindVertexArray(0);
}
- 重写鼠标事件
void MyGLWidget::mousePressEvent(QMouseEvent *event)
{
mPreMousePos = event->pos();
m_bIsMousePressed = true;
}
void MyGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
Q_UNUSED(event)
m_bIsMousePressed = false;
}
void MyGLWidget::mouseMoveEvent(QMouseEvent *event)
{
if (m_bIsMousePressed) {
qDebug() << __FUNCTION__ << __LINE__ << event->localPos();
// get current cursor coordinates
QPointF currMousePos = event->pos();
// get points on arcball
QVector3D va = getArcballVector(mPreMousePos.x(), mPreMousePos.y());
QVector3D vb = getArcballVector(currMousePos.x(), currMousePos.y());
float speedFactor = 0.1f;
float angleOfRotation = speedFactor * acos(std::fmin(1.0f, QVector3D::dotProduct(va, vb)));
// to get the axis of rotation, need to convert from camera coordinates to world coordinates
QVector4D axisCamera = QVector3D::crossProduct(va, vb).toVector4D();
QMatrix4x4 viewMatrix;
viewMatrix.setToIdentity();
viewMatrix.lookAt(QVector3D(0.0f, 0.0f, 30.0f), QVector3D(0.0f, 0.0f, 30.0f) + QVector3D(0.0f, 0.0f, -0.5f), QVector3D(1.0f, 0.0f, 0.0f));
/// FIXED ME: 下面翻译的可能和源代码不么对应!!!
QMatrix4x4 cameraToModel = (viewMatrix*mModelMatrix).inverted();
QVector4D axisModel = cameraToModel * axisCamera;
// update model rotation matrix
float tolerance = 1e-4;
if (angleOfRotation > tolerance) {
mModelMatrix.rotate(angleOfRotation*1000, axisModel.toVector3D());
}
// update cursor position
mPreMousePos = currMousePos;
}
}
void MyGLWidget::wheelEvent(QWheelEvent *event)
{
float delta = event->angleDelta().y() * 0.05f;
mZoom -= delta;
if(mZoom < 1.0f) {
mZoom = 1.0f;
}
if (mZoom > 100.f) {
mZoom = 100.0f;
}
}
4 运行效果演示
5. 后续
后面将会仔细记录以下,QtOpenGL使用方法,并进一步使用Qt对OpenGL的封装类,如QOpenGLContext ,QOpenGLBuffer,QOpenGLVertexArrayObject,QOpenGLShaderProgram等对原生OpenGL资源封装方便类,来使程序的移植性更好,尤其在涉及嵌入式QT的时候。