OpenGL-камера

1. Введение

В самом OpenGL нет понятия камеры (Camera), но мы можем имитировать камеру, перемещая все объекты в сцене в противоположном направлении, создавая ощущение, что движемся мы, а не сцена.

Чтобы определить камеру, нам нужна ее позиция в мировом пространстве, направление, на которое она смотрит, вектор, указывающий вправо, и вектор, указывающий вверх.

Положение камеры:

Получить положение камеры просто. Положение камеры — это просто вектор, указывающий на положение камеры в мировом пространстве. Не забывайте, что положительная ось z указывает на вас с экрана, если мы хотим, чтобы камера двигалась назад, мы двигаемся вдоль положительной оси z.

QVector3D cameraPos = QVector3D( 0.0f,  0.0f,  2.0f);//摄像机位置

Направление камеры:

Это относится к тому, в каком направлении направлена ​​камера. Теперь наведем камеру на начало сцены: (0, 0, 0). Результатом вычитания вектора положения камеры из исходного вектора сцены является вектор наведения камеры. Поскольку мы знаем, что камера указывает на отрицательное направление оси z, мы хотим, чтобы вектор направления (вектор направления) указывал на положительное направление оси z камеры. Если мы поменяем порядок вычитания, мы получим вектор, указывающий в направлении положительной оси z камеры:

    cameraTarget = QVector3D( 0.0f,  0.0f,  0.0f);//摄像机看到的位置
    cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
    cameraDirection.normalize();

Правая ось: 

Он представляет собой положительное направление оси x в пространстве камеры. Чтобы получить правильный вектор, нам нужно использовать небольшую хитрость: сначала определить восходящий вектор (Up Vector). Далее перекрестно умножаем верхний вектор и вектор направления камеры , полученный на втором шаге . Результат перекрестного произведения двух векторов будет перпендикулярен обоим векторам одновременно, поэтому мы получим вектор, указывающий в положительном направлении оси x (если мы поменяем порядок перекрестного произведения двух векторов , мы получим противоположный вектор, указывающий в отрицательном направлении оси x):

    up = QVector3D(0.0f,  1.0f,  0.0f);
    cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
    cameraRight.normalize();

Верхняя ось:

Произведите произведение правильного вектора на вектор направления камеры, чтобы получить вектор камеры вверх :

cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);

Посмотри на:

Одна из приятных особенностей использования матриц заключается в том, что если вы определяете координатное пространство с 3 взаимно перпендикулярными (или нелинейными) осями, вы можете создать матрицу с этими 3 осями плюс вектор смещения, и вы можете умножить его на преобразование в это координатное пространство с любым вектором. 

    QTime gtime;
    QMatrix4x4 view;

    float radius = 10.0f;    //圆半径
    float time = gtime.elapsed()/1000.0;
    float camx = sin(time) * radius;
    float camz = cos(time) * radius;

    view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);

Введение в параметры:

Положение камеры , целевое положение и вектор, представляющий восходящий вектор в мировом пространстве (тот, который мы используем для вычисления правильного вектора).

2. Пример

#ifndef MYOPENGLWIDGET_H
#define MYOPENGLWIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions_3_3_Core>
#include <QOpenGLTexture>
#include <QImage>
#include <QOpenGLShaderProgram>
#include <QVector3D>
#include <QVector>

class MyOpenGLWidget : public QOpenGLWidget,public QOpenGLFunctions_3_3_Core
{
public:
    MyOpenGLWidget(QWidget *parent = nullptr);

protected:
    virtual void initializeGL();
    virtual void paintGL();
    virtual void resizeGL(int w, int h);

private:
    QOpenGLTexture *m_wall;

    QOpenGLTexture *m_face;

    QOpenGLShaderProgram *m_program;

    QVector3D cameraPos;
    QVector3D cameraTarget;
    QVector3D cameraDirection;
    QVector3D up;
    QVector3D cameraRight;
    QVector3D cameraUp;
};

#endif // MYOPENGLWIDGET_H



#include "myopenglwidget.h"
#include <QMatrix4x4>
#include <QTime>
#include <QTimer>
#include <math.h>


float vertices[] = {
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f, 1.0f
};

GLuint indices[] = {
    0, 1, 3,
    1, 2, 3
};

//顶点着色器语言
const GLchar* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 position;\n"
"layout (location = 1) in vec2 texCoord;\n"
"out vec2 outTexCoord;\n"
"uniform mat4 model;\n"
"uniform mat4 view;\n"
"uniform mat4 projection;\n"
"void main()\n"
"{\n"
"gl_Position = projection * view * model * vec4(position,1.0);\n"
"outTexCoord = texCoord;\n"
"}\n\0";

//片段着色器语言
//texture函数会使用之前设置的纹理参数对相应的颜色值进行采样
//mix按一定的比例,混合两个纹理颜色
const GLchar* fragmentShaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform sampler2D ourTexture1;\n"
"uniform sampler2D ourTexture2;\n"
"in vec2 outTexCoord;\n"
"void main()\n"
"{\n"
"color = mix(texture(ourTexture1, outTexCoord),texture(ourTexture2, vec2(outTexCoord.x, outTexCoord.y)),0.45);\n"
"}\n\0";

GLuint VBO, VAO,EBO;
GLuint shaderProgram;

QTimer *timer;
QTime gtime;

QVector<QVector3D> cubePositions = {
  QVector3D( 0.0f,  0.0f,  0.0f),
  QVector3D( 2.0f,  5.0f, -15.0f),
  QVector3D(-1.5f, -2.2f, -2.5f),
  QVector3D(-3.8f, -2.0f, -12.3f),
  QVector3D( 2.4f, -0.4f, -3.5f),
  QVector3D(-1.7f,  3.0f, -7.5f),
  QVector3D( 1.3f, -2.0f, -2.5f),
  QVector3D( 1.5f,  2.0f, -2.5f),
  QVector3D( 1.5f,  0.2f, -1.5f),
  QVector3D(-1.3f,  1.0f, -1.5f)
};

MyOpenGLWidget::MyOpenGLWidget(QWidget *parent)
    : QOpenGLWidget(parent)
{
    timer = new QTimer();
    timer->start(50);
    connect(timer,&QTimer::timeout,[=]{
        update();
    });

    gtime.start();

    cameraPos = QVector3D( 0.0f,  0.0f,  2.0f);//摄像机位置
    cameraTarget = QVector3D( 0.0f,  0.0f,  0.0f);//摄像机看到的位置
    cameraDirection = QVector3D(cameraPos - cameraTarget);//摄像机的方向
    cameraDirection.normalize();

    up = QVector3D(0.0f,  1.0f,  0.0f);
    cameraRight = QVector3D::crossProduct(up,cameraDirection);//两个向量叉乘的结果会同时垂直于两向量,因此我们会得到指向x轴正方向的那个向量
    cameraRight.normalize();

    cameraUp = QVector3D::crossProduct(cameraDirection,cameraRight);
}

void MyOpenGLWidget::initializeGL()
{
    initializeOpenGLFunctions();

    m_program = new QOpenGLShaderProgram();
    m_program->addShaderFromSourceCode(QOpenGLShader::Vertex,vertexShaderSource);
    m_program->addShaderFromSourceCode(QOpenGLShader::Fragment,fragmentShaderSource);
    m_program->link();

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);

    glBindVertexArray(VAO);//绑定VAO
    glBindBuffer(GL_ARRAY_BUFFER, VBO);//顶点缓冲对象的缓冲类型是GL_ARRAY_BUFFER
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//把顶点数据复制到缓冲的内存中GL_STATIC_DRAW :数据不会或几乎不会改变。

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)0);
    glEnableVertexAttribArray(0);

    glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
    glEnableVertexAttribArray(1);

    glBindBuffer(GL_ARRAY_BUFFER, 0);

    glGenBuffers(1, &EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    glBindVertexArray(0);//解绑VAO

    m_wall = new QOpenGLTexture(QImage("./container.jpg").mirrored());
    m_face = new QOpenGLTexture(QImage("./awesomeface.png").mirrored());

    m_program->bind();
    m_program->setUniformValue("ourTexture1",0);
    m_program->setUniformValue("ourTexture2",1);

    //设置投影透视矩阵
    QMatrix4x4 projection;
    projection.perspective(60,(float)( width())/(height()),0.1,100);
    m_program->setUniformValue("projection",projection);

}

void MyOpenGLWidget::paintGL()
{
    glClearColor(0.2f,0.3f,0.3f,1.0f);
    glEnable(GL_DEPTH_TEST);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    QMatrix4x4 model;
    QMatrix4x4 view;

    //随时间变化的x和z坐标
    float radius = 10.0f;
    float time = gtime.elapsed()/1000.0;
    float camx = sin(time) * radius;
    float camz = cos(time) * radius;

    view.lookAt(QVector3D(camx,0.0,camz),cameraTarget,up);

    m_program->bind();

    glBindVertexArray(VAO);//绑定VAO

    m_wall->bind(0);
    m_face->bind(1);

    //设置观察矩阵
    m_program->setUniformValue("view",view);

    foreach(auto pos , cubePositions)
    {
        model.setToIdentity();
        model.translate(pos);
        //model.rotate(time,1.0f,5.0f,3.0f);
        //设置模型矩阵
        m_program->setUniformValue("model",model);
        glDrawArrays(GL_TRIANGLES,0,36);
    }

}

void MyOpenGLWidget::resizeGL(int w, int h)
{

}

3. Свобода передвижения

Теперь функция просмотра становится такой: сначала мы устанавливаем положение камеры в значение cameraPos, определенное ранее. Направление — это текущая позиция плюс вектор направления, который мы только что определили. Это гарантирует, что как бы мы ни двигались, камера всегда будет смотреть в направлении цели.

    QMatrix4x4 view;
    view.lookAt(cameraPos,cameraPos + cameraFront,cameraUp);

События клавиатуры:

void MyOpenGLWidget::keyPressEvent(QKeyEvent *event)
{
    qDebug()<<event->key();
    cameraSpeed = 2.5 * 100 / 1000.0;
    switch (event->key()) {
    case Qt::Key_W:{
        cameraPos += cameraSpeed * cameraFront;
    }
        break;
    case Qt::Key_S:{
        cameraPos -= cameraSpeed * cameraFront;
    }
        break;
    case Qt::Key_A:{
        cameraPos -= cameraSpeed * cameraRight;
    }
        break;
    case Qt::Key_D:{
        cameraPos += cameraSpeed * cameraRight;
    }
        break;
    default:
        break;

    }
    update();
}

Когда мы нажимаем любую из клавиш WASD , положение камеры будет обновляться соответствующим образом. Если мы хотим двигаться вперед или назад, мы добавляем или вычитаем вектор направления к вектору положения или из него. Если мы хотим двигаться влево и вправо, мы используем перекрестное произведение, чтобы создать правый вектор и двигаться по нему соответственно. Это создает знакомый эффект Strafe при работе с камерами. 

4. Движение угла обзора

Просто передвигаться с клавиатурой не очень весело. Тем более, что мы пока не можем поворачиваться, движение очень ограничено.

Чтобы иметь возможность изменить угол обзора, нам нужно изменить вектор cameraFront в соответствии с вводом мыши .

Если вам интересно, вы можете узнать больше об углах Эйлера. Вот код напрямую.

float PI = 3.1415926;
QPoint deltaPos;
void MyOpenGLWidget::mouseMoveEvent(QMouseEvent *event)
{
    static float yaw = -90;
    static float pitch = 0;
    //上一次的位置
    static QPoint lastPos(width()/2,height()/2);
    
    //当前的位置
    auto currentPos = event->pos();
    
    //
    deltaPos = currentPos-lastPos;
    lastPos = currentPos;
    
    //灵敏度
    float sensitivity = 0.1f;
    deltaPos *= sensitivity;
    yaw += deltaPos.x();
    pitch -= deltaPos.y();
    
    if(pitch > 89.0f) 
        pitch = 89.0f;
    
    if(pitch < -89.0f) 
        pitch = -89.0f;
    
    cameraFront.setX(cos(yaw*PI/180.0) * cos(pitch *PI/180));
    cameraFront.setY(sin(pitch*PI/180));
    cameraFront.setZ(sin(yaw*PI/180) * cos(pitch *PI/180));
    cameraFront.normalize();
    
    update();
}

5. Масштаб

Мы говорим, что поле зрения (Field of View) или fov определяет, какую часть сцены мы можем видеть. Когда поле зрения становится меньше, пространство, проецируемое сценой, уменьшается, что приводит к ощущению увеличения (Увеличение). Мы будем использовать колесо прокрутки мыши для увеличения.

Теперь мы должны загружать матрицу перспективной проекции в GPU каждый кадр, но теперь используем переменную fov в качестве ее поля зрения:

projection.perspective(fov,(float)( width())/(height()),0.1,100);

События колеса:


void MyOpenGLWidget::wheelEvent(QWheelEvent *event)
{
    if(fov >= 1.0f && fov <= 75.0f)
        fov -= event->angleDelta().y()/120;
    if(fov <= 1.0f)
        fov = 1.0f;
    if(fov >= 75.0f)
        fov = 75.0f;

    update();
}

 6. Полный исходный код

https://download.csdn.net/download/wzz953200463/87887281 https://download.csdn.net/download/wzz953200463/87887281

Supongo que te gusta

Origin blog.csdn.net/wzz953200463/article/details/131134080
Recomendado
Clasificación