GLSL
前面有提到关于着色器就是用着色器语言写的用于图形渲染管线的程序,在OpenGL当中,着色器是使用GLSL的类c语言写的,GLSL是为图形计算量身定制的,它包含一些针对向量和矩阵操作的有用特性。着色器的开头总是要声明版本,接着是输入和输出变量、和main函数。每个着色器的入口点都是main函数,在这个函数中我们处理所有的输入变量,并将结果输出到输出变量中。
对于顶点着色器,每个输入变量又被称为顶点属性(Vertex Attribute),顶点属性的上限一般取决于硬件,OpenGL确保至少有16个包含4分量的顶点属性可用。
数据类型
和其他编程语言一样,GLSL有数据类型可以来指定变量的种类。GLSL中包含C等其它语言大部分的默认基础数据类型,同时也包含向量和矩阵两种容器类型。对于向量而言,GLSL中的向量是一个可以包含有1、2、3或者4个分量的容器,分量的类型可以是前面默认基础类型的任意一个。它们可以是下面的形式(n
代表分量的数量):
向量访问的方式比较灵活自由,可以通过vec.x的方法获取其中的一个分量,可以是相对于位置的xyzw,也可以是颜色的rgba,亦可以是纹理的stpq。向量这一数据类型也允许一些有趣而灵活的分量选择方式,叫做重组(Swizzling)。以下的重组都是被允许的
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec = differentVec.zyw;
vec4 otherVec = someVec.xxxx + anotherVec.yxzy;
vec2 vect = vec2(0.5f, 0.7f);
vec4 result = vec4(vect, 0.0f, 0.0f);
vec4 otherResult = vec4(result.xyz, 1.0f);
输入与输出
虽然着色器各自是相对独立的小程序,但是互相为一个整体。各个着色器之间通过数据的输入输出来进行交流,GLSL通过in和out关键字来专门实现这个目的,每个着色器用这两个关键字设定输入和输出,只要下一阶段的输入和上个阶段的输出相匹配它就会传递下去。但在顶点和片段着色器中会有点不同。
顶点着色器直接从顶点数据中获取输入,为了定义内存如何管理顶点数据,我们使用location
这一元数据指定输入变量,这样我们才可以在CPU上配置顶点属性。通过location定义不同顶点数据的顶点属性以告诉程序如何管理这些数据。所以顶点着色器需要为他的输入额外定义一个layout标识把它链接到顶点数据。
所以,如果我们打算从一个着色器向另一个着色器发送数据,我们必须在发送方着色器中声明一个输出,在接收方着色器中声明一个类似的输入。当类型和名字都一样的时候,OpenGL就会把两个变量链接到一起,它们之间就能发送数据了。
Uniform
Uniform是一种从cpu向gpu发送数据的方式,但和顶点属性不同。它属于“全局变量”,这意味着在所有着色器程序中都可以使用它,并且是唯一的,Uniform的值会在着色器的各个阶段被保存,直到被更新或者删除,同时我们在着色器外部实用程序的时候可以获取并对其进行赋值使用。但是,如果你声明了一个uniform却在GLSL代码中没用过,编译器会静默移除这个变量,导致最后编译出的版本中并不会包含它,这可能导致几个非常麻烦的错误。
const GLchar* fragmentshaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
"color = ourColor;\n"
"}\n\0";
我们在片段着色器当中声明了一个uniform变量,通过在声明前加关键字uniform。
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 0.6f);
glClear(GL_COLOR_BUFFER_BIT);
//使用uniform
glUseProgram(program);//启动程序
//更新uniform
GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
GLfloat blueValue = (cos(timeValue) / 2 ) + 0.5;
GLint colorLocation = glGetUniformLocation(program,"ourColor");//获取uniform的位置
//设置uniform
glUniform4f(colorLocation, greenValue,greenValue,blueValue,1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
在游戏循环中,我们对我们的uniform进行使用,首先通过glGetUniformLocation在指定程序找指定名称的变量,然后通过glUniform4f进行赋值,关于函数glfwGetTime获取了程序运行的时间,我们通过三角函数将范围变化限制在了0--1,所以最终的颜色是随时间渐变的过程。
由于OpenGL核心是一个C库,而它的库函数不支持函数重载。在函数参数不同的时候就要为其定义新的函数;glUniform是一个典型例子。这个函数有一个特定的后缀,标识设定的uniform的类型。可能的后缀有:
所以这里我们使用的是4f现在我们就可以绘制一个颜色随时间变化的三角形了。
完整代码如下
#define GLEW_STATIC
#include<GL/glew.h>
#include<GLFW/glfw3.h>
#include <iostream>
#include <cmath>
#include "Shader.h"
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"
"out vec3 ourColor;\n"
"void main()\n"
"{\n"
"gl_Position = vec4(position, 1.0);\n"
"ourColor = color;\n"
"}\0";
const GLchar* fragmentshaderSource = "#version 330 core\n"
"out vec4 color;\n"
"uniform vec4 ourColor;\n"
"void main()\n"
"{\n"
"color = ourColor;\n"
"}\n\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(800,800, "Shader1", nullptr, nullptr);
glfwMakeContextCurrent(window);
glfwSetKeyCallback(window, key_callback);
glewExperimental = GL_TRUE;
glewInit();
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
GLuint vertexshader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexshader,1,&vertexshaderSource,NULL);
glCompileShader(vertexshader);
GLuint fragmentshader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentshader, 1, &fragmentshaderSource, NULL);
glCompileShader(fragmentshader);
GLuint program = glCreateProgram();
glAttachShader(program, fragmentshader);
glAttachShader(program, fragmentshader);
glLinkProgram(program);
glDeleteShader(vertexshader);
glDeleteShader(fragmentshader);
GLfloat vertices[] = {
-0.5f,-0.5f,0,
0, 0.5f, 0,
0.5,-0.5,0 };
GLuint VBO, VAO;
glCreateBuffers(1, &VBO);
glCreateVertexArrays(1, &VAO);
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(GLfloat),(void*)0);
glEnableVertexAttribArray(0);
glBindVertexArray(0);
while (!glfwWindowShouldClose(window)) {
glfwPollEvents();
glClearColor(0.2f, 0.3f, 0.3f, 0.6f);
glClear(GL_COLOR_BUFFER_BIT);
//使用uniform
glUseProgram(program);//启动程序
//更新uniform
GLfloat timeValue = glfwGetTime();
GLfloat greenValue = (sin(timeValue) / 2) + 0.5;
GLfloat blueValue = (cos(timeValue) / 2 ) + 0.5;
GLint colorLocation = glGetUniformLocation(program,"ourColor");//获取uniform的位置
//设置uniform
glUniform4f(colorLocation, greenValue,greenValue,blueValue,1.0f);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
glBindVertexArray(0);
glfwSwapBuffers(window);
}
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
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);
}
}