一、 着色器
1. GLSL
一个典型的着色器有下面的结构:
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;}
OpenGL确保顶点着色器的输入变量(顶点属性)至少有16个包含4分量的顶点属性可用,但是有些硬件或许允许更多的顶点属性,你可以查询GL_MAX_VERTEX_ATTRIBS来获取具体的上限:
GLint nrAttributes;glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;
2. 数据类型
GLSL中包含C等其它语言大部分的默认基础数据类型:int、float、double、uint和bool。GLSL也有两种容器类型,它们会在这个教程中使用很多,分别是向量(Vector)和矩阵(Matrix)。
3. 向量
GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n代表分量的数量):
类型 | 含义 | vecn包含n个float分量的默认向量bvecn包含n个bool分量的向量ivecn包含n个int分量的向量uvecn包含n个unsigned int分量的向量dvecn包含n个double分量的向量一个向量的分量可以通过vec.x这种方式获取,这里x是指这个向量的第一个分量。你可以分别使用.x、.y、.z和.w来获取它们的第1、2、3、4个分量。GLSL也允许你对颜色使用rgba,或是对纹理坐标使用stpq访问相同的分量。
向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。重组允许这样的语法:
vec2 someVec;vec4 differentVec = someVec.xyxx;vec3 anotherVec = differentVec.zyw;vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
你可以使用上面4个字母任意组合来创建一个和原来向量一样长的(同类型)新向量,只要原来向量有那些分量即可;然而,你不允许在一个vec2向量中去获取.z元素。我们也可以把一个向量作为一个参数传给不同的向量构造函数,以减少需求参数的数量:
vec2 vect = vec2(0.5f, 0.7f);vec4 result = vec4(vect, 0.0f, 0.0f);vec4 otherResult = vec4(result.xyz, 1.0f);
4. 输入与输出
GLSL定义了in和out关键字专门来实现输入和输出,只要一个输出变量与下一个着色器阶段的输入匹配,它就会传递下去。
顶点着色器的输入特殊在,它从顶点数据中直接接收输入。为了定义顶点数据该如何管理,我们使用location这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。顶点着色器需要为它的输入提供一个额外的layout标识,这样我们才能把它链接到顶点数据。
你也可以忽略layout (location = 0)标识符,通过在OpenGL代码中使用glGetAttribLocation查询属性位置值(Location),但是我更喜欢在着色器中设置它们,这样会更容易理解而且节省你(和OpenGL)的工作量。
另一个例外是片段着色器,它需要一个vec4颜色输出变量,因为片段着色器需要生成一个最终输出的颜色。如果你在片段着色器没有定义输出颜色,OpenGL会把你的物体渲染为黑色(或白色)。
顶点着色器
#version 330 corelayout(location = 0) in vec3 position; out vec4 vertexColor; void main(){ gl_Position = vec4(position, 1.0); vertexColor = vec4(0.5f, 0.0f, 0.0f, 1.0f); }
片段着色器
version 330 core
in vec4 vertexColor; out vec4 color; void main(){ color = vertexColor;}
Uniform是一种从CPU中的应用向GPU中的着色器发送数据的方式,但uniform和顶点属性有些不同。首先,uniform是全局的(Global)。全局意味着uniform变量必须在每个着色器程序对象中都是独一无二的,而且它可以被着色器程序的任意着色器在任意阶段访问。第二,无论你把uniform值设置成什么,uniform会一直保存它们的数据,直到它们被重置或更新。
如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误,记住这点!
#version 330 coreshader:out vec4 color;uniform vec4 ourColor; void main(){ color = ourColor;}程序:GLfloat timeValue = glfwGetTime();GLfloat greenValue = (sin(timeValue) / 2) + 0.5;GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");glUseProgram(shaderProgram);glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
我们用glGetUniformLocation查询uniform ourColor的位置值。我们为查询函数提供着色器程序和uniform的名字(这是我们希望获得的位置值的来源)。如果glGetUniformLocation返回-1就代表没有找到这个位置值。最后,我们可以通过glUniform4f函数设置uniform值。注意,查询uniform地址不要求你之前使用过着色器程序,但是更新一个unform之前你必须先使用程序(调用glUseProgram),因为它是在当前激活的着色器程序中设置unform的。
glUniform函数有一个特定的后缀,标识设定的uniform的类型。可能的后缀有:
后缀 | 含义 | f函数需要一个float作为它的值i函数需要一个int作为它的值ui函数需要一个unsigned int作为它的值3f函数需要3个float作为它的值fv函数需要一个float向量/数组作为它的值下面我们就计算greenValue然后每个渲染迭代都更新这个uniform:
while (!glfwWindowShouldClose(window)){ glfwPollEvents(); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glUseProgram(shaderProgram); GLfloat timeValue = glfwGetTime(); GLfloat greenValue = (sin(timeValue) / 2) + 0.5; GLint vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor"); glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f); glBindVertexArray(VAO); glDrawArrays(GL_TRIANGLES, 0, 3); glBindVertexArray(0);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
6. 更多属性
这次,我们同样打算把颜色数据加进顶点数据中。我们将把颜色数据添加为3个float值至vertices数组。我们将把三角形的三个角分别指定为红色、绿色和蓝色:
GLfloat 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标识符来把color属性的位置值设置为1:
#version 330 corelayout(location = 0) in vec3 position; layout(location = 1) in vec3 color; out vec3 ourColor; void main(){ gl_Position = vec4(position, 1.0); ourColor = color; }
由于我们不再使用uniform来传递片段的颜色了,现在使用ourColor输出变量,我们必须再修改一下片段着色器:
#version 330 corein vec3 ourColor;out vec4 color;void main(){ color = vec4(ourColor, 1.0f);}
因为我们添加了另一个顶点属性,并且更新了VBO的内存,我们就必须重新配置顶点属性指针。更新后的VBO内存中的数据现在看起来像这样:
知道了现在使用的布局,我们就可以使用glVertexAttribPointer函数更新顶点格式,
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0);glEnableVertexAttribArray(0);glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));glEnableVertexAttribArray(1);
glVertexAttribPointer函数的前几个参数比较明了。这次我们配置属性位置值为1的顶点属性。颜色值有3个float那么大,我们不去标准化这些值。
由于我们现在有了两个顶点属性,我们不得不重新计算步长值。为获得数据队列中下一个属性值(比如位置向量的下个x分量)我们必须向右移动6个float,其中3个是位置值,另外3个是颜色值。这使我们的步长值为6乘以float的字节数(=24字节)。
同样,这次我们必须指定一个偏移量。对于每个顶点来说,位置顶点属性在前,所以它的偏移量是0。颜色属性紧随位置数据之后,所以偏移量就是3 * sizeof(GLfloat),用字节来计算就是12字节。
运行程序你应该会看到如下结果:
一、 自定义着色器类
头文件:
我们会把着色器类全部放在在头文件里,主要是为了学习用途,当然也方便移植。我们先来添加必要的include,并定义类结构:
#pragma once#ifndef SHADER_H#define SHADER_H#include <string>#include <fstream>#include <sstream>#include <iostream>#include <GL/glew.h>; using namespace std;class Shader{public: GLuint Program; Shader(); Shader(std::string vertexPath, std::string fragmentPath); Shader(const GLchar* vShaderCode, const GLchar* fShaderCode); void Use();private:};#endif
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
在上面,我们在头文件顶部使用了几个预处理指令(Preprocessor Directives)。这些预处理指令会告知你的编译器只在它没被包含过的情况下才包含和编译这个头文件,即使多个文件都包含了这个着色器头文件。它是用来防止链接冲突的。
着色器类储存了着色器程序的ID。它的构造器需要顶点和片段着色器源代码的文件路径,这样我们就可以把源码的文本文件储存在硬盘上了。
源文件:
#include "Shader.h"Shader::Shader(){ Program = 0;}Shader::Shader(std::string vertexPath, std::string fragmentPath){ std::string vertexCode; std::string fragmentCode; std::ifstream vShaderFile; std::ifstream fShaderFile; vShaderFile.exceptions(std::ifstream::badbit); fShaderFile.exceptions(std::ifstream::badbit); try { vShaderFile.open(vertexPath); fShaderFile.open(fragmentPath); std::stringstream vShaderStream, fShaderStream; vShaderStream << vShaderFile.rdbuf(); fShaderStream << fShaderFile.rdbuf(); vShaderFile.close(); fShaderFile.close(); vertexCode = vShaderStream.str(); fragmentCode = fShaderStream.str(); } catch (std::ifstream::failure e) { std::cout << "ERROR::SHADER::FILE_NOT_SUCCESFULLY_READ" << std::endl; } Shader(vertexCode.c_str(), fragmentCode.c_str());}Shader::Shader(const GLchar* vShaderCode, const GLchar* fShaderCode){ GLuint vertex, fragment; GLint success; GLchar infoLog[512]; vertex = glCreateShader(GL_VERTEX_SHADER); glShaderSource(vertex, 1, &vShaderCode, NULL); glCompileShader(vertex); glGetShaderiv(vertex, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(vertex, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; }; fragment = glCreateShader(GL_FRAGMENT_SHADER); glShaderSource(fragment, 1, &fShaderCode, NULL); glCompileShader(fragment); glGetShaderiv(fragment, GL_COMPILE_STATUS, &success); if (!success) { glGetShaderInfoLog(fragment, 512, NULL, infoLog); std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl; }; this->Program = glCreateProgram(); glAttachShader(this->Program, vertex); glAttachShader(this->Program, fragment); glLinkProgram(this->Program); glGetProgramiv(this->Program, GL_LINK_STATUS, &success); if (!success) { glGetProgramInfoLog(this->Program, 512, NULL, infoLog); std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl; } glDeleteShader(vertex); glDeleteShader(fragment);}void Shader::Use(){ glUseProgram(this->Program);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
转载请注明出处:http://blog.csdn.net/ylbs110/article/details/52145794
二、 实例
代码:
shader使用上述自定义shader,不再再实例中直接处理shader。
#include <iostream>using namespace std;#define GLEW_STATIC#include <GL/glew.h>#include <GLFW/glfw3.h>#include "Shader.h"const GLuint WIDTH = 800, HEIGHT = 600;void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);const GLchar* vertexShaderSource = "#version 330 core\n""layout (location = 0) in vec3 position;\n""layout (location = 1) in vec3 color;\n""uniform vec4 offset;\n""out vec3 glColor;\n""out vec4 vertexColor;\n""void main()\n""{\n""gl_Position = vec4(position.x, -position.y, position.z, 1.0)+offset;\n""vertexColor=gl_Position;\n""glColor=color;\n""}\0";const GLchar* fragmentShaderSourceOrange = "#version 330 core\n""out vec4 color;\n""in vec4 vertexColor;\n""in vec3 glColor;\n""void main()\n""{\n""color = vec4(glColor, 1.0f)+vertexColor;\n""}\n\0";const GLchar* fragmentShaderSourceYellow = "#version 330 core\n""out vec4 color;\n""in vec4 vertexColor;\n""in vec3 glColor;\n""void main()\n""{\n""color = vertexColor;\n""}\n\0";Shader yellowShader,orangeShader;GLuint VBO1, VAO1;GLuint VBO2, VAO2, EBO2;void shaderInit() { yellowShader = Shader(vertexShaderSource, fragmentShaderSourceYellow); orangeShader = Shader(vertexShaderSource, fragmentShaderSourceOrange);}void vertexObjectInit() { GLfloat vertices1[] = { -0.5f, 0.2f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.2f, 0.0f, 1.0f, 1.0f, 0.0f, -0.2f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.5f, 0.2f, 0.0f, 1.0f, 1.0f, 0.0f, -0.2f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.2f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f }; glGenBuffers(1, &VBO1); glGenVertexArrays(1, &VAO1); glBindVertexArray(VAO1); glBindBuffer(GL_ARRAY_BUFFER, VBO1); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices1), vertices1, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); GLfloat vertices[] = { -0.5f, 0.2f, 0.0f, 1.0f, 0.5f, 0.2f, 0.5f, 0.2f, 0.0f, 1.0f, 0.5f, 0.2f, -0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.2f, 0.5f, -0.5f, 0.0f, 1.0f, 0.5f, 0.2f }; GLuint indices[] = { 0,1,2, 1,2,3 }; glGenBuffers(1, &VBO2); glGenBuffers(1, &EBO2); glGenVertexArrays(1, &VAO2); glBindVertexArray(VAO2); glBindBuffer(GL_ARRAY_BUFFER, VBO2); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO2); glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW); glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)0); glEnableVertexAttribArray(0); glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat))); glEnableVertexAttribArray(1); glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0);}int main(){ glfwInit(); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); glfwWindowHint(GLFW_RESIZABLE, GL_FALSE); GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr); if (window == nullptr) { std::cout << "Failed to create GLFW window" << std::endl; glfwTerminate(); return -1; } glfwMakeContextCurrent(window); glfwSetKeyCallback(window, key_callback); glewExperimental = GL_TRUE; if (glewInit() != GLEW_OK) { std::cout << "Failed to initialize GLEW" << std::endl; return -1; } int width, height; glfwGetFramebufferSize(window, &width, &height); glViewport(0, 0, width, height); shaderInit(); vertexObjectInit(); while (!glfwWindowShouldClose(window)) { glfwPollEvents(); glClearColor(0.2f, 0.3f, 0.3f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); GLfloat timeValue = glfwGetTime(); GLfloat offsetx = (sin(timeValue) / 2) + 0.5; GLfloat offsety = (cos(timeValue) / 2) + 0.5; yellowShader.Use(); GLint vertexColorLocation = glGetUniformLocation(yellowShader.Program, "offset"); glUniform4f(vertexColorLocation, offsetx, offsety, 0.0f, 1.0f); glBindVertexArray(VAO1); glDrawArrays(GL_TRIANGLES, 0, 6); glBindVertexArray(0); orangeShader.Use(); GLint vertexorangeLocation = glGetUniformLocation(orangeShader.Program, "offset"); glUniform4f(vertexorangeLocation, offsetx, offsety, 0.0f, 1.0f); glBindVertexArray(VAO2); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); glBindVertexArray(0); glfwSwapBuffers(window); } glDeleteVertexArrays(1, &VAO1); glDeleteBuffers(1, &VBO1); glDeleteVertexArrays(1, &VAO2); glDeleteBuffers(1, &VBO2); glfwTerminate(); return 0;}void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode){ if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) glfwSetWindowShouldClose(window, GL_TRUE);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
结果:
运行程序后可以看到这个倒置的(顶点Shader将y坐标反转)房屋在运动的同时颜色也在变化,其中梯形部分由于是使用黄色进行了叠加而导致颜色更偏黄偏暖色调。