zoukankan      html  css  js  c++  java
  • 3D Computer Grapihcs Using OpenGL

    在11节我们说过,MVP矩阵中目前只应用了两个矩阵,World to View 矩阵被省略了,这就导致我们的画面没有办法转换视角。

    本节我们将添加这一环节,让相机可以旋转。

    为了实现这一目的,我们添加一个相机类, Camera类。

    Camera.h:

     1 #pragma once
     2 #include <glmglm.hpp>
     3 
     4 class Camera
     5 {
     6 private:
     7     glm::vec3 position;
     8     glm::vec3 viewDirection;
     9     const glm::vec3 UP;
    10     glm::vec2 oldMousePosition;
    11 
    12 public:
    13     Camera();
    14     glm::mat4 getWorldToViewMatrix() const;
    15     void mouseUpdate(const glm::vec2& newMousePosition);
    16 };

    Camera.cpp:

     1 #include "Camera.h"
     2 #include "glmgtx	ransform.hpp"
     3 
     4 Camera::Camera():
     5     viewDirection(0.0f,0.0f,-1.0f),
     6     UP(0.0f,1.0f,0.0f)
     7 {
     8 
     9 }
    10 
    11 glm::mat4 Camera::getWorldToViewMatrix() const
    12 {
    13     return glm::lookAt(position, position + viewDirection, UP);
    14 }
    15 
    16 void Camera::mouseUpdate(const glm::vec2 & newMousePosition)
    17 {
    18     glm::vec2 mouseDelta = newMousePosition - oldMousePosition;
    19     if (glm::length(mouseDelta) > 10.0f)
    20     {
    21         oldMousePosition = newMousePosition;
    22         return;
    23     }
    24         
    25 
    26     viewDirection = glm::mat3(glm::rotate(mouseDelta.x * 0.01f, UP)) * viewDirection;
    27 
    28     oldMousePosition = newMousePosition;
    29 }

    glm::lookAt

    构建Camera类的最终目的是提供一个 World to view 转换矩阵,这个矩阵可以使用一个函数 glm::lookAt 来构造。

    glm::lookAt需要三个参数:

    1. 相机在世界中的位置坐标
    2. 相机的观察目标
    3. 相机的“上”方向

    Camera类

    Camera类中定义了这些成员:

    • position- 表示相机的位置
    • viewDirection - 表示相机的视线方向
    • UP - 一个常量,用于表示世界的上方
    • oldMousePosition - 表示上一次鼠标的位置
    • getWorldToViewMatrix()函数 - 用于返回World to View转换矩阵
    • mouseUpdate() 函数,计算两次更新之间鼠标的位置变化,并根据此变化更新world to view矩阵

    其中前三个成员正好可以提供给getWorldToViewmatrix用于返回world to view矩阵,唯一需要做点计算的是第二个参数,lookAt函数需要的是一个目标,而目标位置可以使用相机位置加上视线方向"模拟"出来,实际上只要朝向是我们需要的,我们并不用关心真正的“目标”是什么。

    为什么不直接提供一个“目标位置”的成员呢?

    原因是我们需要“旋转”相机,而旋转操作的结果是“方向”向量。这点在Camera.cpp的26行体现出来了。如果我们直接提供的是“目标位置”,这里的计算就无法进行了。

    第26行之所以给mouseDelta.x乘以0.01,是对旋转的速度进行了细节的调整。

    19-23行是为了避免鼠标离开屏幕后,再次进入时产生的跳跃。

    MyGlWindow类的修改

    MyGlWindow.h

    • 引入了Camera.h
    • 新增一个Camera类型的成员camera
    • override了一个 mouseMoveEvent函数,这个函数是QWidget类的虚函数,在鼠标按下以后会持续调用
    • 把transformMatrixBufferID提取到类成员中。

    最终代码:

     1 #pragma once
     2 #include <QtOpenGLqgl.h>
     3 #include <string>
     4 #include "Camera.h"
     5 
     6 class MyGlWindow :public QGLWidget
     7 {
     8 protected:
     9     void sendDataToOpenGL();
    10     void installShaders();
    11     void initializeGL();
    12     void paintGL();
    13     GLuint transformMatrixBufferID;
    14     Camera camera;
    15     std::string ReadShaderCode(const char* fileName);
    16     void mouseMoveEvent(QMouseEvent*);
    17 };

    MyGlWindow.cpp

    • 头文件包含 <Qt3DInputqmouseevent.h>
    • 修改sendDataToOpenGL()函数
    • 修改paintGL()函数
    • 重新实现mouseMoveEvent()函数

    修改后的代码:

     1 #include <glglew.h>
     2 #include "MyGlWindow.h"
     3 #include <iostream>
     4 #include <fstream>
     5 #include <glmgtcmatrix_transform.hpp>
     6 #include <glmgtx	ransform.hpp>
     7 #include <ShapeGenerator.h>
     8 #include <Qt3DInputqmouseevent.h>
     9 
    10 
    11 GLuint programID;
    12 GLuint numIndices;
    13 
    14 void MyGlWindow::sendDataToOpenGL()
    15 {
    16     
    17     ShapeData shape = ShapeGenerator::makeCube();
    18 
    19     GLuint vertexBufferID;
    20     glGenBuffers(1, &vertexBufferID);
    21     glBindBuffer(GL_ARRAY_BUFFER, vertexBufferID);
    22     glBufferData(GL_ARRAY_BUFFER, shape.vertexBufferSize(), shape.vertices, GL_STATIC_DRAW);
    23 
    24     glEnableVertexAttribArray(0);
    25     glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, 0);
    26 
    27 
    28     GLuint indexBufferID;
    29     glGenBuffers(1, &indexBufferID);
    30     glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBufferID);
    31     glBufferData(GL_ELEMENT_ARRAY_BUFFER, shape.indexBufferSize(), shape.indices, GL_STATIC_DRAW);
    32 
    33     glEnableVertexAttribArray(1);
    34     glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (char*)(sizeof(GLfloat) * 3));
    35 
    36     numIndices = shape.numIndices;
    37     shape.cleanUp();
    38 
    39     //instancing
    40     
    41     glGenBuffers(1, &transformMatrixBufferID);
    42     glBindBuffer(GL_ARRAY_BUFFER, transformMatrixBufferID);
    43 
    44     glBufferData(GL_ARRAY_BUFFER, sizeof(glm::mat4) * 2, 0, GL_DYNAMIC_DRAW);
    45     glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 0));
    46     glVertexAttribPointer(3, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 4));
    47     glVertexAttribPointer(4, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 8));
    48     glVertexAttribPointer(5, 4, GL_FLOAT, GL_FALSE, sizeof(glm::mat4), (void*)(sizeof(float) * 12));
    49     glEnableVertexAttribArray(2);
    50     glEnableVertexAttribArray(3);
    51     glEnableVertexAttribArray(4);
    52     glEnableVertexAttribArray(5);
    53     glVertexAttribDivisor(2, 1);
    54     glVertexAttribDivisor(3, 1);
    55     glVertexAttribDivisor(4, 1);
    56     glVertexAttribDivisor(5, 1);
    57 }
    58 
    59 void MyGlWindow::installShaders()
    60 {
    61     //...
    62 }
    63 
    64 void MyGlWindow::initializeGL()
    65 {
    66     //...
    67 }
    68 
    69 void MyGlWindow::paintGL()
    70 {
    71 
    72     glm::mat4 projectionMatrix = glm::perspective(30.0f, ((float)width()) / height(), 0.1f, 10.0f);
    73 
    74     glm::mat4 fullTransforms[] =
    75     {
    76         projectionMatrix * camera.getWorldToViewMatrix() * glm::translate(glm::vec3(0.0f, 0.0f, -3.0f)) * glm::rotate(54.0f,glm::vec3(1.0f, 0.0f, 0.0f)),
    77         projectionMatrix * camera.getWorldToViewMatrix() * glm::translate(glm::vec3(2.0f, 0.0f, -4.0f)) * glm::rotate(126.0f, glm::vec3(0.0f, 1.0f, 0.0f))
    78     };
    79 
    80     glBufferData(GL_ARRAY_BUFFER, sizeof(fullTransforms), fullTransforms, GL_DYNAMIC_DRAW);
    81 
    82     glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
    83     glViewport(0, 0, width(), height());
    84     glDrawElementsInstanced(GL_TRIANGLES, numIndices, GL_UNSIGNED_SHORT, 0, 2);
    85 }
    86 
    87 
    88 std::string MyGlWindow::ReadShaderCode(const char* fileName)
    89 {
    90     //...
    91 }
    92 
    93 void MyGlWindow::mouseMoveEvent(QMouseEvent * e)
    94 {
    95     camera.mouseUpdate(glm::vec2(e->x(), e->y()));
    96     repaint();
    97 }

    72-80行是从sendDataToOpenGL中复制过来的,因为现在需要持续性修改和更新,所以要移动到paintGL函数中。

     我们能看到在fullTransforms数组中,两个元素中都在中间增加了camera.getWorldToViewMatrix()。

    80行和44行内容基本一样,除了44行没有提供任何数据,而80行提供了实际的数据。这里和之前将的glBufferSubData()的用法是一样的,之所以没有用glBufferSubData,原因是它的作用是改变buffer中的一部分数据,而在这里我们全部都要改变,所以就使用glBufferData是很合适的。

    另外44行和80行的函数最后一个参数都改成了GL_DYANMIC_DRAW,原因是绘制的内容要频繁更新。

    93行开始实现了mouseMoveEvent函数,该函数是声明在QWidget中的一个虚函数,只要鼠标按下并移动,就会触发这个函数,鼠标事件以参数的形式传入函数。在这里我们首先更新world to view矩阵,然后调用repaint()函数重新绘制画面。

    最终的结果就是在画布上按下鼠标并左右移动的时候,我们的视角也左右旋转了。

    加入上下旋转

    根据左右旋转的方法,可以很方便的加入上下旋转,上下旋转和左右旋转的唯一区别是旋转轴不一样:左右旋转是绕“上”方向轴旋转,这个“上”很容易提供,就是世界坐标的上。而上下旋转是绕相机局部坐标的x轴旋转,这个坐标轴我们无法使用世界坐标,因为它不是固定的,随着相机在其他轴向上的旋转,这个轴会发生变化。

    这里我们可以使用一个数学方法来计算这个轴,我们的“上”方向是固定的,而“前”方向也是计算出来了的(就是viewDirection),这样我们可以使用“向量的叉乘”来得到一个垂直于“上和前方向构成的平面”的向量,也就是“朝向相机左或者右方向的向量”,也就是相机的局部坐标的x方向或者-x方向。

    叉乘可以使用glm::cross()函数来实现。因此,上下旋转的矩阵可以这样构建:

    glm::vec3 pitchAxis = glm::cross(viewDirection, UP);
    glm::vec3 pitchMatrix = glm::rotate(mouseDelta.y * 0.01f, pitchAxis)

    第一行定义的是旋转轴,第二行就是绕这个旋转轴进行的变化,注意这里用的是mouseDelta的y分量。

    我们看一下最终修改的代码:

    Camera.cpp的 mouseUpdate函数:

     1 void Camera::mouseUpdate(const glm::vec2 & newMousePosition)
     2 {
     3     glm::vec2 mouseDelta = newMousePosition - oldMousePosition;
     4     if (glm::length(mouseDelta) > 50.0f)
     5     {
     6         oldMousePosition = newMousePosition;
     7         return;
     8     }
     9         
    10     glm::vec3 pitchAxis = glm::cross(viewDirection, UP);
    11 
    12     viewDirection = glm::mat3(
    13         glm::rotate(mouseDelta.x * 0.01f, UP) * 
    14         glm::rotate(mouseDelta.y * 0.01f, pitchAxis)
    15     ) * viewDirection;
    16     
    17 
    18     oldMousePosition = newMousePosition;
    19 }

    注意我们把绕两个轴向旋转的变换结合到一个表达式里了。

    最终效果就是相机可以在上下左右方向自由移动了。

    潜在编译错误:

    如果编译时出现"cannot open source file "QObject" 等错误提示,需要定位到#include <QObject>等相关语句,在路径前增加QtCore路径:

    例如:#include <QtCore/QObject>

    这段代码出现在qt的头文件中qmouseevent.h和qkeyevent.h中。尚不清楚这是源码中的错误还是我的设置问题。

    后面章节出现类似问题的话解决方法一样。

  • 相关阅读:
    发布AI芯片昆仑和百度大脑3.0、L4自动驾驶巴士量产下线,这是百度All in AI一年后的最新答卷...
    Redis 使用多个数据库及密码配置
    Redis 使用多个数据库及密码配置
    Redis 使用多个数据库及密码配置
    [Winform]DataGridView列自适应宽度
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    xgqfrms™, xgqfrms® : xgqfrms's offical website of GitHub!
    运筹学那些事,专科学生学习运筹学之图论方法,No.7
    SpringMVC的HttpMessageConverter
    @ResponseStatus的作用
  • 原文地址:https://www.cnblogs.com/AnKen/p/8400067.html
Copyright © 2011-2022 走看看