前言:上几张我们做好了一个立方体模型(三角形图元)和第一视角,但是我们会发现内容单一,且绘制不成模式(没有系统的管理绘制步骤),效率也不是特别高(采用vbo的方式)。可能我们还不明白vbo的机制。所以我们无法绘制多个不同模型。
原因:
1.我们不知道是采用每个模型对象都对应一个shader程序,还是所有模型采用同一个shader程序。
2.vao和vbo的机制是怎样的,我们也不清楚。
分析:
1.很显然大部分的模型采用同一个shader程序效率会更高,因为在同一场景shader程序中所需要的矩阵基本都是一致的,比如在同一种光源下的阴影算法会基本相同,模型视口矩阵也基本相同。只有在部份特殊需求的情况才会单独给模型建立shader程序。比如一些特殊地点需要特殊的光照算法。
2.虽然我们明白了要使用同一个shader程序,但是这又会引入困惑,我们需要在每次绘制时绑定不同的顶点属性。因为是同一个shader程序,所以设置的时候如果不了解vbo的机制,就容易导致错误绘制,很可能模型会只认最后一次设置的顶点属性。
3.如果明白vbo机制和会使用的话,我们也会发现每一次的绘制都会对当前上下文(当前的vbo缓冲区)vbo进行绑定、设置顶点属性才能进行绘制。因此这样会降低效率,这时vao就能解决这种情况,vao会记录顶点属性的设置,能很轻松的管理顶点属性,绘制时就不需要管vbo了,统一使用vao来进行绘制。
注意:因为传统的OpenGL的名词(上下文、状态机)可能很难帮助我们理解OpenGL的一些知识,我会尽量把语言通俗化。
vbo:
首先我们可以把红线当成吸管或者通讯线路。它默认是断开的。在OpenGL原生代码中
....
glGenBuBuffers(1,vbo);//创建一个vbo对象,并分配缓冲区;
....
// 绑定VBO,设置VBO中的数据
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 设置顶点属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 2. 使用Shader程序
glUseProgram(shaderProgram);
// 3. 绘制
glDrawArrays(GL_QUADS, 0, 24);
可以看见glBindBuffer(GL_ARRAY_BUFFER, VBO);原文说的绑定,其实可以理解成告诉vbo缓冲区将有吸管插入进来,准备进行数据传输。glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);指的是当前将吸管1插入分配的缓冲区中,按指定的大小和数据是否会改变的形式把数据吐到vbo缓冲区中。glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);将吸管2插入到着色器程序中告诉他我要用这种方式(每次取3个值,自动偏移1个位置)去取顶点数据。glEnableVertexAttribArray(0); 把吸管3从着色器插入到vbo缓冲区,在调用draw函数时一个点一个点的把数据吸到着色器中。大家可以观察到这套步骤时一个流程,中途不能打断,如果打断就会出现绘制错误,导致着色器不知道去哪个缓冲区吸数据,他就会默认从最后一次设置去吸取数据。当然在同一个函数中不会出现被打断的情况。
出错原因:
我们使用同一个shader程序,在同一个模型对象中平时会写一个init()函数和一个draw函数。这时代码会被分配到不同函数中,所以每个模型的吸管3会一直指向最后一次的vbo缓冲区,为了避免出错,我们会在init调用一次glBindBuffer把数据分配给vbo缓冲区,在绘制时调用glBindBuffer告诉缓冲区我们当前吸管3重新连接,这是就能重新插入到当前使用的vbo缓冲区。所以我们每次绘制都会调用glBindBuffer和重新设置顶点属性。这样会影响效率所以vao就诞生了。
vao:
vao不需要画图了,因为他就是一个记录和管理功能(把吸管1,2,3的步骤都记录下来),作为桥梁我们就只需要使用vao来绘制。这样就可以把之前的vbo的操作全部写到init函数中,在draw中使用vao就可以了,这样也能避免vbo被分开出错和效率底的问题。
vao原生代码:
void init(){
//创建VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);//创建vao
glBindVertexArray(VAO);//启动vao
glBindBuffer(GL_ARRAY_BUFFER, VBO); //设置了VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//设置VBO中的数据
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); //设置顶点属性(索引为0的属性,与shader中的内容有交互)
glEnableVertexAttribArray(0); //设置开启顶点属性(索引为0的属性,与shader中的内容有交互)
glBindVertexArray(0); //关闭vao,vao操作完成后要关闭,避免后面的代码影响它的设置
}
void draw(){
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //启动vao,这时我们就开始使用它储存的属性了
glDrawArrays(GL_QUADS, 0, 24);
glBindVertexArray(0); //关闭vao
}
好了分析完毕,上qt代码:
我们建立一个skybox类绘制一个以正方形为图元的立方体,为后面的天空盒做准备。实际前几张的绘制代码都要这样改(以类和对象的形式)。
.h
#pragma once
#include
#include "Camera.h"
class SkyBox : protected QOpenGLFunctions_4_3_Core
{
public:
SkyBox();
~SkyBox();
void init(QOpenGLShaderProgram* shaderProgram, int width, int height);
void draw(Camera camera);
private:
GLuint vPosition, uvlo;
float cameraX, cameraY, cameraZ;
float cubeLocX, cubeLocY, cubeLocZ;
QOpenGLShaderProgram* shaderProgram;
QOpenGLTexture* mTexture;
GLuint mvLoc, projLoc, imgTexture;
QMatrix4x4 pMat, mMat, mvMat, lookat;
QOpenGLVertexArrayObject vao;//对应vao
QOpenGLBuffer vbo, uvVbo;//对应vbo
void setupVertices();
};
.cpp
#include "stdafx.h"
#include "SkyBox.h"
SkyBox::SkyBox() {
}
SkyBox::~SkyBox() {
delete mTexture;
vbo.destroy();
uvVbo.destroy();
}
void SkyBox::init(QOpenGLShaderProgram* shaderProgram, int width, int height) {
initializeOpenGLFunctions();
this->shaderProgram = shaderProgram;
cameraX = 0.0f; cameraY = 0.0f; cameraZ = 0.0f;
cubeLocX = 0.0f; cubeLocY = 0.0f; cubeLocZ = 0.0f;
setupVertices();
mvLoc = shaderProgram->uniformLocation("mv_matrix");
projLoc = shaderProgram->uniformLocation("proj_matrix");//获取程序的统一变量mv,投影矩阵
//构建透视矩阵
float aspect = (float)width / (float)height;
pMat.perspective(60.0f, aspect, 0.1f, 1000.f);
//构建视图矩阵
QMatrix4x4 vMat;
vMat.lookAt(QVector3D(cameraX, cameraY, cameraZ), QVector3D(0.0f, 0.0f, 0.0f), QVector3D(0.0f, 1.0f, 0.0f));
mMat.translate(cubeLocX, cubeLocY, cubeLocZ);
mvMat = vMat * mMat;
//将mv矩阵发送给对应的统一变量
shaderProgram->setUniformValue(mvLoc, mvMat);
shaderProgram->setUniformValue(projLoc, pMat);
mTexture = new QOpenGLTexture(QImage(":/skyBox/Resources/skyBox/front.bmp").mirrored());
mTexture->setMinificationFilter(QOpenGLTexture::Nearest);
mTexture->setMagnificationFilter(QOpenGLTexture::Linear);
}
void SkyBox::setupVertices() {
GLfloat vertexPositions[72] = {
-0.5,-0.5,-0.5,0.5,-0.5,-0.5,0.5,0.5,-0.5,-0.5,0.5,-0.5,
0.5,-0.5,0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5,0.5, 0.5, 0.5,
-0.5,-0.5,0.5,-0.5,-0.5,-0.5,-0.5,0.5, -0.5,-0.5, 0.5,0.5,
0.5,-0.5,-0.5,0.5,-0.5,0.5,0.5, 0.5,0.5,0.5, 0.5, -0.5,
-0.5,0.5,-0.5,0.5,0.5,-0.5,0.5, 0.5,0.5,-0.5, 0.5, 0.5,
-0.5,-0.5f, 0.5,0.5,-0.5, 0.5,0.5,-0.5,-0.5,-0.5,-0.5,-0.5
};
GLfloat uv[48] = {
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f,0.0f,1.0f,
};
vao.create();
vbo.create();
uvVbo.create();
vao.bind();
vbo.bind();
vbo.setUsagePattern(QOpenGLBuffer::StaticDraw);
vbo.allocate(vertexPositions, sizeof(vertexPositions));
vPosition = shaderProgram->attributeLocation("vPosition");
shaderProgram->setAttributeBuffer(vPosition, GL_FLOAT, 0, 3, 0);
uvVbo.bind();
uvVbo.allocate(uv, sizeof(uv));
glEnableVertexAttribArray(vPosition);
uvlo = shaderProgram->attributeLocation("inuv");
shaderProgram->setAttributeBuffer(uvlo, GL_FLOAT, 0, 2, 0);
glEnableVertexAttribArray(uvlo);
vao.release();
}
void SkyBox::draw(Camera camera) {
mTexture->bind(mTexture->textureId());
shaderProgram->setUniformValue("imgTexture", mTexture->textureId());
QMatrix4x4 v;
v.lookAt(QVector3D(camera.location.x, camera.location.y, camera.location.z),
QVector3D(camera.viewPoint.x, camera.viewPoint.y, camera.viewPoint.z),
QVector3D(camera.worldY.x, camera.worldY.y, camera.worldY.z));
mvMat = v * mMat;
shaderProgram->setUniformValue(mvLoc, mvMat);
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
vao.bind();
glDrawArrays(GL_QUADS, 0, 24);
vao.release();
mTexture->release();
}
OpenglWegdit
#include "stdafx.h"
#include "MyOpenglWegdit.h"
MyShader shader;
SkyBox sky;
Cube cube;
MyOpenglWegdit::MyOpenglWegdit(QWidget*parent)
: QOpenGLWidget(parent)
{
this->grabKeyboard();
}
MyOpenglWegdit::~MyOpenglWegdit()
{
}
void MyOpenglWegdit::initializeGL()
{
initializeOpenGLFunctions();
shader.creatShader(":/QtGuiApplication1/Resources/config/shader.vs", ":/QtGuiApplication1/Resources/config/shader.fs");
glShadeModel(GL_SMOOTH);//设置阴影平滑模式
glClearColor(0.98, 0.625, 0.12, 0.5);//改变窗口的背景颜色
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//进行透视校正
sky.init(shader.getShader(), width(), height());
cube.init(shader.getShader(), width(), height());
}
void MyOpenglWegdit::resizeGL()
{
glViewport(0, 0, width(), height());
}
void MyOpenglWegdit::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
updataDetlaTime();
sky.draw(camera);
glEnable(GL_DEPTH_TEST);
glDepthFunc(GL_LEQUAL);
cube.draw(camera);
//glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection);
}
效果图:
目录
VSC++2019+QT+OpenGL
QT+OpenGL一之绘制立方体(三角形图元)
QT+OpenGL二之纹理贴图
QT+OpenGL三之矩阵简解
QT+OpenGL四之相机的移动和旋转
QT+OpenGL五之绘制不同的模型(vao,vbo机制)
QT+OpenGL六之天空盒
QT+OpenGL七之使用EBO
QT+OPenGL八之模型准备
QT+OPenGL九之模型解码
QT+OPenGL十之光照模型
QT+OPenGL十一之漫反射和镜面反射贴图
QT+OPenGL十二之定向光
QT+OPenGL十三之真正的点光源和聚光灯
QT+OPenGL十四之多光源混合的问题
QT+OPenGL十五之深度缓冲区
QT+OPenGL十六之模板缓冲区
QT+OPenGL十七帧缓冲区(离屏渲染)
QT+OPenGL十八抗锯齿
QT+OPenGL十九镜面反射效率调整
QT+OPenGL二十Gamma校正