基于Qt的OpenGL编程(3.x以上GLSL可编程管线版)---(八)简单框架

Vries的教程是我看过的最好的可编程管线OpenGL教程,没有之一,本文以Vries的2D游戏框架为基础,(链接https://learnopengl-cn.github.io/06%20In%20Practice/2D-Game/01%20Breakout/ ),通过改变视口函数与其他一些简单操作,将其扩展为3D OpenGL框架,方便在此框架基础上,简化OpenGL的参数赋值操作。

  • Tip1:为什么要写这个框架,因为单纯的对OpenGL进行shader生成,纹理生成,参数赋值,真的太麻烦了,故写一个管理   器对这些东西进行管理,简化了操作步骤,真的十分方便。接下来的教程基本都是在这个框架的基础上完成。
  • Tip2:之前的3D OpenGL框架因为过于追求与Vries的框架相似,有很多冗余的地方,故特此从写,老版本的源代码已放到本文最下。
  • Tip3:该框架的详细思想细节,强烈建议先看Vries的教程,链接如上,本文只描述大致思想细节。

程序源代码链接:https://pan.baidu.com/s/1SzpHH9Bx-36TwVlAuaZhkw 提取码:9xu5

编译环境:Qt5.9.4

编译器:Desktop Qt5.9.4 MSVC2017 64bit

IDE:QtCreator

一,程序简介

图1 界面
图2 线框模式

 

这是一个简单的3D框架,具有自由移动视角,OpenGL与GUI交互的基本功能。已内嵌了三个常用的部件进行渲染,一个正方体,一个平面与一个坐标系。

操作:使用WASD按键进行视角的移动,按住鼠标左键进行视角的拖拽,鼠标滚轮进行视角的放大与缩小。

项目管理如下:

图3 项目管理

 camera.h: 摄像机类,负责视角的转换 

 littlethings.h: 立方体,平面,坐标等一些基础部件

 mainwindow.h: 主窗口,负责OpenGL界面与GUI的交互,每10ms刷新一次OpenGL窗口

 oglmanager.h: 继承QOpenGLWidget,OpenGL窗口绘制类,shader,texture的加载与参数设置都在这里运行

 resourcemanager.h: 一个静态类,管理shader, texture

 shader.h: 为方便resourcemanager类管理,shader单独成类

 texture2d.h: 同理,texture单独成类

二,如何使用框架

这是OGLManager.h头文件内容,这个类继承了QOpenGLWidget,负责关于OpenGL的一切调用。


#include <QOpenGLWidget>
#include <QKeyEvent>
#include <QOpenGLFunctions_3_3_Core>
#include <QTime>
#include "camera.h"
#include <QMouseEvent>

class OGLManager : public QOpenGLWidget{
public:
  explicit OGLManager(QWidget *parent = 0);
  ~OGLManager();
  void handleKeyPressEvent(QKeyEvent *event);   //键盘按下事件
  void handleKeyReleaseEvent(QKeyEvent *event);  //键盘释放事件

  GLboolean keys[1024];//获取键盘按键,实现多键触控
  GLboolean isOpenLighting;
  GLboolean isLineMode;
protected:
  void mouseMoveEvent(QMouseEvent *event);
  void wheelEvent(QWheelEvent *event) override;    //滚轮事件
  void mousePressEvent(QMouseEvent *event) override;  //鼠标按下事件
  void mouseReleaseEvent(QMouseEvent *event) override; //鼠标释放事件
  virtual void initializeGL();
  virtual void resizeGL(int w, int h);
  virtual void paintGL();
private:
  void processInput(GLfloat dt);//摄像机键盘处理函数,
  void updateGL();//opengl 更新函数

  QOpenGLFunctions_3_3_Core *core;

  GLboolean isFirstMouse;
  GLboolean isLeftMousePress;
  GLint lastX;
  GLint lastY;

  QTime time;
  GLfloat deltaTime;
  GLfloat lastFrame;//上一帧

  Camera *camera;

};


关于QOpenGLWidget:

  这个Qt下的OpenGL绘制框架,有三个protected权限的成员函数:

在initializeGL()里做参数的初始化,paintGL()里进行物体的绘制,resizeGL(int w, int h)里进行glViewport()视口的选择。

所以据此,我们在


initializeGL()里进行shader的加载与赋值:ResourceManager是一个静态函数类,负责shader与texture的加载与赋值。

比如,只需要一行代码,ResourceManager::loadShader("shader名称", "顶点着色器路径", "片段着色器路径", "几何着色器路径"= null)即可完成一个着色器的加载。加载纹理同理。

..............
   
/************ 载入shader ***********/
  ResourceManager::loadShader("coordinate", ":/shaders/res/shaders/coordinate.vert", ":/shaders/res/shaders/coordinate.frag");
  ResourceManager::loadShader("cube", ":/shaders/res/shaders/cube.vert", ":/shaders/res/shaders/cube.frag");
  ResourceManager::loadShader("plane", ":/shaders/res/shaders/plane.vert", ":/shaders/res/shaders/plane.frag");

  /************ 载入Texture ***********/
  ResourceManager::loadTexture("brickwall", ":/textures/res/textures/brickwall.jpg");
  ResourceManager::loadTexture("cementwall", ":/textures/res/textures/cementwall.jpg");


  /***********  cube shader参数 **************/
  QMatrix4x4 model;
  ResourceManager::getShader("cube").use().setMatrix4f("model", model);
  ResourceManager::getShader("cube").use().setInteger("ambientMap", 0);

..............

paintGL()里专门负责各个部件的绘制,不做过多累赘

void OGLManager::paintGL(){
  /*********** 计算两次帧数之间的时间间隔  ***************/
  GLfloat currentFrame = (GLfloat)time.elapsed()/100;
  deltaTime = currentFrame - lastFrame;
  lastFrame = currentFrame;

  this->processInput(deltaTime); //调用键盘响应操作
  this->updateGL();              //调用OpenGL更新函数

  /*********  绘制cube ************/
  ResourceManager::getShader("cube").use();
  core->glActiveTexture(GL_TEXTURE0);
  ResourceManager::getTexture("brickwall").bind();
  cube->draw(GL_TRUE);

  /*********  绘制plane ************/
  ResourceManager::getShader("plane").use();
  core->glActiveTexture(GL_TEXTURE0);
  ResourceManager::getTexture("cementwall").bind();
  plane->draw(GL_TRUE);

  /*********  绘制坐标系统 ************/
  ResourceManager::getShader("coordinate").use();
  coordinate->draw();

}

另定义updateGL()函数,负责着色器的一些更新操作,比如projection, view矩阵会跟随视角位置的改变而改变,放进更新函数里进行管理会很方便。

void OGLManager::updateGL(){
  if(this->isLineMode)
    core->glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  else
    core->glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

  QMatrix4x4 projection, view;
  projection.perspective(camera->zoom, (GLfloat)width()/(GLfloat)height(), 0.1f, 2000.f);
  view = camera->getViewMatrix();

  ResourceManager::getShader("cube").use().setMatrix4f("projection", projection);
  ResourceManager::getShader("cube").use().setMatrix4f("view", view);

  ResourceManager::getShader("plane").use().setMatrix4f("projection", projection);
  ResourceManager::getShader("plane").use().setMatrix4f("view", view);

  ResourceManager::getShader("coordinate").use().setMatrix4f("projection", projection);
  ResourceManager::getShader("coordinate").use().setMatrix4f("view", view);
}

三,小部件

关于littlethings.h这个类,这个类存储了一些小部件,为这些小部件单独建类又好麻烦,故放在一个类里进行管理。

目前这些小部件有:

  1. Cube立方体
  2. Plane平面体
  3. Coordinate坐标系

建立Cube与Plane类时,我留了个心眼,因为后续的内容经常需要各种类型的立方体或平面体,或有无纹理坐标,或有无法线,针对不同要求重写VBO是件很麻烦的事情。

故我将纹理坐标,法线用bool值进行管理,在draw函数中选择是否将对应值载入VBO。

以Plane类举例:

 Plane头文件

/*
  很简单的操作,
  init()函数 初始化数据
  draw()往VBO中绑定数据 并绘制
*/
class Plane{
public:
  Plane();
  ~Plane();
  void init();
  void draw(GLboolean isTexture = GL_FALSE,GLboolean isNormal = GL_FALSE);
private:
  QOpenGLFunctions_3_3_Core *core;
  GLuint VBO;
};

 Plane源文件:

 void init()  函数负责数据的初始化与绑定进buffer

 void draw(bool, bool)  根据参数决定是否将纹理坐标与法线信息绑定至指定着色器

Plane::Plane(): VBO(0){
  core = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_3_Core>();
}

Plane::~Plane(){
  if(VBO != 0)
    core->glDeleteBuffers(1, &VBO);
}


void Plane::init(){
  float vertices[] = {
    //!!!!!这里有一个很重要的知识点,当纹理的取值范围大于1时,且纹理为repeat时,show的方式为repeat.比如纹理坐标都是2,应映射效果为2*2共4个纹理
    // positions        // textures        // normals
    -0.5f,  0.0f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.0f, -0.5f,  1.0f,  1.0f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.0f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
     0.5f,  0.0f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.0f,  0.5f,  0.0f,  0.0f,  0.0f,  1.0f,  0.0f,
    -0.5f,  0.0f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f,  0.0f,
  };
  core->glGenBuffers(1, &VBO);

  core->glBindBuffer(GL_ARRAY_BUFFER, VBO);
  core->glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

}

void Plane::draw(GLboolean isTexture, GLboolean isNormal){
  core->glBindBuffer(GL_ARRAY_BUFFER, VBO);
  core->glEnableVertexAttribArray(0);
  core->glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)0);
  if(isTexture){
    core->glEnableVertexAttribArray(1);
    core->glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(3*sizeof(float)));
  }
  if(isNormal){
    core->glEnableVertexAttribArray(2);
    core->glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(5*sizeof(float)));
  }

  core->glDrawArrays(GL_TRIANGLES, 0, 6);
}

/***********************************  以下为旧版本  *************************************************/


Qt开发平台:5.8.0

编译器:Desktop Qt 5.8.0 MSVC2015_64bit 

程序源代码链接: https://pan.baidu.com/s/196FAnQylehc9XHOUR8MLIg

 

主体内容仍是上一节的十个箱子:

代码结构所示,因其源文件过多,将项目文件上传至百度云,

 

项目结构:

 

以下是一些类的意义:

类desktop: 等于Vries中的main.cpp,作为main函数使用

类oglmanager:等于Vries中的类Game,作为管理器使用

其余一样

 

评论 19
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值