Qt3D改变观察视角例程(一)

发布时间 2023-10-31 03:36:30作者: 兜尼完

在3D显示中,有Model矩阵、View矩阵和Project矩阵。简称为MVP矩阵。这里实现的是改变View矩阵中的观察点的位置,视野中心不变。亦即站在一个圆环的不同地方朝圆心观察。本文显示的是一个平面(地面)上面悬浮一个四面体,鼠标按下移动来改变观察点和方向。关于摄像机姿势的概念可以参考以下网页内容,主要是欧拉角的变换:

本文代码效果图是:

下面是头文件,测试环境是VS2017和Qt5.9:

class QOpenGLTexture;
class QOpenGLBuffer;

class MuOpenGLWidget : public QOpenGLWidget, private QOpenGLFunctions
{
    Q_OBJECT

private:
    struct VertexData
    {
        QVector3D pos;
        QVector3D normal;
        QVector3D color;
    };

public:
    MuOpenGLWidget(QWidget* parent = 0);

private:
    void initializeGL() override;
    void paintGL() override;
    void resizeGL(int w, int h) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    QVector3D calcDirVec() const;

private:
    QOpenGLShaderProgram* program;
    QOpenGLBuffer* vertexBuff;
    QOpenGLBuffer* indexBuff;
    /* 下面2个是相机方向,都为0时相机朝向X轴正方向 */
    float yaw; /* 绕Y轴旋转 */
    float pitch; /* 绕X轴旋转 */
    /* 下面1个是鼠标上次点击的位置 */
    QPoint lastPos;
};

CPP文件:

MuOpenGLWidget::MuOpenGLWidget(QWidget* parent) :
    QOpenGLWidget(parent), lastPos(0xDEADBEEFi32, 0xDEADBEEFi32)
{
    yaw = -90.0f;
    pitch = 0;

    QSurfaceFormat surface;
    surface.setSamples(4);
    setFormat(surface);
}

void MuOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();
    glEnable(GL_DEPTH_TEST);

    program = new QOpenGLShaderProgram(this);
    const char* vsrc = u8R"(
        #version 330 core
        layout (location = 0) in vec3 aPos; // 空间坐标
        layout (location = 1) in vec3 aNormal; // 法线向量
        layout (location = 2) in vec3 aColor; // 顶点颜色
        uniform mat4 modelMatrix;
        uniform mat4 mvpMatrix;
        out vec3 oNormal;
        out vec3 oFragPos;
        out vec3 oColor;

        void main()
        {
            oFragPos = vec3(modelMatrix * vec4(aPos, 1.0));
            oNormal =  mat3(transpose(inverse(modelMatrix))) * aNormal;
            oColor = aColor;
            gl_Position = mvpMatrix * vec4(aPos, 1.0);
        })";
    program->addShaderFromSourceCode(QOpenGLShader::Vertex, vsrc);
    const char* fsrc = u8R"(
        #version 330 core
        uniform vec3 lightPos; 
        uniform vec3 viewPos; 
        uniform vec3 lightColor;
        in vec3 oNormal;
        in vec3 oFragPos;
        in vec3 oColor;

        void main()
        {
            // 环境光照
            float ambientStrength = 0.4;
            vec3 ambient = ambientStrength * lightColor;
            // 漫反射光照 
            vec3 norm = normalize(oNormal);
            vec3 lightDir = normalize(lightPos - oFragPos);
            float diff = max(dot(norm, lightDir), 0.0);
            vec3 diffuse = diff * lightColor;
            // 镜面光照
            float specularStrength = 1.0;
            vec3 viewDir = normalize(viewPos - oFragPos);
            vec3 reflectDir = reflect(-lightDir, norm);
            float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32.0);
            vec3 specular = specularStrength * spec * lightColor;
            // 把纹理中的颜色和光照参数相乘
            gl_FragColor = vec4((ambient + diffuse + specular) * oColor, 1.0);
        })";
    program->addShaderFromSourceCode(QOpenGLShader::Fragment, fsrc);

    program->link();
    program->bind();

    VertexData vertices[] =
    {
        { QVector3D(-0.433f, 0, -0.25f), QVector3D(-0.816f, 0.333f, 0.471f), QVector3D(1.0f, 0, 0) },
        { QVector3D(0, 0, 0.5f), QVector3D(-0.816f, 0.333f, 0.471f), QVector3D(0, 1.0f, 0) },
        { QVector3D(0, 0.707f, 0), QVector3D(-0.816f, 0.333f, 0.471f), QVector3D(0, 0, 1.0f) },

        { QVector3D(0, 0, 0.5f), QVector3D(0.816f, 0.333f, 0.471f), QVector3D(1.0f, 1.0f, 0) },
        { QVector3D(0.433f, 0, -0.25f), QVector3D(0.816f, 0.333f, 0.471f), QVector3D(1.0f, 0, 1.0f) },
        { QVector3D(0, 0.707f, 0), QVector3D(0.816f, 0.333f, 0.471f), QVector3D(0, 1.0f, 1.0f) },

        { QVector3D(0.433f, 0, -0.25f), QVector3D(0, 0.333f, -0.943f), QVector3D(0, 0, 1.0f) },
        { QVector3D(-0.433f, 0, -0.25f), QVector3D(0, 0.333f, -0.943f), QVector3D(0, 1.0f, 0) },
        { QVector3D(0, 0.707f, 0), QVector3D(0, 0.333f, -0.943f), QVector3D(1.0f, 0, 0) },

        { QVector3D(0, 0, 0.5f), QVector3D(0, -1.0f, 0), QVector3D(0, 1.0f, 1.0f) },
        { QVector3D(-0.433f, 0, -0.25f), QVector3D(0, -1.0f, 0), QVector3D(1.0f, 1.0f, 0) },
        { QVector3D(0.433f, 0, -0.25f), QVector3D(0, -1.0f, 0), QVector3D(1.0f, 0, 1.0f) },

        /* 后面6个点绘制水平的地面 */
        { QVector3D(-10.0f, -0.2f, -10.0f), QVector3D(0, 1.0f, 0), QVector3D(0, 1.0f, 1.0f) },
        { QVector3D(-10.0f, -0.2f, 10.0f), QVector3D(0, 1.0f, 0), QVector3D(1.0f, 1.0f, 0) },
        { QVector3D(10.0f, -0.2f, -10.0f), QVector3D(0, 1.0f, 0), QVector3D(1.0f, 0, 1.0f) },

        { QVector3D(10.0f, -0.2f, -10.0f), QVector3D(0, 1.0f, 0), QVector3D(0, 1.0f, 1.0f) },
        { QVector3D(-10.0f, -0.2f, 10.0f), QVector3D(0, 1.0f, 0), QVector3D(1.0f, 1.0f, 0) },
        { QVector3D(10.0f, -0.2f, 10.0f), QVector3D(0, 1.0f, 0), QVector3D(1.0f, 0, 1.0f) },
    };
    GLushort indices[] =
    {
        0, 1, 2, 3, 4, 5,
        6, 7, 8, 9, 10, 11,
        12, 13, 14, 15, 16, 17,
    };
    vertexBuff = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
    vertexBuff->create();
    vertexBuff->bind();
    vertexBuff->allocate(vertices, 18 * sizeof(VertexData));
    indexBuff = new QOpenGLBuffer(QOpenGLBuffer::IndexBuffer);
    indexBuff->create();
    indexBuff->bind();
    indexBuff->allocate(indices, 18 * sizeof(float));

    int offset = 0;
    int vertexLocation = program->attributeLocation("aPos");
    program->enableAttributeArray(vertexLocation);
    program->setAttributeBuffer(vertexLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
    offset += sizeof(QVector3D);
    int normalLocation = program->attributeLocation("aNormal");
    program->enableAttributeArray(normalLocation);
    program->setAttributeBuffer(normalLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
    offset += sizeof(QVector3D);
    int colorLocation = program->attributeLocation("aColor");
    program->enableAttributeArray(colorLocation);
    program->setAttributeBuffer(colorLocation, GL_FLOAT, offset, 3, sizeof(VertexData));
}

#define LIGHT_COLOR QVector3D(0.9f, 1.0f, 2.0f) /* 这里的颜色值可以大于1不知道为什么 */
#define LIGHT_POS   QVector3D(2.5f, 5.8f, 2.0f)
#define VIEW_CENTER QVector3D(0.0f, 0.25f, 0.0f)

void MuOpenGLWidget::paintGL()
{
    QMatrix4x4 projection;
    projection.perspective(45.0f, 1, 0.1f, 100.0f);
    QMatrix4x4 viewMatrix;
    QVector3D eyeDir = calcDirVec();
    QVector3D eyePos = VIEW_CENTER - 10 * eyeDir;
    viewMatrix.lookAt(eyePos, VIEW_CENTER, QVector3D(0, 1, 0));
    QMatrix4x4 modelMatrix;
    modelMatrix.translate(0, -0.8, 0); // 平移
    modelMatrix.rotate(0, 0, 1); // 旋转
    modelMatrix.scale(4.0); // 缩放
    QMatrix4x4 mvpMatrix = projection * viewMatrix * modelMatrix;

    program->setUniformValue("lightColor", LIGHT_COLOR);
    program->setUniformValue("lightPos", LIGHT_POS);
    program->setUniformValue("viewPos", eyePos);
    program->setUniformValue("mvpMatrix", mvpMatrix);
    program->setUniformValue("modelMatrix", modelMatrix);
    glDrawElements(GL_TRIANGLES, 18, GL_UNSIGNED_SHORT, 0);
}

#undef LIGHT_COLOR
#undef LIGHT_POS
#undef EYE_CENTER

void MuOpenGLWidget::resizeGL(int w, int h)
{
}

void MuOpenGLWidget::mousePressEvent(QMouseEvent *event)
{
    lastPos = event->pos();
}

void MuOpenGLWidget::mouseReleaseEvent(QMouseEvent *event)
{
    lastPos = QPoint(0xDEADBEEFi32, 0xDEADBEEFi32);
}

void MuOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    float xoffset = event->pos().x() - lastPos.x();
    float yoffset = lastPos.y() - event->pos().y();
    lastPos = event->pos();

    float sensitivity = 0.25f;
    xoffset *= sensitivity;
    yoffset *= sensitivity;

    yaw += xoffset;
    pitch += yoffset;
    pitch = std::max(-89.0f, std::min(89.0f, pitch));

    update();
}

QVector3D MuOpenGLWidget::calcDirVec() const
{
    QVector3D front;
    front.setX(cosf(qDegreesToRadians(yaw)) * cosf(qDegreesToRadians(pitch)));
    front.setY(sinf(qDegreesToRadians(pitch)));
    front.setZ(sinf(qDegreesToRadians(yaw)) * cosf(qDegreesToRadians(pitch)));
    return front.normalized();
}