鐘表玻璃東莞網(wǎng)站建設寧波seo網(wǎng)絡推廣軟件系統(tǒng)
文章目錄
- QT+ OpenGL
- QOpenGLWidget:不需要GLFW
- QOpenGLFunction_X_X_Core:不需要GLAD
- 你好,三角形
- 頂點輸入
- 頂點著色器
- 片段著色器
- 鏈接著色器
- 本節(jié)代碼
- 元素緩沖對象EBO
- QT交互
- GLSL
- GLSL支持的類型
- 輸入輸出
- Uniform
- 紋理
- 紋理單元
- 紋理環(huán)繞
- 紋理過濾
- 多級漸遠紋理
QT+ OpenGL
本篇完整工程見gitee:QTOpenGL
對應點的tag,由turbolove提供技術支持,您可以關注博主或者私信博主。
什么是opengl
open graphics library 他是一個由Khronos組織制定并且維護的規(guī)范
opengl核心是一個c庫,同時也支持多種語言的派生
核心模式(core-profile):
- 也叫可編程管線,提供了更多的靈活性,更高的效率,更重要的是可以深入的理解圖形編程。
立即渲染模式(Immediate mode)
- 早期的OpenGL使用的模式(也就是固定渲染管線)
- OpenGL的大多數(shù)功能都被庫隱藏起來,容易使用和理解,但是效率低下
- 開發(fā)者很少能控制OpenGL如何進行計算
- 因此從OpenGL3.2開始推出核心模式
狀態(tài)機(state machine)
- OpenGL是一個聚到的狀態(tài)機 - 描述如何操作的所有變量的大集合
- OpenGL的狀態(tài)通常被稱為上下文Context
- 狀態(tài)設置函數(shù)(State-changing Function)
- 狀態(tài)應用函數(shù)(State-using Function)
對象(Object)
- 一個對象是指一些選項的集合,代表OpenGL狀態(tài)的一個子集
- 當前狀態(tài)只有一份,如果每次顯示不同的效果,都重新配置會很麻煩
- 我們需要使用一些小助理(對象),幫忙記錄某些狀態(tài)信息,以便復用
// 創(chuàng)建對象
GLunit objId = 0;
glGenObject(1, &objId);
// 綁定對象到上下文
glBindObject(GL_WINDOW_TARGET, objId);
// 設置GL_WINDOW_TARGET對象的一些選項
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_WIDTH, 800);
glSetObjectOption(GL_WINDOW_TARGET, GL_OPTION_WINDOW_HEIGHT, 600);
// 將上下文的GL_WINDOW_TARGET對象設置成默認值
glBindObject(GL_WINDOW_TARGET, 0);
// 一旦我們重新綁定這個對象到GL_WINDOW_TARGET位置,這些選項就會重新生效
QOpenGLWidget:不需要GLFW
QOpenGLWidget提供了三個便捷的虛擬函數(shù),可以沖在,用來實現(xiàn)典型的opengl任務
- paintGL:渲染opengl場景,widget需要更新時候調(diào)用
- resizeGL:設置opengl視口,投影等,widget調(diào)整大小(或者首次顯示)時候調(diào)用
- initializeGL:設置opengl資源和狀態(tài),第一次調(diào)用resizeGL()或者paintGL()之前調(diào)用一次
如果需要從paintGL()意外的位置觸發(fā)重新繪制(典型示例是使用計時器設置場景動畫),則應該調(diào)用widget的update()函數(shù)來安排更新。
調(diào)用paintGL()、resizeGL()、initializeGL()時,widget的opengl呈現(xiàn)上下文變?yōu)楫斍?。如果需要從其他位?#xff08;例如在widget的構(gòu)造函數(shù)或者自己的繪制函數(shù)中)調(diào)用openglAPI函數(shù),則必須首先調(diào)用makeCurrent()。
QOpenGLFunction_X_X_Core:不需要GLAD
QOpenGLFunction_X_X_Core提供OpenGL x.x版本核心模式的所有功能,是對OpenGL函數(shù)的封裝
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLFunctions_4_5_Core>class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:explicit TurboOpenGLWidget(QWidget *parent = 0);protected:void initializeGL() override;void paintGL() override;void resizeGL(int w, int h) override;private:};#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include "turboopenglwidget.h"TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();QOpenGLWidget::initializeGL();
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);QOpenGLWidget::paintGL();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{QOpenGLWidget::resizeGL(w, h);
}
你好,三角形
float vertices[] =
{-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f,
};
標準化設備坐標:
訂單著色器中處理過后,應該就是標準化設備坐標,x、y、z的值在-1.0到1.0的一小段空間(立方體)。落在范圍外的坐標都會被裁減。
頂點輸入
- 他會在GPU上創(chuàng)建內(nèi)存,用于存儲我們的頂點數(shù)據(jù)
- 通過點緩沖對象(vertex buffer object , VBO)管理
- 頂點緩沖對象的緩沖類型是GL_ARRAY_BUFFER
- 通過點緩沖對象(vertex buffer object , VBO)管理
- 配置OpenGL如何解釋這些內(nèi)存
- 通過頂點數(shù)組對象(vertex array object , VAO)管理
數(shù)組力的每一個項都對應一個屬性的解析。
OpenGL允許我們同時綁定多個緩沖,只要他們是不同的緩沖類型(每個緩沖類型類似于前面說的子集,每個VBO是一個小助理)。
VAO并不保存實際數(shù)據(jù),而是放頂點結(jié)構(gòu)定義。
// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);// 綁定VAO和VBO對象
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲
// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);
// 開啟VAO管理的第一個屬性的值
glEnableVertexAttribArray(0);// 釋放VAO和VBO對象
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glBufferData
是一個專門用來把用戶定義的數(shù)據(jù)復制到當前綁定緩沖的函數(shù)。
-
第一個參數(shù)是目標緩沖的類型:頂點緩沖對象當前綁定到GL_ARRAY_BUFFER目標上。
-
第二個參數(shù)指定傳輸數(shù)據(jù)的大小(以字節(jié)為單位);用一個簡單的
sizeof
計算出頂點數(shù)據(jù)大小就行。 -
第三個參數(shù)是我們希望發(fā)送的實際數(shù)據(jù)。
-
第四個參數(shù)指定了我們希望顯卡如何管理給定的數(shù)據(jù)。它有三種形式:
-
GL_STATIC_DRAW :數(shù)據(jù)不會或幾乎不會改變。
-
GL_DYNAMIC_DRAW:數(shù)據(jù)會被改變很多。
-
GL_STREAM_DRAW :數(shù)據(jù)每次繪制時都會改變。
-
glVertexAttribPointer
- 第一個參數(shù)指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用
layout(location = 0)
定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0
。因為我們希望把數(shù)據(jù)傳遞到這一個頂點屬性中,所以這里我們傳入0
。 - 第二個參數(shù)指定頂點屬性的大小。頂點屬性是一個
vec3
,它由3個值組成,所以大小是3。 - 第三個參數(shù)指定數(shù)據(jù)的類型,這里是GL_FLOAT(GLSL中
vec*
都是由浮點數(shù)值組成的)。 - 下個參數(shù)定義我們是否希望數(shù)據(jù)被標準化(Normalize)。如果我們設置為GL_TRUE,所有數(shù)據(jù)都會被映射到0(對于有符號型signed數(shù)據(jù)是-1)到1之間。我們把它設置為GL_FALSE。
- 第五個參數(shù)叫做步長(Stride),它告訴我們在連續(xù)的頂點屬性組之間的間隔。由于下個組位置數(shù)據(jù)在3個
float
之后,我們把步長設置為3 * sizeof(float)
。要注意的是由于我們知道這個數(shù)組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數(shù)值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數(shù)的意思簡單說就是從這個屬性第二次出現(xiàn)的地方到整個數(shù)組0位置之間有多少字節(jié))。 - 最后一個參數(shù)的類型是
void*
,所以需要我們進行這個奇怪的強制類型轉(zhuǎn)換。它表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset)。由于位置數(shù)據(jù)在數(shù)組的開頭,所以這里是0。我們會在后面詳細解釋這個參數(shù)。
頂點著色器
#version 330 core
layout (location = 0) in vec3 aPos;void main()
{gl_Position = vec4(aPos, 1.0);
}
// 創(chuàng)建頂點著色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);
glCompileShader(vertexShader);
glShaderSource
- 第一個參數(shù)是函數(shù)把要編譯的著色器對象。
- 第二參數(shù)指定了傳遞的源碼字符串數(shù)量,這里只有一個。
- 第三個參數(shù)是頂點著色器真正的源碼,
- 第四個參數(shù)我們先設置為
NULL
。
片段著色器
#version 330 core
out vec4 FragColor;void main()
{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
// 創(chuàng)建頂片段著色器
unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);
glCompileShader(fragShader);
鏈接著色器
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragShader);
glLinkProgram(shaderProgram);glDeleteShader(vertexShader);
glDeleteShader(fragShader);
本節(jié)代碼
turboopenglwidget.cpp
#include "turboopenglwidget.h"float vertices[] =
{-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f,
};
const char *vertexShaderSource ="#version 330 core \n""layout (location = 0) in vec3 aPos;\n""\n""void main()\n""{\n""\tgl_Position = vec4(aPos, 1.0);\n""}";
const char *fragShaderSource ="#version 330 core\n""out vec4 FragColor;\n""\n""void main()\n""{\n"" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""} ";// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// 綁定VAO和VBO對象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(0);// 釋放VAO和VBO對象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);// 創(chuàng)建頂點著色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);glCompileShader(vertexShader);// 創(chuàng)建頂片段著色器unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);glCompileShader(fragShader);shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragShader);glLinkProgram(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragShader);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);glDrawArrays(GL_TRIANGLES, 0, 3);
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}
元素緩沖對象EBO
可以繪制兩個三角形來組合成一個矩形,這會生成下面的頂點的集合:
float vertices[] = {// 第一個三角形0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, 0.5f, 0.0f, // 左上角// 第二個三角形0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};
值得慶幸的是,元素緩沖區(qū)對象的工作方式正是如此。 EBO是一個緩沖區(qū),就像一個頂點緩沖區(qū)對象一樣,它存儲 OpenGL 用來決定要繪制哪些頂點的索引。這種所謂的索引繪制(Indexed Drawing)正是我們問題的解決方案。首先,我們先要定義(不重復的)頂點,和繪制出矩形所需的索引:
代碼展示:
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一個三角形1, 2, 3 // 第二個三角形
};
const char *vertexShaderSource ="#version 330 core \n""layout (location = 0) in vec3 aPos;\n""\n""void main()\n""{\n""\tgl_Position = vec4(aPos, 1.0);\n""}";
const char *fragShaderSource ="#version 330 core\n""out vec4 FragColor;\n""\n""void main()\n""{\n"" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n""} ";// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
unsigned int EBO;
unsigned int shaderProgram;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 綁定VAO和VBO對象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 釋放VAO和VBO對象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);// 創(chuàng)建頂點著色器unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader ,1 ,&vertexShaderSource, NULL);glCompileShader(vertexShader);// 創(chuàng)建頂片段著色器unsigned int fragShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragShader ,1 ,&fragShaderSource, NULL);glCompileShader(fragShader);shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragShader);glLinkProgram(shaderProgram);glDeleteShader(vertexShader);glDeleteShader(fragShader);glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);glUseProgram(shaderProgram);glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);
// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}
QT交互
- 如果需要從paintGL()以外的位置觸發(fā)重新繪制(典型示例是使用計時器設置場景動畫), 則應該調(diào)用widget的update()函數(shù)來安排更新
- 調(diào)用paintGL()、resizeGL()和initializeGL()時,widget的OpenGL呈現(xiàn)上下文將變?yōu)楫斍?。如果需要從其他位?#xff08;例如,在widget的構(gòu)造函數(shù)或自己的繪制函數(shù)中)調(diào)用opengl API函數(shù),則必須首先調(diào)用makeCurrent()。
代碼示例:
turboopenglwidget.h
#ifndef QTOPENGL_TURBOOPENGLWIDGET_H
#define QTOPENGL_TURBOOPENGLWIDGET_H#include <QOpenGLWidget>
#include <QOpenGLShaderProgram>
#include <QOpenGLFunctions_4_5_Core>class TurboOpenGLWidget : public QOpenGLWidget, QOpenGLFunctions_4_5_Core
{Q_OBJECT
public:enum Shape{None,Rect,Circle,Triangle,};explicit TurboOpenGLWidget(QWidget *parent = 0);~TurboOpenGLWidget() override;void drawShape(Shape shape);void setWireFrame(bool mode);protected:void initializeGL() override;void paintGL() override;void resizeGL(int w, int h) override;private:Shape shape_;QOpenGLShaderProgram shader_program_;
};#endif //QTOPENGL_TURBOOPENGLWIDGET_H
turboopenglwidget.cpp
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一個三角形1, 2, 3 // 第二個三角形
};// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}TurboOpenGLWidget::~TurboOpenGLWidget()
{makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 綁定VAO和VBO對象glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(0);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 釋放VAO和VBO對象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);bool success = false;shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
mainwidow.h
#ifndef QTOPENGL_MAINWINDOW_H
#define QTOPENGL_MAINWINDOW_H#include <QMainWindow>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECT
public:explicit MainWindow(QWidget *parent = nullptr);~MainWindow() override;protected slots:void drawRect();void clearPic();void lineModel(const bool &mode);private:Ui::MainWindow *ui;QToolBar *tool_bar_{nullptr};
};#endif //QTOPENGL_MAINWINDOW_H
mainwidow.cpp
#include "mainwindow.h"
#include "ui_MainWindow.h"
#include <QToolBar>
#include <QAction>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setCentralWidget(ui->openGLWidget);tool_bar_ = new QToolBar(this);auto *action = new QAction(tr("繪制矩形"), this);auto *action2 = new QAction(tr("清空圖形"), this);auto *action3 = new QAction(tr("線框模式"), this);action3->setCheckable(true);connect(action, &QAction::triggered, this, &MainWindow::drawRect);connect(action2, &QAction::triggered, this, &MainWindow::clearPic);connect(action3, &QAction::triggered, this, &MainWindow::lineModel);tool_bar_->addAction(action);tool_bar_->addAction(action2);tool_bar_->addAction(action3);addToolBar(tool_bar_);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::drawRect()
{ui->openGLWidget->drawShape(TurboOpenGLWidget::Rect);
}void MainWindow::clearPic()
{ui->openGLWidget->drawShape(TurboOpenGLWidget::None);
}void MainWindow::lineModel(const bool &mode)
{ui->openGLWidget->setWireFrame(mode);
}
GLSL
OpenGL Shading Languaage
一個典型的shader程序結(jié)構(gòu):
#version version_number
in type in_variable_name;
in type in_variable_name;out type out_variable_name;uniform type uniform_name;int main()
{// 處理輸入并進行一些圖形操作...// 輸出處理過的結(jié)果到輸出變量out_variable_name = weird_stuff_we_processed;
}
我們能聲明的頂點數(shù)量是有限的,可以通過下面的代碼獲取:
int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
OpenGL確保至少有16個包含4分量的頂點屬性可用,但是有些硬件可能會允許更多的頂點屬性。
GLSL支持的類型
類型:
GLSL中包含C等其他語言大部分默認的基礎數(shù)據(jù)類型
int float double uint bool
GLSL也有兩種容器類型
類型 | 含義 |
---|---|
vecn | 包含n 個float分量的默認向量 |
bvecn | 包含n 個bool分量的向量 |
ivecn | 包含n 個int分量的向量 |
uvecn | 包含n 個unsigned int分量的向量 |
dvecn | 包含n 個double分量的向量 |
向量這一數(shù)據(jù)類型也允許一些有趣而靈活的分量選擇方式,叫做重組(Swizzling)。重組允許這樣的語法:
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);
輸入輸出
- 在發(fā)送方著色器聲明一個輸出
- 在接收方著色器聲明一個類似的輸入
- 當類型和名稱都一致的時候OpenGL會把兩個變量鏈接到一起(在鏈接程序?qū)ο髸r候完成)
頂點著色器
#version 330 core
layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為0out vec4 vertexColor; // 為片段著色器指定一個顏色輸出void main()
{gl_Position = vec4(aPos, 1.0); // 注意我們?nèi)绾伟岩粋€vec3作為vec4的構(gòu)造器的參數(shù)vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把輸出變量設置為暗紅色
}
片段著色器
#version 330 core
out vec4 FragColor;in vec4 vertexColor; // 從頂點著色器傳來的輸入變量(名稱相同、類型相同)void main()
{FragColor = vertexColor;
}
頂點著色器接收的是一種特殊形式的輸入,否則就會效率低下
從頂點數(shù)據(jù)中直接接收輸入。為了定義頂點數(shù)據(jù)該如何管理,我們使用location這一元數(shù)據(jù)(metadata)指定輸入變量,這樣我們才可以在CPU上配置頂點屬性。例如: layout(location = 0)。 layout這個標識,使得我們能把它鏈接到頂點數(shù)據(jù)。
可以忽略layout( location = 0) 標識符,通過在OPenGL代碼中使用glGetAttrribLocation查詢屬性位置(Location),或者是glBindAttribLocation屬性位置值(Location),但是推薦在著色器中設置他們,這樣會更容易理解而且節(jié)省你和(OpenGL)的工作量
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, // 右上角0.5f, -0.5f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一個三角形1, 2, 3 // 第二個三角形
};// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{}TurboOpenGLWidget::~TurboOpenGLWidget()
{makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 綁定VAO和VBO對象glBindVertexArray(VAO);shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");bool success = false;success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值int location = shader_program_.attributeLocation("aPos");glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 3*sizeof(float), (void *)0);// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(location);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);// 釋放VAO和VBO對象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);
}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
Uniform
另一種從CPU的應用,向GPU中的著色器發(fā)送數(shù)據(jù)的方式
uniform是全局的,可以被任意的著色器程序在任一階段訪問
#version 330 core
out vec4 FragColor;uniform vec4 ourColor; // 在OpenGL程序代碼中設定這個變量void main()
{FragColor = ourColor;
}
如果聲明了一個uniform卻沒有用過,編譯器會默認移除這個變,導致最后編譯出的版本中并不會包含它,這可能導致幾個非常麻煩的錯誤,切記
這次我們不去給像素單獨傳遞一個顏色,而是讓他隨著時間改變顏色。
OpenGL在其核心是一個C庫,所以他不支持類型重載,在函數(shù)參數(shù)類型不同時候就要為其定義新的函數(shù),glUniform是一個典型的例子。這個函數(shù)有特定的后綴,用來標識設定的uniform的類型??赡艿暮缶Y有:
后綴 | 含義 |
---|---|
f | 函數(shù)需要一個float作為它的值 |
i | 函數(shù)需要一個int作為它的值 |
ui | 函數(shù)需要一個unsigned int作為它的值 |
3f | 函數(shù)需要3個float作為它的值 |
fv | 函數(shù)需要一個float向量/數(shù)組作為它的值 |
QT 為我們封裝了這個函數(shù),因此我們可以不用太過關注該函數(shù)的詳細內(nèi)容,但是你要是用原生的OpenGL的話,需要關注該函數(shù)。
紋理
當我們需要給圖形賦予真實的顏色的時候,不大可能使用前面的方法為每一個頂點指定第一個顏色,通常我們會采用紋理貼圖。
每個頂點關聯(lián)一個紋理坐標(Texture Coordinate),之后在圖形的其他片段上進行片段插值
我們只需要告訴OpenGL如何對紋理采樣即可
頂點著色器
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 1) in vec2 aTexCord;
out vec3 ourColor; // 向片段著色器輸出一個顏色
out vec2 texCord; // 向片段著色器輸出一個顏色
void main()
{gl_Position = vec4(aPos, 1.0);ourColor = aColor; // 將ourColor設置為我們從頂點數(shù)據(jù)那里得到的輸入顏色texCord = aTexCord;
}
片段著色器
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 texCord;
uniform sampler2D texture0;
void main()
{FragColor = texture(texture0, texCord);
}
對應的顯示代碼:
#include <iostream>
#include "turboopenglwidget.h"float vertices[] = {0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, // 右上角0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, // 右下角-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, // 左下角-0.5f, 0.5f, 0.0f, 0.5f, 0.5f, 0.5f, 0.0f, 1.0f // 左上角
};unsigned int indices[] = {0, 1, 3, // 第一個三角形1, 2, 3 // 第二個三角形
};// 創(chuàng)建VAO和VBO對象,并且賦予ID
unsigned int VBO, VAO;
unsigned int EBO;
TurboOpenGLWidget::TurboOpenGLWidget(QWidget *parent): QOpenGLWidget(parent)
{connect(&timer, &QTimer::timeout, this, &TurboOpenGLWidget::timeout);// timer.start(100);
}TurboOpenGLWidget::~TurboOpenGLWidget()
{if(!isValid()) return;makeCurrent();glDeleteBuffers(1, &VBO);glDeleteBuffers(1, &EBO);glDeleteVertexArrays(1, &VAO);doneCurrent();
}void TurboOpenGLWidget::initializeGL()
{initializeOpenGLFunctions();glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);glGenBuffers(1, &EBO);// 綁定VAO和VBO對象glBindVertexArray(VAO);shader_program_.addShaderFromSourceFile(QOpenGLShader::Vertex, ":/resources/shader.vert");shader_program_.addShaderFromSourceFile(QOpenGLShader::Fragment, ":/resources/shader.frag");bool success = false;success = shader_program_.link();if(!success){std::cout << "shader is failed" << std::endl;}glBindBuffer(GL_ARRAY_BUFFER, VBO);// 為當前綁定到target的緩沖區(qū)對象創(chuàng)建一個新的數(shù)據(jù)存儲// 如果data不是NULL, 則使用來自此指針的數(shù)據(jù)初始化數(shù)據(jù)存儲glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 告知顯卡如何解析緩沖里面的屬性值int location = shader_program_.attributeLocation("aPos");glVertexAttribPointer(location, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)0);// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(location);int location2 = shader_program_.attributeLocation("aColor");glVertexAttribPointer(location2, 3, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(3*sizeof(float)));// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(location2);int location3 = shader_program_.attributeLocation("aTexCord");glVertexAttribPointer(location3, 2, GL_FLOAT, GL_FALSE, 8*sizeof(float), (void *)(6*sizeof(float)));// 開啟VAO管理的第一個屬性的值glEnableVertexAttribArray(location3);glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg"));// 釋放VAO和VBO對象glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);}void TurboOpenGLWidget::paintGL()
{glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);shader_program_.bind();glBindVertexArray(VAO);
// glDrawArrays(GL_TRIANGLES, 0, 6);// glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);switch(shape_){case Rect:texture_wall_->bind();glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);break;}update();
}void TurboOpenGLWidget::resizeGL(int w, int h)
{}void TurboOpenGLWidget::drawShape(TurboOpenGLWidget::Shape shape)
{shape_ = shape;
}void TurboOpenGLWidget::setWireFrame(bool mode)
{makeCurrent();if(mode){glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);}else{glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);}
}
#include <QTime>
void TurboOpenGLWidget::timeout()
{if(shape_){return;}makeCurrent();int time = QTime::currentTime().second();float green = (sin(time) / 2.0f) + 0.5f;shader_program_.setUniformValue("ourColor", 0.0f, green, 0.0f, 1.0f);
}
紋理單元
OpenGL保證至少16個紋理單元,也就是說你可以激活從GL_TEXTURE0到GL_TEXTURE15。他們都是按照順序定義的, GL_TEXTURE0+8可以獲得GL_TEXTURE8
以下是QT主要代碼,在gitee項目中查看完整代碼。
texture_wall_ = new QOpenGLTexture(QImage(":/resources/wall.jpg").mirrored());
texture_le_ = new QOpenGLTexture(QImage(":/resources/awesomeface.png").mirrored());shader_program_.bind();
shader_program_.setUniformValue("textureWall", 0);
shader_program_.setUniformValue("textureSmile", 1);
紋理環(huán)繞
環(huán)繞方式 | 描述 |
---|---|
GL_REPEAT | 對紋理的默認行為。重復紋理圖像。 |
GL_MIRRORED_REPEAT | 和GL_REPEAT一樣,但每次重復圖片是鏡像放置的。 |
GL_CLAMP_TO_EDGE | 紋理坐標會被約束在0到1之間,超出的部分會重復紋理坐標的邊緣,產(chǎn)生一種邊緣被拉伸的效果。 |
GL_CLAMP_TO_BORDER | 超出的坐標為用戶指定的邊緣顏色。 |
相關代碼請到gitee查看,這里不復制
紋理過濾
紋理坐標不依賴于分辨率,OpenGL需要知道怎么將紋理像素映射到紋理坐標;
可以想象你打開一張圖片,不斷放大,會發(fā)現(xiàn)它是由無數(shù)像素點組成的,這個點就是紋理像素
- 紋理坐標的精度是無限的,可以是任意浮點值
- 紋理像素是有限的(圖片分辨率)
- 一個像素需要一個顏色
- 所謂采樣就是通過紋理坐標,問圖片要紋理像素的顏色值
大圖片貼小面片時:紋理的精度高,相鄰紋理像素往往色差不打,無需融合,直接就近選取即可。
主要函數(shù):
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
相關代碼請到gitee查看,這里不復制
多級漸遠紋理
簡單來說就是一系列的紋理圖像,根據(jù)觀察者與物體的距離,參考臨界值,選擇最適合物體的距離的那個紋理
OpenGL有一個glGenerateMipmaps函數(shù),可以生產(chǎn)多級漸遠紋理
過濾方式 | 描述 |
---|---|
GL_NEAREST_MIPMAP_NEAREST | 使用最鄰近的多級漸遠紋理來匹配像素大小,并使用鄰近插值進行紋理采樣 |
GL_LINEAR_MIPMAP_NEAREST | 使用最鄰近的多級漸遠紋理級別,并使用線性插值進行采樣 |
GL_NEAREST_MIPMAP_LINEAR | 在兩個最匹配像素大小的多級漸遠紋理之間進行線性插值,使用鄰近插值進行采樣 |
GL_LINEAR_MIPMAP_LINEAR | 在兩個鄰近的多級漸遠紋理之間使用線性插值,并使用線性插值進行采樣 |
主要函數(shù):
texture_small_->generateMipMaps();
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
相關代碼請到gitee查看,這里不復制