前言:上幾張我們做好了一個(gè)立方體模型(三角形圖元)和第一視角,但是我們會(huì)發(fā)現(xiàn)內(nèi)容單一,且繪制不成模式(沒(méi)有系統(tǒng)的管理繪制步驟),效率也不是特別高(采用vbo的方式)??赡芪覀冞€不明白vbo的機(jī)制。所以我們無(wú)法繪制多個(gè)不同模型。
原因:
1.我們不知道是采用每個(gè)模型對(duì)象都對(duì)應(yīng)一個(gè)shader程序,還是所有模型采用同一個(gè)shader程序。
2.vao和vbo的機(jī)制是怎樣的,我們也不清楚。
分析:
1.很顯然大部分的模型采用同一個(gè)shader程序效率會(huì)更高,因?yàn)樵谕粓?chǎng)景shader程序中所需要的矩陣基本都是一致的,比如在同一種光源下的陰影算法會(huì)基本相同,模型視口矩陣也基本相同。只有在部份特殊需求的情況才會(huì)單獨(dú)給模型建立shader程序。比如一些特殊地點(diǎn)需要特殊的光照算法。
2.雖然我們明白了要使用同一個(gè)shader程序,但是這又會(huì)引入困惑,我們需要在每次繪制時(shí)綁定不同的頂點(diǎn)屬性。因?yàn)槭峭粋€(gè)shader程序,所以設(shè)置的時(shí)候如果不了解vbo的機(jī)制,就容易導(dǎo)致錯(cuò)誤繪制,很可能模型會(huì)只認(rèn)最后一次設(shè)置的頂點(diǎn)屬性。
3.如果明白vbo機(jī)制和會(huì)使用的話,我們也會(huì)發(fā)現(xiàn)每一次的繪制都會(huì)對(duì)當(dāng)前上下文(當(dāng)前的vbo緩沖區(qū))vbo進(jìn)行綁定、設(shè)置頂點(diǎn)屬性才能進(jìn)行繪制。因此這樣會(huì)降低效率,這時(shí)vao就能解決這種情況,vao會(huì)記錄頂點(diǎn)屬性的設(shè)置,能很輕松的管理頂點(diǎn)屬性,繪制時(shí)就不需要管vbo了,統(tǒng)一使用vao來(lái)進(jìn)行繪制。
注意:因?yàn)閭鹘y(tǒng)的OpenGL的名詞(上下文、狀態(tài)機(jī))可能很難幫助我們理解OpenGL的一些知識(shí),我會(huì)盡量把語(yǔ)言通俗化。
vbo:

首先我們可以把紅線當(dāng)成吸管或者通訊線路。它默認(rèn)是斷開的。在OpenGL原生代碼中
....
glGenBuBuffers(1,vbo);//創(chuàng)建一個(gè)vbo對(duì)象,并分配緩沖區(qū);
....
// 綁定VBO,設(shè)置VBO中的數(shù)據(jù)
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 設(shè)置頂點(diǎn)屬性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);
glEnableVertexAttribArray(0);
// 2. 使用Shader程序
glUseProgram(shaderProgram);
// 3. 繪制
glDrawArrays(GL_QUADS, 0, 24);
可以看見(jiàn)glBindBuffer(GL_ARRAY_BUFFER, VBO);原文說(shuō)的綁定,其實(shí)可以理解成告訴vbo緩沖區(qū)將有吸管插入進(jìn)來(lái),準(zhǔn)備進(jìn)行數(shù)據(jù)傳輸。glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);指的是當(dāng)前將吸管1插入分配的緩沖區(qū)中,按指定的大小和數(shù)據(jù)是否會(huì)改變的形式把數(shù)據(jù)吐到vbo緩沖區(qū)中。glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0);將吸管2插入到著色器程序中告訴他我要用這種方式(每次取3個(gè)值,自動(dòng)偏移1個(gè)位置)去取頂點(diǎn)數(shù)據(jù)。glEnableVertexAttribArray(0); 把吸管3從著色器插入到vbo緩沖區(qū),在調(diào)用draw函數(shù)時(shí)一個(gè)點(diǎn)一個(gè)點(diǎn)的把數(shù)據(jù)吸到著色器中。大家可以觀察到這套步驟時(shí)一個(gè)流程,中途不能打斷,如果打斷就會(huì)出現(xiàn)繪制錯(cuò)誤,導(dǎo)致著色器不知道去哪個(gè)緩沖區(qū)吸數(shù)據(jù),他就會(huì)默認(rèn)從最后一次設(shè)置去吸取數(shù)據(jù)。當(dāng)然在同一個(gè)函數(shù)中不會(huì)出現(xiàn)被打斷的情況。
出錯(cuò)原因:
我們使用同一個(gè)shader程序,在同一個(gè)模型對(duì)象中平時(shí)會(huì)寫一個(gè)init()函數(shù)和一個(gè)draw函數(shù)。這時(shí)代碼會(huì)被分配到不同函數(shù)中,所以每個(gè)模型的吸管3會(huì)一直指向最后一次的vbo緩沖區(qū),為了避免出錯(cuò),我們會(huì)在init調(diào)用一次glBindBuffer把數(shù)據(jù)分配給vbo緩沖區(qū),在繪制時(shí)調(diào)用glBindBuffer告訴緩沖區(qū)我們當(dāng)前吸管3重新連接,這是就能重新插入到當(dāng)前使用的vbo緩沖區(qū)。所以我們每次繪制都會(huì)調(diào)用glBindBuffer和重新設(shè)置頂點(diǎn)屬性。這樣會(huì)影響效率所以vao就誕生了。
vao:
vao不需要畫圖了,因?yàn)樗褪且粋€(gè)記錄和管理功能(把吸管1,2,3的步驟都記錄下來(lái)),作為橋梁我們就只需要使用vao來(lái)繪制。這樣就可以把之前的vbo的操作全部寫到init函數(shù)中,在draw中使用vao就可以了,這樣也能避免vbo被分開出錯(cuò)和效率底的問(wèn)題。
vao原生代碼:
void init(){
//創(chuàng)建VAO
GLuint VAO;
glGenVertexArrays(1, &VAO);//創(chuàng)建vao
glBindVertexArray(VAO);//啟動(dòng)vao
glBindBuffer(GL_ARRAY_BUFFER, VBO); //設(shè)置了VBO
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//設(shè)置VBO中的數(shù)據(jù)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), (GLvoid*)0); //設(shè)置頂點(diǎn)屬性(索引為0的屬性,與shader中的內(nèi)容有交互)
glEnableVertexAttribArray(0); //設(shè)置開啟頂點(diǎn)屬性(索引為0的屬性,與shader中的內(nèi)容有交互)
glBindVertexArray(0); //關(guān)閉vao,vao操作完成后要關(guān)閉,避免后面的代碼影響它的設(shè)置
}
void draw(){
glUseProgram(shaderProgram);
glBindVertexArray(VAO); //啟動(dòng)vao,這時(shí)我們就開始使用它儲(chǔ)存的屬性了
glDrawArrays(GL_QUADS, 0, 24);
glBindVertexArray(0); //關(guān)閉vao
}
好了分析完畢,上qt代碼:
我們建立一個(gè)skybox類繪制一個(gè)以正方形為圖元的立方體,為后面的天空盒做準(zhǔn)備。實(shí)際前幾張的繪制代碼都要這樣改(以類和對(duì)象的形式)。
.h
#pragma once
#include <qopenglfunctions_4_3_core.h>
#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;//對(duì)應(yīng)vao
QOpenGLBuffer vbo, uvVbo;//對(duì)應(yīng)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");//獲取程序的統(tǒng)一變量mv,投影矩陣
//構(gòu)建透視矩陣
float aspect = (float)width / (float)height;
pMat.perspective(60.0f, aspect, 0.1f, 1000.f);
//構(gòu)建視圖矩陣
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矩陣發(fā)送給對(duì)應(yīng)的統(tǒng)一變量
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);//設(shè)置陰影平滑模式
glClearColor(0.98, 0.625, 0.12, 0.5);//改變窗口的背景顏色
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);//進(jìn)行透視校正
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三之矩陣簡(jiǎn)解
QT+OpenGL四之相機(jī)的移動(dòng)和旋轉(zhuǎn)
QT+OpenGL五之繪制不同的模型(vao,vbo機(jī)制)
QT+OpenGL六之天空盒
QT+OpenGL七之使用EBO
QT+OPenGL八之模型準(zhǔn)備
QT+OPenGL九之模型解碼
QT+OPenGL十之光照模型
QT+OPenGL十一之漫反射和鏡面反射貼圖
QT+OPenGL十二之定向光
QT+OPenGL十三之真正的點(diǎn)光源和聚光燈
QT+OPenGL十四之多光源混合的問(wèn)題
QT+OPenGL十五之深度緩沖區(qū)
QT+OPenGL十六之模板緩沖區(qū)
QT+OPenGL十七幀緩沖區(qū)(離屏渲染)
QT+OPenGL十八抗鋸齒
QT+OPenGL十九鏡面反射效率調(diào)整
QT+OPenGL二十Gamma校正