OpenGL从入门到精通--着色器的使用
著色器
github源碼倉庫
opengl環境準備
opengl編程從入門到精通-hello,window
OpenGL從入門到精通–你好三角形
OpenGL從入門到精通–著色器的使用
著色器(Shader)是運行在GPU上的小程序。這些小程序為圖形渲染管線的某個特定部分而運行。從基本意義上來說,著色器只是一種把輸入轉化為輸出的程序。著色器也是一種非常獨立的程序,因為它們之間不能相互通信;它們之間唯一的溝通只有通過輸入和輸出。
GLSL
著色器是使用一種叫做GLSL的類C語言寫成的,GLSL是為圖形計算量身定制的,他包含一些針對向量和矩陣操作的有用特性。
著色器的開頭總是要聲明版本,接著是輸入和輸出變量、uniform和main函數。每個著色器的入口點都是main函數,在這個函數中我們處理所有的輸入變量,并將結果輸出到輸出變量中。如果你不知道什么是uniform也不用擔心,我們后面會進行講解。
如下:
#version version_number in type in_variable_name; in type in_variable_name;out type out_variable_name;uniform type uniform_name;int main() {// 處理輸入并進行一些圖形操作...// 輸出處理過的結果到輸出變量out_variable_name = weird_stuff_we_processed; }數據類型
和其他編程語言一樣,GLSL有數據類型可以來指定變量的種類。GLSL中包含C等其它語言大部分的默認基礎數據類型:int、float、double、uint和bool。GLSL也有兩種容器類型,它們會在這個教程中使用很多,分別是向量(Vector)和矩陣(Matrix),其中矩陣我們會在之后的教程里再討論。
向量
GLSL中的向量是一個可以包含有1、2、3或者4個分量的容器,分量的類型可以是前面默認基礎類型的任意一個。它們可以是下面的形式(n代表分量的數量):
| vecn | 包含n個float分量的默認向量 |
| bvecn | 包含n個bool分量的向量 |
| ivecn | 包含n個int分量的向量 |
| uvecn | 包含n個unsigned int分量的向量 |
| dvecn | 包含n個double分量的向量 |
向量這一數據類型也允許一些有趣而靈活的分量選擇方式,叫做重組(Swizzling)。重組允許這樣的語法:
vec2 someVec; vec4 differentVec = someVec.xyxx; vec3 anotherVec = differentVec.zyw; vec4 otherVec = someVec.xxxx + anotherVec.yxzy;你可以使用上面4個字母任意組合來創建一個和原來向量一樣長的(同類型)新向量,只要原來向量有那些分量即可;然而,你不允許在一個vec2向量中去獲取.z元素。我們也可以把一個向量作為一個參數傳給不同的向量構造函數,以減少需求參數的數量:
vec2 vect = vec2(0.5, 0.7); vec4 result = vec4(vect, 0.0, 0.0); vec4 otherResult = vec4(result.xyz, 1.0);向量是一種靈活的數據類型,我們可以把用在各種輸入和輸出上。學完教程你會看到很多新穎的管理向量的例子。
輸入與輸出
雖然著色器是各自獨立的小程序,但是它們都是一個整體的一部分,出于這樣的原因,我們希望每個著色器都有輸入和輸出,這樣才能進行數據交流和傳遞。GLSL定義了in和out關鍵字專門來實現這個目的。每個著色器使用這兩個關鍵字設定輸入和輸出,只要一個輸出變量與下一個著色器階段的輸入匹配,它就會傳遞下去。但在頂點和片段著色器中會有點不同。
頂點著色器應該接收的是一種特殊形式的輸入,否則就會效率低下。頂點著色器的輸入特殊在,它從頂點數據中直接接收輸入。為了定義頂點數據該如何管理,我們使用location這一元數據指定輸入變量,這樣我們才可以在CPU上配置頂點屬性。我們已經在前面的教程看過這個了,layout (location = 0)。頂點著色器需要為它的輸入提供一個額外的layout標識,這樣我們才能把它鏈接到頂點數據。
頂點著色器
#version 330 core layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為0out vec4 vertexColor; // 為片段著色器指定一個顏色輸出void main() {gl_Position = vec4(aPos, 1.0); // 注意我們如何把一個vec3作為vec4的構造器的參數vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // 把輸出變量設置為暗紅色 }片段著色器
#version 330 core out vec4 FragColor;in vec4 vertexColor; // 從頂點著色器傳來的輸入變量(名稱相同、類型相同)void main() {FragColor = vertexColor; }Uniform
Uniform是一種從CPU中的應用向GPU中國捏的著色器發送數據的方式,但是uniform和頂點的屬性有些不同。首先uniform是全局的,全局意味著uniform變量在每個著色器程序對象中都是獨一無二的,而且他可以被著色器程序的任意著色器在任意階段訪問。無論你把uniform設置成什么,uniform會一直保存它們的數據,直到它們被重新設置或者更新。
#version 330 core out vec4 FragColor;uniform vec4 ourColor; // 在OpenGL程序代碼中設定這個變量void main() {FragColor = ourColor; }如果你聲明了一個uniform卻在GLSL代碼中沒用過,編譯器會靜默移除這個變量,導致最后編譯出的版本中并不會包含它,這可能導致幾個非常麻煩的錯誤,記住這點!
float timeValue = glfwGetTime(); float greenValue = (sin(timeValue) / 2.0f) + 0.5f; int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor"); glUseProgram(shaderProgram); glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);首先我們通過glfwGetTime()獲取運行的秒數。然后我們使用sin函數讓顏色在0.0到1.0之間改變,最后將結果儲存到greenValue里。
接著,我們用glGetUniformLocation查詢uniform ourColor的位置值。我們為查詢函數提供著色器程序和uniform的名字(這是我們希望獲得的位置值的來源)。如果glGetUniformLocation返回-1就代表沒有找到這個位置值。最后,我們可以通過glUniform4f函數設置uniform值。注意,查詢uniform地址不要求你之前使用過著色器程序,但是更新一個uniform之前你必須先使用程序(調用glUseProgram),因為它是在當前激活的著色器程序中設置uniform的。
完整源碼實現
// // Created by andrew on 2021/1/17. //#include <glad/glad.h> #include <GLFW/glfw3.h>#include <iostream> #include <math.h>void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;const char *vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 aPos;\n""void main()\n""{\n"" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n""}\0"; const char *fragmentShaderSource = "#version 330 core\n""out vec4 FragColor;\n""uniform vec4 ourColor;\n""void main()\n""{\n"" FragColor = ourColor;\n""}\n\0";int main() {// glfw: initialize and configure// ------------------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){std::cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------// vertex shaderint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// check for shader compile errorsint success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// fragment shaderint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);glCompileShader(fragmentShader);// check for shader compile errorsglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// link shadersint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// check for linking errorsglGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {0.5f, 0.5f, 0.0f, // top right0.5f, -0.5f, 0.0f, // bottom right-0.5f, 0.5f, 0.0f // top left};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.// 解綁 VAOglBindVertexArray(0);glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 使用線框模式繪制圖形glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 查看opengl如何繪制矩形// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// be sure to activate the shader before any calls to glUniformglUseProgram(shaderProgram);// update shader uniformdouble timeValue = glfwGetTime();float greenValue = sin(timeValue) / 2.0f + 0.5f;int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);glDrawArrays(GL_TRIANGLES, 0, 3);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose:// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteProgram(shaderProgram);// glfw: terminate, clearing all previously allocated GLFW resources.// ------------------------------------------------------------------glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// make sure the viewport matches the new window dimensions; note that width and// height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height); }更多屬性!
在前面的教程中,我們了解了如何填充VBO、配置頂點屬性指針以及如何把它們都儲存到一個VAO里。這次,我們同樣打算把顏色數據加進頂點數據中。我們將把顏色數據添加為3個float值至vertices數組。我們將把三角形的三個角分別指定為紅色、綠色和藍色:
float vertices[] = {// 位置 // 顏色0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // 右下-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // 左下0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // 頂部 };由于現在有更多的數據要發送到頂點著色器,我們有必要去調整一下頂點著色器,使它能夠接收顏色值作為一個頂點屬性輸入。需要注意的是我們用layout標識符來把aColor屬性的位置值設置為1:
#version 330 core layout (location = 0) in vec3 aPos; // 位置變量的屬性位置值為 0 layout (location = 1) in vec3 aColor; // 顏色變量的屬性位置值為 1out vec3 ourColor; // 向片段著色器輸出一個顏色void main() {gl_Position = vec4(aPos, 1.0);ourColor = aColor; // 將ourColor設置為我們從頂點數據那里得到的輸入顏色 }由于我們不再使用uniform來傳遞片段的顏色了,現在使用ourColor輸出變量,我們必須再修改一下片段著色器:
#version 330 core out vec4 FragColor; in vec3 ourColor;void main() {FragColor = vec4(ourColor, 1.0); }因為我們添加了另一個頂點屬性,并且更新了VBO的內存,我們就必須重新配置頂點屬性指針。更新后的VBO內存中的數據現在看起來像這樣:
知道了現在使用的布局,我們就可以使用glVertexAttribPointer函數更新頂點格式,
// 位置屬性 // 第一個參數和 layout (location = 0)對應 glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0); glEnableVertexAttribArray(0); // 顏色屬性 glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3* sizeof(float))); glEnableVertexAttribArray(1);glVertexAttribPointer函數的前幾個參數比較明了。這次我們配置屬性位置值為1的頂點屬性。顏色值有3個float那么大,我們不去標準化這些值。
由于我們現在有了兩個頂點屬性,我們不得不重新計算步長值。為獲得數據隊列中下一個屬性值(比如位置向量的下個x分量)我們必須向右移動6個float,其中3個是位置值,另外3個是顏色值。這使我們的步長值為6乘以float的字節數(=24字節)。
同樣,這次我們必須指定一個偏移量。對于每個頂點來說,位置頂點屬性在前,所以它的偏移量是0。顏色屬性緊隨位置數據之后,所以偏移量就是3 * sizeof(float),用字節來計算就是12字節。
運行程序你應該會看到如下結果:
如果你在哪卡住了,可以在這里查看源碼。
這個圖片可能不是你所期望的那種,因為我們只提供了3個顏色,而不是我們現在看到的大調色板。這是在片段著色器中進行的所謂片段插值(Fragment Interpolation)的結果。當渲染一個三角形時,光柵化(Rasterization)階段通常會造成比原指定頂點更多的片段。光柵會根據每個片段在三角形形狀上所處相對位置決定這些片段的位置。
基于這些位置,它會插值(Interpolate)所有片段著色器的輸入變量。比如說,我們有一個線段,上面的端點是綠色的,下面的端點是藍色的。如果一個片段著色器在線段的70%的位置運行,它的顏色輸入屬性就會是一個綠色和藍色的線性結合;更精確地說就是30%藍 + 70%綠。
這正是在這個三角形中發生了什么。我們有3個頂點,和相應的3個顏色,從這個三角形的像素來看它可能包含50000左右的片段,片段著色器為這些像素進行插值顏色。如果你仔細看這些顏色就應該能明白了:紅首先變成到紫再變為藍色。片段插值會被應用到片段著色器的所有輸入屬性上。
完整代碼
// // Created by andrew on 2021/1/17. //#include <glad/glad.h> #include <GLFW/glfw3.h>#include <iostream> #include <cmath>using namespace std;void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;const char *vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 aPos;\n""layout (location = 1) in vec3 aColor;\n""out vec3 ourColor;\n""void main()\n""{\n"" gl_Position = vec4(aPos, 1.0);\n"" ourColor = aColor;\n""}\0"; const char *fragmentShaderSource = "#version 330 core\n""out vec4 FragColor;\n""in vec3 ourColor;\n""void main()\n""{\n"" FragColor = vec4(ourColor, 1.0f);\n""}\n\0";int main() {// glfw: initialize and configure// ------------------------------glfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// --------------------GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);if (window == NULL){cout << "Failed to create GLFW window" << endl;glfwTerminate();return -1;}glfwMakeContextCurrent(window);glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// ---------------------------------------if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize GLAD" << endl;return -1;}// build and compile our shader program// ------------------------------------// vertex shaderint vertexShader = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);glCompileShader(vertexShader);// check for shader compile errorsint success;char infoLog[512];glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(vertexShader, 512, nullptr, infoLog);std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;}// fragment shaderint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragmentShader, 1, &fragmentShaderSource, nullptr);glCompileShader(fragmentShader);// check for shader compile errorsglGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(fragmentShader, 512, nullptr, infoLog);std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;}// link shadersint shaderProgram = glCreateProgram();glAttachShader(shaderProgram, vertexShader);glAttachShader(shaderProgram, fragmentShader);glLinkProgram(shaderProgram);// check for linking errorsglGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);if (!success) {glGetProgramInfoLog(shaderProgram, 512, nullptr, infoLog);std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;}glDeleteShader(vertexShader);glDeleteShader(fragmentShader);// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions // colors0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).glBindVertexArray(VAO);glBindBuffer(GL_ARRAY_BUFFER, VBO);glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);// 要和shader對應, "layout (location = 0) in vec3 aPos;\n" 對應這里第一個參數,也就是索引// 這里第一個參數對應的0 要說明0參數加載的方式 // 6 * sizeof(float) 每次的步長glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));glEnableVertexAttribArray(1);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.// 解綁 VAO 防止有多個 VAO時渲染出錯glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);// 使用線框模式繪制圖形// glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); // 查看opengl如何繪制矩形// seeing as we only have a single VAO there's// no need to bind it every time, but we'll do so to keep things a bit more organized// VAO只有一個只需要綁定一次就行了glBindVertexArray(VAO);// 連接器只有一個,只需要綁定一次就行了// as we only have a single shader, we could also just activate our shader once beforehand if we want toglUseProgram(shaderProgram);// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// 繪制三角形,渲染三角形glDrawArrays(GL_TRIANGLES, 0, 3);// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// optional: de-allocate all resources once they've outlived their purpose:// ------------------------------------------------------------------------glDeleteVertexArrays(1, &VAO);glDeleteBuffers(1, &VBO);glDeleteProgram(shaderProgram);// glfw: terminate, clearing all previously allocated GLFW resources.// ------------------------------------------------------------------glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// make sure the viewport matches the new window dimensions; note that width and// height will be significantly larger than specified on retina displays.glViewport(0, 0, width, height); }讀取文件獲取vs和fs
將著色器的處理封裝到一個類中,這樣就不用每次都處理著色器的代碼了
shader_s.h
// // Created by andrew on 2021/1/17. //#ifndef OPENGL_SHADER_S_H #define OPENGL_SHADER_S_H#include <glad/glad.h>#include <string> #include <fstream> #include <sstream> #include <iostream>class Shader { public:// 程序IDunsigned int ID;// 構造器讀取并構建著色器// constructor generates the shader on the fly// ------------------------------------------------------------------------Shader(const char* vertexPath, const char* fragmentPath){// 1. retrieve the vertex/fragment source code from filePathstd::string vertexCode;std::string fragmentCode;std::ifstream vShaderFile;std::ifstream fShaderFile;// ensure ifstream objects can throw exceptions:vShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);fShaderFile.exceptions (std::ifstream::failbit | std::ifstream::badbit);try{// open filesvShaderFile.open(vertexPath);fShaderFile.open(fragmentPath);std::stringstream vShaderStream, fShaderStream;// read file's buffer contents into streamsvShaderStream << vShaderFile.rdbuf();fShaderStream << fShaderFile.rdbuf();std::cout << vShaderStream.str() << std::endl;// close file handlersvShaderFile.close();fShaderFile.close();// convert stream into stringvertexCode = vShaderStream.str();fragmentCode = fShaderStream.str();}catch (std::ifstream::failure& e){std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl;}const char* vShaderCode = vertexCode.c_str();const char * fShaderCode = fragmentCode.c_str();// 2. compile shadersunsigned int vertex, fragment;// vertex shadervertex = glCreateShader(GL_VERTEX_SHADER);glShaderSource(vertex, 1, &vShaderCode, NULL);glCompileShader(vertex);checkCompileErrors(vertex, "VERTEX");// fragment Shaderfragment = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(fragment, 1, &fShaderCode, NULL);glCompileShader(fragment);checkCompileErrors(fragment, "FRAGMENT");// shader ProgramID = glCreateProgram();glAttachShader(ID, vertex);glAttachShader(ID, fragment);glLinkProgram(ID);checkCompileErrors(ID, "PROGRAM");// delete the shaders as they're linked into our program now and no longer necessaryglDeleteShader(vertex);glDeleteShader(fragment);}// activate the shader// ------------------------------------------------------------------------void use() const{glUseProgram(ID);}// 注意下面這幾個函數一定要在use之后才能是使用,只有激活了對應的程序更改uniform值才能生效// utility uniform functions// ------------------------------------------------------------------------void setBool(const std::string &name, bool value) const{glUniform1i(glGetUniformLocation(ID, name.c_str()), (int)value);}// ------------------------------------------------------------------------void setInt(const std::string &name, int value) const{glUniform1i(glGetUniformLocation(ID, name.c_str()), value);}// ------------------------------------------------------------------------void setFloat(const std::string &name, float value) const{// glGetUniformLocation獲取對應uniform的location的名字 // std::cout << name.c_str() << "ID" <<ID << std::endl;glUniform1f(glGetUniformLocation(ID, name.c_str()), value);}private:// utility function for checking shader compilation/linking errors.// ------------------------------------------------------------------------static void checkCompileErrors(unsigned int shader, std::string type){int success;char infoLog[1024];if (type != "PROGRAM"){glGetShaderiv(shader, GL_COMPILE_STATUS, &success);if (!success){glGetShaderInfoLog(shader, 1024, NULL, infoLog);std::cout << "ERROR::SHADER_COMPILATION_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;}}else{glGetProgramiv(shader, GL_LINK_STATUS, &success);if (!success){glGetProgramInfoLog(shader, 1024, NULL, infoLog);std::cout << "ERROR::PROGRAM_LINKING_ERROR of type: " << type << "\n" << infoLog << "\n -- --------------------------------------------------- -- " << std::endl;}}} };#endif //OPENGL_SHADER_S_H著色器代碼放到文件中
shader_vs1.vs
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;out vec3 ourColor;void main() {gl_Position = vec4(aPos, 1.0);ourColor = aColor; }shader_vs2.vs
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;out vec3 ourColor;void main() {vec3 tmpPos;tmpPos = vec3(aPos.x, -aPos.y, aPos.z);gl_Position = vec4(tmpPos, 1.0);ourColor = aColor; }shader_vs3.vs
#version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;out vec3 ourColor;uniform float xOffset;void main() {gl_Position = vec4(aPos.x + xOffset, aPos.y, aPos.z, 1.0); // add the xOffset to the x position of the vertex positionourColor = aColor; }shader_vs4.vs
// Vertex shader: // ============== #version 330 core layout (location = 0) in vec3 aPos; layout (location = 1) in vec3 aColor;// out vec3 ourColor; out vec3 ourPosition;void main() {gl_Position = vec4(aPos, 1.0);// ourColor = aColor;ourPosition = aPos; }shader_fs4,fs
// Fragment shader: // ================ #version 330 core out vec4 FragColor; // in vec3 ourColor; in vec3 ourPosition;void main() {FragColor = vec4(ourPosition, 1.0); // note how the position value is linearly interpolated to get all the different colors }/* Answer to the question: Do you know why the bottom-left side is black? -- -------------------------------------------------------------------- Think about this for a second: the output of our fragment's color is equal to the (interpolated) coordinate of the triangle. What is the coordinate of the bottom-left point of our triangle? This is (-0.5f, -0.5f, 0.0f). Since the xy values are negative they are clamped to a value of 0.0f. This happens all the way to the center sides of the triangle since from that point on the values will be interpolated positively again. Values of 0.0f are of course black and that explains the black side of the triangle. */通過修改vs文件實現三角形的反轉
// // Created by andrew on 2021/1/17. // #include "glad/glad.h" #include <GLFW/glfw3.h> #include "shader/shader_s.h"#include <iostream>using namespace std;void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;int main() {// 對glfw進行初始化glfwInit();// 打印出glfw的版本信息// int* major, int* minor, int* revint major, minor, rev;glfwGetVersion(&major, &minor, &rev);cout << "major = " << major << " minor = " << minor << " rev = " << rev << endl;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// glfw創建窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);if (window == nullptr){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// 為當前window設置上下文,每個線程只能設置一個,并且線程之間共用時,需要將當前線程設置為 non-currentglfwMakeContextCurrent(window);// 設置窗口大小的回調函數,當窗口大小改變時,會調用該函數調整串口的大小// 注冊窗口大小改變回調函數glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// glad 會加載所有openGL函數指針,在調用任何opengl函數之前需要先初始化gladif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------Shader ourShader("/work/opengl_tutorial/src/shader_src/shader_vs2.vs","/work/opengl_tutorial/src/shader_src/shader_fs1.fs"); // you can name your shader files however you like// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions // colors0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s)./* 要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用 *//* // ..:: 初始化代碼(只運行一次 (除非你的物體頻繁改變)) :: ..// 1. 綁定VAO */glBindVertexArray(VAO);// 2. 把頂點數組復制到緩沖中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);// 把之前定義的頂點,復制到緩沖的內存中去/*GL_STATIC_DRAW :數據不會或幾乎不會改變。GL_DYNAMIC_DRAW:數據會被改變很多。GL_STREAM_DRAW :數據每次繪制時都會改變。 *//*三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW。如果,比如說一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分。 */glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);/*位置數據被儲存為32位(4字節)浮點值。每個位置包含3個這樣的值。在這3個值之間沒有空隙(或其他值)。這幾個值在數組中緊密排列(Tightly Packed)。數據中第一個值在緩沖開始的位置 */// 告訴GPU數據怎樣取/*第一個參數指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0。我們會在后面詳細解釋這個參數。 */// 設置頂點屬性指針glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);/*現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性;頂點屬性默認是禁用的。 */glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));// 啟用對應頂點的屬性,頂點定義在vs完成glEnableVertexAttribArray(1);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.// glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);/*就這么多了!前面做的一切都是等待這一刻,一個儲存了我們頂點屬性配置和應使用的VBO的頂點數組對象。一般當你打算繪制多個物體時,你首先要生成/配置所有的VAO(和必須的VBO及屬性指針),然后儲存它們供后面使用。當我們打算繪制物體的時候就拿出相應的VAO,綁定它,繪制完物體后,再解綁VAO。*/// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------// 北背景glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// draw our first triangle// 2. 當我們渲染一個物體時要使用著色器程序ourShader.use();glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized// 3. 繪制物體glDrawArrays(GL_TRIANGLES, 0, 3);// glBindVertexArray(0); // no need to unbind it every time// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// 正確的釋放之前分配的所有資源glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {// 用戶按下 esc鍵,就設置退出串口為真if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// opengl渲染串口大小,每次調整窗口cout << "view port call back" << endl;//glViewport(0, 0, width, height); }通過更改uniform實現三角形右移
float offset = 0.5f; ourShader.setFloat("xOffset", offset); // // Created by andrew on 2021/1/17. // #include "glad/glad.h" #include <GLFW/glfw3.h> #include "shader/shader_s.h"#include <iostream>using namespace std;void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;int main() {// 對glfw進行初始化glfwInit();// 打印出glfw的版本信息// int* major, int* minor, int* revint major, minor, rev;glfwGetVersion(&major, &minor, &rev);cout << "major = " << major << " minor = " << minor << " rev = " << rev << endl;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// glfw創建窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);if (window == nullptr){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// 為當前window設置上下文,每個線程只能設置一個,并且線程之間共用時,需要將當前線程設置為 non-currentglfwMakeContextCurrent(window);// 設置窗口大小的回調函數,當窗口大小改變時,會調用該函數調整串口的大小// 注冊窗口大小改變回調函數glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// glad 會加載所有openGL函數指針,在調用任何opengl函數之前需要先初始化gladif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------Shader ourShader("/work/opengl_tutorial/src/shader_src/shader_vs3.vs","/work/opengl_tutorial/src/shader_src/shader_fs1.fs"); // you can name your shader files however you like// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions // colors0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s)./* 要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用 *//* // ..:: 初始化代碼(只運行一次 (除非你的物體頻繁改變)) :: ..// 1. 綁定VAO */glBindVertexArray(VAO);// 2. 把頂點數組復制到緩沖中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);// 把之前定義的頂點,復制到緩沖的內存中去/*GL_STATIC_DRAW :數據不會或幾乎不會改變。GL_DYNAMIC_DRAW:數據會被改變很多。GL_STREAM_DRAW :數據每次繪制時都會改變。 *//*三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW。如果,比如說一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分。 */glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);/*位置數據被儲存為32位(4字節)浮點值。每個位置包含3個這樣的值。在這3個值之間沒有空隙(或其他值)。這幾個值在數組中緊密排列(Tightly Packed)。數據中第一個值在緩沖開始的位置 */// 告訴GPU數據怎樣取/*第一個參數指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0。我們會在后面詳細解釋這個參數。 */// 設置頂點屬性指針glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);/*現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性;頂點屬性默認是禁用的。 */glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));// 啟用對應頂點的屬性,頂點定義在vs完成glEnableVertexAttribArray(1);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.// glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);/*就這么多了!前面做的一切都是等待這一刻,一個儲存了我們頂點屬性配置和應使用的VBO的頂點數組對象。一般當你打算繪制多個物體時,你首先要生成/配置所有的VAO(和必須的VBO及屬性指針),然后儲存它們供后面使用。當我們打算繪制物體的時候就拿出相應的VAO,綁定它,繪制完物體后,再解綁VAO。*/// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------// 北背景glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// draw our first triangle// 2. 當我們渲染一個物體時要使用著色器程序ourShader.use();float offset = 0.5f;ourShader.setFloat("xOffset", offset);glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized// 3. 繪制物體glDrawArrays(GL_TRIANGLES, 0, 3);// glBindVertexArray(0); // no need to unbind it every time// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// 正確的釋放之前分配的所有資源glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {// 用戶按下 esc鍵,就設置退出串口為真if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// opengl渲染串口大小,每次調整窗口cout << "view port call back" << endl;//glViewport(0, 0, width, height); }將三角形的頂點數據作為顏色數據使用
// // Created by andrew on 2021/1/17. // #include "glad/glad.h" #include <GLFW/glfw3.h> #include "shader/shader_s.h"#include <iostream>using namespace std;void framebuffer_size_callback(GLFWwindow* window, int width, int height); void processInput(GLFWwindow *window);// settings const unsigned int SCR_WIDTH = 800; const unsigned int SCR_HEIGHT = 600;int main() {// 對glfw進行初始化glfwInit();// 打印出glfw的版本信息// int* major, int* minor, int* revint major, minor, rev;glfwGetVersion(&major, &minor, &rev);cout << "major = " << major << " minor = " << minor << " rev = " << rev << endl;glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);// glfw window creation// glfw創建窗口GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", nullptr, nullptr);if (window == nullptr){std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}// 為當前window設置上下文,每個線程只能設置一個,并且線程之間共用時,需要將當前線程設置為 non-currentglfwMakeContextCurrent(window);// 設置窗口大小的回調函數,當窗口大小改變時,會調用該函數調整串口的大小// 注冊窗口大小改變回調函數glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);// glad: load all OpenGL function pointers// glad 會加載所有openGL函數指針,在調用任何opengl函數之前需要先初始化gladif (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)){cout << "Failed to initialize GLAD" << std::endl;return -1;}// build and compile our shader program// ------------------------------------Shader ourShader("/work/opengl_tutorial/src/shader_src/shader_vs4.vs","/work/opengl_tutorial/src/shader_src/shader_fs4.fs"); // you can name your shader files however you like// set up vertex data (and buffer(s)) and configure vertex attributes// ------------------------------------------------------------------float vertices[] = {// positions // colors0.5f, -0.5f, 0.0f, 1.0f, 0.0f, 0.0f, // bottom right-0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, // bottom left0.0f, 0.5f, 0.0f, 0.0f, 0.0f, 1.0f // top};unsigned int VBO, VAO;glGenVertexArrays(1, &VAO);glGenBuffers(1, &VBO);// bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s)./* 要想使用VAO,要做的只是使用glBindVertexArray綁定VAO。從綁定之后起,我們應該綁定和配置對應的VBO和屬性指針,之后解綁VAO供之后使用 *//* // ..:: 初始化代碼(只運行一次 (除非你的物體頻繁改變)) :: ..// 1. 綁定VAO */glBindVertexArray(VAO);// 2. 把頂點數組復制到緩沖中供OpenGL使用glBindBuffer(GL_ARRAY_BUFFER, VBO);// 把之前定義的頂點,復制到緩沖的內存中去/*GL_STATIC_DRAW :數據不會或幾乎不會改變。GL_DYNAMIC_DRAW:數據會被改變很多。GL_STREAM_DRAW :數據每次繪制時都會改變。 *//*三角形的位置數據不會改變,每次渲染調用時都保持原樣,所以它的使用類型最好是GL_STATIC_DRAW。如果,比如說一個緩沖中的數據將頻繁被改變,那么使用的類型就是GL_DYNAMIC_DRAW或GL_STREAM_DRAW,這樣就能確保顯卡把數據放在能夠高速寫入的內存部分。 */glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);/*位置數據被儲存為32位(4字節)浮點值。每個位置包含3個這樣的值。在這3個值之間沒有空隙(或其他值)。這幾個值在數組中緊密排列(Tightly Packed)。數據中第一個值在緩沖開始的位置 */// 告訴GPU數據怎樣取/*第一個參數指定我們要配置的頂點屬性。還記得我們在頂點著色器中使用layout(location = 0)定義了position頂點屬性的位置值(Location)嗎?它可以把頂點屬性的位置值設置為0。因為我們希望把數據傳遞到這一個頂點屬性中,所以這里我們傳入0。第二個參數指定頂點屬性的大小。頂點屬性是一個vec3,它由3個值組成,所以大小是3。第三個參數指定數據的類型,這里是GL_FLOAT(GLSL中vec*都是由浮點數值組成的)。下個參數定義我們是否希望數據被標準化(Normalize)。如果我們設置為GL_TRUE,所有數據都會被映射到0(對于有符號型signed數據是-1)到1之間。我們把它設置為GL_FALSE。第五個參數叫做步長(Stride),它告訴我們在連續的頂點屬性組之間的間隔。由于下個組位置數據在3個float之后,我們把步長設置為3 * sizeof(float)。要注意的是由于我們知道這個數組是緊密排列的(在兩個頂點屬性之間沒有空隙)我們也可以設置為0來讓OpenGL決定具體步長是多少(只有當數值是緊密排列時才可用)。一旦我們有更多的頂點屬性,我們就必須更小心地定義每個頂點屬性之間的間隔,我們在后面會看到更多的例子(譯注: 這個參數的意思簡單說就是從這個屬性第二次出現的地方到整個數組0位置之間有多少字節)。最后一個參數的類型是void*,所以需要我們進行這個奇怪的強制類型轉換。它表示位置數據在緩沖中起始位置的偏移量(Offset)。由于位置數據在數組的開頭,所以這里是0。我們會在后面詳細解釋這個參數。 */// 設置頂點屬性指針glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);/*現在我們已經定義了OpenGL該如何解釋頂點數據,我們現在應該使用glEnableVertexAttribArray,以頂點屬性位置值作為參數,啟用頂點屬性;頂點屬性默認是禁用的。 */glEnableVertexAttribArray(0);// color attributeglVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));// 啟用對應頂點的屬性,頂點定義在vs完成glEnableVertexAttribArray(1);// You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other// VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.// glBindVertexArray(0);// uncomment this call to draw in wireframe polygons.//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);/*就這么多了!前面做的一切都是等待這一刻,一個儲存了我們頂點屬性配置和應使用的VBO的頂點數組對象。一般當你打算繪制多個物體時,你首先要生成/配置所有的VAO(和必須的VBO及屬性指針),然后儲存它們供后面使用。當我們打算繪制物體的時候就拿出相應的VAO,綁定它,繪制完物體后,再解綁VAO。*/// render loop// -----------while (!glfwWindowShouldClose(window)){// input// -----processInput(window);// render// ------// 北背景glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);// draw our first triangle// 2. 當我們渲染一個物體時要使用著色器程序ourShader.use();float offset = 0.5f;ourShader.setFloat("xOffset", offset);glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized// 3. 繪制物體glDrawArrays(GL_TRIANGLES, 0, 3);// glBindVertexArray(0); // no need to unbind it every time// glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)// -------------------------------------------------------------------------------glfwSwapBuffers(window);glfwPollEvents();}// 正確的釋放之前分配的所有資源glfwTerminate();return 0; }// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly // --------------------------------------------------------------------------------------------------------- void processInput(GLFWwindow *window) {// 用戶按下 esc鍵,就設置退出串口為真if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)glfwSetWindowShouldClose(window, true); }// glfw: whenever the window size changed (by OS or user resize) this callback function executes // --------------------------------------------------------------------------------------------- void framebuffer_size_callback(GLFWwindow* window, int width, int height) {// opengl渲染串口大小,每次調整窗口cout << "view port call back" << endl;//glViewport(0, 0, width, height); } 與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的OpenGL从入门到精通--着色器的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【2016年第1期】农业大数据给商品交易
- 下一篇: OpenGL从入门到精通--纹理