GLSL基础
我们至少需要两种着色器:顶点着色器和片段你着色器。还有一种可选的着色器为几何着色器。我们可以在三种方式中选择一种来为顶点着色器传递数据:
~参数:对顶点而言。
~统一值:针对整个顶点数据批次的常量。
~纹理数据:为片段着色器设置统一值和纹理数据。
注意:将顶点属性发送到片段着色器上毫无意义。片段着色器只是用来在图元进行光栅化后对片段进行填充。不过每个顶点数据都可以通过顶点程序传递到片段着色器。但是在这种情况下,这些数据可能市场量,或者这些值也可以用不同的方式在图元表面进行插值。
注意相对于C语言来说,其数据机构可以任意使用向量数据。vec2,vec3之类的,以及矩阵类型mat,使用方式和unity当中的向量使用方式大同小异。这里列出一些值得注意的地方:
注意在进行纹理操作的时候使用stpq作为纹理向量的四个坐标。正常访问:xyzw,访问颜色rgba。
注意,不能讲不同组混合到一个向量访问中。
注意,矩阵类型只支持浮点数。
注意:mat4(1f)这种时使用了一个更快的矩阵构造器,来创建一个单位矩阵。
(1)存储限定符
着色器变量声明也可以选择制定一个存储限定符。
存储限定符用于将变量标记为 输入变量(in uniform) 输出变量(out) 常量(const)。
输入变量接受来自OPENGL客户端或者以前的着色器阶段。输出变量是在任何着色器阶段进行写入的变量。我们希望后续的着色器阶段能够看到这些变量。
<none>:只是普通的本地变量,外部不可见,外部不可访问。
const:一个编译时的常量,或者说是一个对韩素来说为只读的参数。
in:从以前的阶段传递过来的变量。
in centroid:一个从以前阶段传递过来的变量,使用之心差值。
out:传递传递到下一个处理阶段或者在一个函数指定一个返回值。
out centroid:传递到下一个处理阶段,使用质心差值。
inout:一个读写变量,只能用于局部函数参数。
uniform:一个从客户端代码传递过来的变量,在顶点之间不做改变。
inout只能在一个函数中声明一个参数的时候使用。OPENGL不支持指针,所以将一个值传递到一个函数并且允许这个函数修改并且返回同一个变量值的唯一方法。
注意,除非在对一个多重采样缓冲区进行渲染,否则centroid限定符不会起任何作用。在一个单采样缓冲区中,插值操作总是从像素的中心开始的。对于多重采样,在使用centroid时候,差不知将会被选中,因此他会落到图元和像素中。
默认状态下,参数将会在两个着色器阶段之间用一种透视正确的方法进行插补。我们可以通过noperspective关键字来制定一个非透视插值。或者可以通过flat关键字而不进行插值。或者使用smooth关键字来声明,这个变量是以一种透视正确的方法进行差不得。但是这实际上已经是默认设置了。
真正的着色器
#version 130
//指定版本
//每个定点程序最多16个属性
//标记为in是指定版本。标记为in是只读的。
in vec4 vColor;
in vec4 vVertex;
//声明输出。将要传送到片元着色器的一个属性
out vec4 vVaryingColor;
//当在一个顶点着色器中将一个值设置为out,在片段着色器中声明为in,这个片段着色器接受的
//变量值为一个插补值。在默认情况下,这些工作将以一个正确透视的方式进行,并且在变量
//之前指定另一个额外的限定符smooth。以确保完成工作。
void main(void)
{
vVaryingColor = vColor;
//gl_Position是一个预定义的内建4分量向量。包含顶点着色器要求的一个输出。
//输入gl_Position的值是几何图形阶段用来装配图元的。我们没有选择任何附加的
//变换,那么顶点将会映射到所有3个坐标范围都在+-1之间的笛卡尔坐标上。
gl_Position = vVertex;
}
顶点着色器,作为vp文件存在,放置在工程文件目录下。主程序中会以读取文档的形式对其代码读取。
// The ShadedIdentity Shader
// Fragment Shader
// Richard S. Wright Jr.
// OpenGL SuperBible
#version 130
//在渲染一个图元的时候,一旦三个顶点由顶点程序进行了处理,那么他们将会组装成一个三角形
//而这个三角形将由硬件进行光栅化。图形硬件确定独立片段属于屏幕上的什么位置,并且为三角形中的每个
//片段执行片段程序的一个实例。
//注意这个输出,在内部被分配为“输出0”。这是片段着色器的第一个输出,
//并且将传输到由glDrawBuffer设置的缓冲区目标中,默认情况为GL_BACK。黑色换冲区
//实际颜色缓冲区并不包含4个浮点分量,这样输出值就会映射到目标缓冲区的范围内。
out vec4 vFragColor;
//输出片段着色器是经过平滑插值的颜色值,由定点程序上游传入。这只是作为一个in变量
//进行声明的。
in vec4 vVaryingColor;
void main(void)
{
//直接进行赋值。
vFragColor = vVaryingColor;
}
片段着色器,以fp文件格式存在于主程序的目录之下,主程序以读取文本文件的形式对文件进行读取并且运行。
#pragma comment(lib,"GLTools.lib")
#include <GLTools.h> // OpenGL toolkit
#include <GLShaderManager.h> // Shader Manager Class
#include <GL/glut.h>
#include <iostream>
using namespace std;
//接受可变参数列表的函数声明,对于这个函数来说,第一个参数是
//顶点程序包含属性的数量。在这之后是一个对应于第一个属性的基于0
//的索引,以及作为一个字符数组的属性名。然后,只要有必要,属性槽的数量
//和名称将进行足够多次数的重复。
GLuint gltLoadShaderPairWithAttributes(const char* szVertexProg, const char*szFragmentProg, ...)
{
GLuint hVerrexShader;
GLuint hFragmentShader;
GLuint hReturn = 0;
GLint testVal;
//创建两个着色器对象,分别对应顶点着色器以及片元着色器。
hVerrexShader = glCreateShader(GL_VERTEX_SHADER);
hFragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
if (gltLoadShaderFile(szVertexProg, hVerrexShader) == false)
{
glDeleteShader(hVerrexShader);
glDeleteShader(hFragmentShader);
cout << "The shader at " << szVertexProg << " could not be found" << endl;
return (GLuint)NULL;
}
if (gltLoadShaderFile(szFragmentProg, hFragmentShader) == false)
{
glDeleteShader(hVerrexShader);
glDeleteShader(hFragmentShader);
cout << "The shader at " << szFragmentProg << " could not be found" << endl;
return (GLuint)NULL;
}
//编译着色器
glCompileShader(hVerrexShader);
glCompileShader(hFragmentShader);
//使用下面的函数,来检查失败。
glGetShaderiv(hVerrexShader, GL_COMPILE_STATUS, &testVal);
if(testVal==GL_FALSE)
{
char infoLog[1024];
//使用下面的函数检查着色器的消息日志。
glGetShaderInfoLog(hVerrexShader, 1024, NULL, infoLog);
cout << "The shader at " << szVertexProg
<< "fail to compile with the following error: "
<< infoLog << endl;
glDeleteShader(hVerrexShader);
glDeleteShader(hFragmentShader);
return (GLuint)NULL;
}
glGetShaderiv(hFragmentShader, GL_COMPILE_STATUS, &testVal);
if (testVal == GL_FALSE)
{
char infoLog[1024];
glGetShaderInfoLog(hFragmentShader, 1024, NULL, infoLog);
cout << "The shader at " << hFragmentShader
<< "fail to compile with the following error: "
<< infoLog << endl;
glDeleteShader(hVerrexShader);
glDeleteShader(hFragmentShader);
return (GLuint)NULL;
}
//创建最终的着色器程序对象
hReturn = glCreateProgram();
//将顶点着色器和片段着色器绑定到一起。
glAttachShader(hReturn, hVerrexShader);
glAttachShader(hReturn, hFragmentShader);
va_list attributeList;
va_start(attributeList, hFragmentShader);
char * szNextArg;
int iArgCount = va_arg(attributeList, int);
for (int i = 0; i < iArgCount; i++)
{
int index = va_arg(attributeList, int);
szNextArg= va_arg(attributeList, char*);
//将属性名称绑定到指定的数字属性位置。
//他接受着色器的标识符,将要进行绑定的属性位置,属性变量的名称。
//进行绑定之前,必须按照这种凡事对属性位置进行连接。我们遍历了可变参数列表
//只需要简单的为每个需要进行绑定的属性重复调用这个函数。
glBindAttribLocation(hReturn, index, szNextArg);
}
va_end(attributeList);
//尝试连接,连接着色器
glLinkProgram(hReturn);
glDeleteShader(hVerrexShader);
glDeleteShader(hFragmentShader);
glGetProgramiv(hReturn, GL_LINK_STATUS, &testVal);
if (testVal == GL_FALSE)
{
char infoLog[1024];
glGetProgramInfoLog(hReturn, 1024, NULL, infoLog);
cout << "The shader at " << hFragmentShader
<< "fail to compile with the following error: "
<< infoLog << endl;
glDeleteProgram(hReturn);
return (GLuint)NULL;
}
return hReturn;
}
//综上所述,所谓加载着色器,也就八个步骤:
//1.创建着色器: glCreateShader
//2.对着色器对象加载着色器程序: gltLoadShaderFile
//3.编译着色器对象: glCompileShader
//4. 对顶点着色器检查编译错误: glGetShaderiv
//5.创建程序对象函数 glCreateProgram
//6. 把程序连接着色器对象 glAttachShader
//7. 连接程序 glLinkProgram
//8. 检验链接程序是否正确glGetShaderiv
对于在主程序当中调用的函数的详细分析,这一个函数就是连接着色器程序和主程序的桥梁。非常重要。
// Triangle.cpp
// 注意,OPENGL API不支持任何类型的IO操作,最简单的方式是将着色器存储在ASC||纯文本文件中。
//或者硬编码到程序当中。(但是这种应变仪使得程序更加封闭)
#pragma comment(lib,"GLTools.lib")
#include <GLTools.h> // OpenGL toolkit
#include <GLShaderManager.h> // Shader Manager Class
#include <GL/glut.h> // Windows FreeGlut equivalent
//这个程序来说明编译绑定链接shader。
//着色器中的属性名需要绑定到由GLSL所提供的16种
//预分配属性槽中的一个。
GLBatch triangleBatch;
GLShaderManager shaderManager;
//这个int就作为渲染器的程序
GLint myIdentityShader;
///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.
void ChangeSize(int w, int h)
{
glViewport(0, 0, w, h);
}
///////////////////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering context.
// This is the first opportunity to do any OpenGL related tasks.
void SetupRC()
{
// Blue background
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
shaderManager.InitializeStockShaders();
// 硬性加载了
GLfloat vVerts[] = { -0.5f, 0.0f, 0.0f,
0.5f, 0.0f, 0.0f,
0.0f, 0.5f, 0.0f };
GLfloat vColors[] = { 1.0f, 0.0f, 0.0f, 1.0f,
0.0f, 1.0f, 0.0f, 1.0f,
0.0f, 0.0f, 1.0f, 1.0f };
triangleBatch.Begin(GL_TRIANGLES, 3);
triangleBatch.CopyVertexData3f(vVerts);
triangleBatch.CopyColorData4f(vColors);
triangleBatch.End();
//对渲染器加载属性
myIdentityShader = gltLoadShaderPairWithAttributes("ShadedIdentity.vp", "ShadedIdentity.fp", 2,
GLT_ATTRIBUTE_VERTEX, "vVertex", GLT_ATTRIBUTE_COLOR, "vColor");
}
///////////////////////////////////////////////////////////////////////////////
// Cleanup
void ShutdownRC()
{
//结束设置的时候,就可以删除这个程序了。
glDeleteProgram(myIdentityShader);
}
///////////////////////////////////////////////////////////////////////////////
// Called to draw scene
void RenderScene(void)
{
// Clear the window with current clearing color
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
//如果使用GLSL着色器,必须要使用glUseProgram去选定他。
//将着色器设置为活动的,现在顶点着色器和片段着色器可以处理
//所有提交的集合图形。
glUseProgram(myIdentityShader);
triangleBatch.Draw();
// 交换前后台缓冲区
glutSwapBuffers();
}
///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs
int main(int argc, char* argv[])
{
gltSetWorkingDirectory(argv[0]);
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
glutInitWindowSize(800, 600);
glutCreateWindow("Shaded Triangle");
glutReshapeFunc(ChangeSize);
glutDisplayFunc(RenderScene);
GLenum err = glewInit();
if (GLEW_OK != err) {
fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
return 1;
}
SetupRC();
glutMainLoop();
ShutdownRC();
return 0;
}
真正调用编辑的着色器的主程序。
Provoking Vertex
如果有一个值对于整个批次来说都必须是常数,那么应该使用一个UNIFORM值。不过有时候,有一个对整个图元的表面来说是唯一的。但是对每一个三角形需要进行性改变的值,还是非常有用的。使用统一值时发送大量三角形,每个批次用一个三角形作为实例,这样做非常低效。引入flat存储标识符。
但一个图元的每个顶点都有一个不同的平面着色变量值的时候,只有其中一个顶点可以“平面地”应用。默认的约定是使用为图元的最后一个顶点指定的值。这个约定就是“provoking vertex”
void glProvokingVertex(GLenum provokeMode);
//合法值:GL_FIRST_VERTEX_CONVENTION 和 GL_LAST_VERTEX_CONVENTIONS(默认值)