A.1 OpenGL管线
A.2 C++/OpenGL应用程序
在我们尝试编写着色器之前,我们先用编写一个简单的C++/OpenGL应用程序,创建一个GLFWwindow实例并为其设置背景色。
- 初始化GLFW库 - 实例化GLFWwindow - 初始化GLEW库 - 调用一次init()函数 - 重复调用display()函数 - 注意:在初始化GLEW前必须先初始化GLFW,也就是说必须在调用glewInit()前先调用glfwInit()。
将每个程序的初始化任务都放在init()函数中,用于绘制GLFWwindow的代码都放在display()函数中。
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
void init(GLFWwindow* window){ }
void display(GLFWwindow* window, double currentTime)
{
//让我们指定颜色缓冲区清除后填充的值,rgba表示,此处为红色
glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
//用于清除窗口的颜色缓冲区
glClear(GL_COLOR_BUFFER_BIT);
}
int main()
{
if (!glfwInit())
exit(EXIT_FAILURE);
//指定计算机必需与OpenGL版本4.3的兼容,主版本号为4,次版本号为3
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
//glfwCreateWindow,创建宽,高位600像素,顶部标题为Hello,World,
// 另外两个参数分别用来表示允许全屏显示和资源共享
GLFWwindow* window = glfwCreateWindow(600, 600, "Hello, World", nullptr, nullptr);
//关联当前GLFW窗口和OpenGL上下文(我们创建的GLFW窗口不会自动关联OpenGL的上下文)
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK)
exit(EXIT_FAILURE);
//开启垂直同步
glfwSwapInterval(1);
init(window);
while (!glfwWindowShouldClose(window))
{
display(window, glfwGetTime());
glfwSwapBuffers(window);
//处理窗口相关的事件(如按键事件)
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
A.3顶点着色器和片段着色器
OpenGL只能绘制几类非常简单的东西,如点、线、三角形。这些简单的东西叫图元。
图元由顶点组成,如三角形由三个顶点。
顶点来源有很多,如从文件读取并由C++/OpenGL应用载入缓冲区,直接在C++中硬编码,或者直接在GLSL代码中生成。
在加载顶点之前,C++/OpenGL应用程序必须编译并链接合适的GLSL顶点着色器和片段着色器程序,之后将他们载入管线。
我们会使用一个OpenGL的函数来构建图形,所有的顶点都会被传入顶点着色器,被逐次处理。
-
glDrawArrays()
语法:
void WINAPI glDrawArrays( GLenum mode,//要呈现的基元类型 GLint first,//已启用的数组中的起始索引。 GLsizei count//要呈现的所引数 );
使用C++硬编码的方式,画一个点
//在C++代码中写GLSL代码--以字符串的形式
//需要写一个顶点着色器(指定绘制的形状,一般以数组的形式传递)
//还需一个片段着色器(主要指颜色、光照、阴影等光影效果)
//可以将其封装到函数CreateShaderProgram
unsigned int CreateShaderProgram()
{
const char* vShaderSource =
"#version 430 \n"
"void main(void) \n"
"{gl_Position = vec4(0.0, 0.0, 0.0, 1.0);}";
//gl_Position内置的坐标变量,前三个参数对应坐标的(x,y,z),第四个参数为矩阵变换时会用到的值,变换矩阵是4x4,所有我们 //还需要添加一位才能进行矩阵的乘法
const char* fShaderSource =
"#version 430 \n"
"out vec4 color; \n"
"void main(void) \n"
"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";
//创建顶点着色器和片段着色器
unsigned int vShader = glCreateShader(GL_VERTEX_SHADER);
unsigned int fShader = glCreateShader(GL_FRAGMENT_SHADER);
//替换vShader,fShader中的源代码,使用自定义的编码
glShaderSource(vShader, 1, &vShaderSource, NULL);
glShaderSource(fShader, 1, &fShaderSource, NULL);
//对着色器对象进行编译
glCompileShader(vShader);
glCompileShader(fShader);
//创建一个Program链接着色器对象,执行的就是链接有着色器对象的程序
unsigned int vfProgram = glCreateProgram();
//把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
//返回连接好的着色器程序
return vfProgram;
}
//init函数
#define numVAOs 1
unsigned int renderingProgram;
unsigned int vao[numVAOs];//顶点数组,必须要有
void init(GLFWwindow* window)
{
renderingProgram = CreateShaderProgram();
glGenVertexArrays(numVAOs, vao);//生成顶点数组对象名称,用numVAOs存储
glBindVertexArray(vao[0]);//绑定vao[0]
}
//display函数
void display(GLFWwindow* window, double currentTime)
{
glUseProgram(renderingProgram);//激活程序对象
glPointSize(30.0f);//设置点的大小
glDrawArrays(GL_POINTS, 0, 1);//绘制方式,绘制一个点
}
完整.cpp代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>
#define numVAOs 1
unsigned int renderingProgram;
unsigned int vao[numVAOs];//顶点数组,必须要有
unsigned int CreateShaderProgram();
void init(GLFWwindow* window)
{
renderingProgram = CreateShaderProgram();
glGenVertexArrays(numVAOs, vao);//生成顶点数组对象名称,用numVAOs存储
glBindVertexArray(vao[0]);//绑定vao[0]
}
void display(GLFWwindow* window, double currentTime)
{
glUseProgram(renderingProgram);//激活程序对象
glPointSize(30.0f);//设置点的大小
glDrawArrays(GL_POINTS, 0, 1);//绘制方式,绘制一个点
}
unsigned int CreateShaderProgram()
{
const char* vShaderSource =
"#version 430 \n"
"void main(void) \n"
"{gl_Position = vec4(0.0, 0.0, 0.0, 1.0);}";
//gl_Position内置的坐标变量,前三个参数对应坐标的(x,y,z),第四个参数为矩阵变换时会用到的值,变换矩阵是4x4,所有我们 //还需要添加一位才能进行矩阵的乘法
const char* fShaderSource =
"#version 430 \n"
"out vec4 color; \n"
"void main(void) \n"
"{ color = vec4(0.0, 0.0, 1.0, 1.0); }";
//创建顶点着色器和片段着色器
unsigned int vShader = glCreateShader(GL_VERTEX_SHADER);
unsigned int fShader = glCreateShader(GL_FRAGMENT_SHADER);
//替换vShader,fShader中的源代码,使用自定义的编码
glShaderSource(vShader, 1, &vShaderSource, NULL);
glShaderSource(fShader, 1, &fShaderSource, NULL);
//对着色器对象进行编译
glCompileShader(vShader);
glCompileShader(fShader);
//创建一个Program链接着色器对象,执行的就是链接有着色器对象的程序
unsigned int vfProgram = glCreateProgram();
//把之前编译的着色器附加到程序对象上,然后用glLinkProgram链接它们
glAttachShader(vfProgram, vShader);
glAttachShader(vfProgram, fShader);
glLinkProgram(vfProgram);
//返回连接好的着色器程序
return vfProgram;
}
int main()
{
if (!glfwInit())
exit(EXIT_FAILURE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
GLFWwindow* window = glfwCreateWindow(600, 600, "Hello, world", nullptr, nullptr);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK)
exit(EXIT_FAILURE);
glfwSwapInterval(1);
init(window);
while (!glfwWindowShouldClose(window))
{
display(window, glfwGetTime());
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
相关函数
参考:1.GLES2.0中文API-glShaderSource-CSDN博客等相关博客
1.glCreateShader - 创建一个着色器对象
C规范
GLuint glCreateShader(GLenum shaderType);
参数
shaderType
指定要创建的着色器的类型。 只能是GL_VERTEX_SHADER或GL_FRAGMENT_SHADER。
描述
glCreateShader创建一个空的着色器对象,并返回一个可以引用的非零值(shader ID)。着色器对象用于维护定义着色器的源代码字符串。shaderType指示要创建的着色器的类型。 支持两种类型的着色器。 GL_VERTEX_SHADER类型的着色器是一个用于在可编程顶点处理器上运行的着色器。 GL_FRAGMENT_SHADER类型的着色器是一个着色器,旨在在可编程片段处理器上运行。
创建时,着色器对象的GL_SHADER_TYPE参数设置为GL_VERTEX_SHADER或GL_FRAGMENT_SHADER,具体取决于shaderType的值。
注意
与纹理对象一样,着色器对象的名称空间可以在一组上下文中共享,只要上下文的服务器端共享相同的地址空间即可。 如果名称空间跨上下文共享,则也会共享任何附加对象和与这些附加对象关联的数据。
当从不同的执行线程访问对象时,应用程序需要负责跨API调用提供同步。
错误
如果创建着色器对象时发生错误,则此函数返回0。
GL_INVALID_ENUM:shaderType不是一个可接受的值。
2.glGetShaderSource - 从着色器对象返回源代码字符串
C规范
void glGetShaderSource( GLuint shader, GLsizei bufSize, GLsizei length*, GLchar source*);
参数
shader
指定要查询的着色器对象。
bufSize
指定用于存储返回的源代码字符串的字符缓冲区的大小。
length
返回source中返回的字符串的长度(不包括null终止符)。
source
指定用于返回源代码字符串的字符数组。
描述
glGetShaderSource返回由着色器指定的着色器对象的源代码字符串的串联。着色器对象的源代码字符串是先前调用glShaderSource的结果。函数返回的字符串将以null结尾。
glGetShaderSource尽可能多地在源代码中返回源代码字符串,最多可返回bufSize字符。实际返回的字符数(不包括空终止字符)由length指定。如果不需要返回字符串的长度,则可以在length参数中传递NULL值。可以通过调用值为GL_SHADER_SOURCE_LENGTH的glGetShaderiv来获取存储返回的源代码字符串所需的缓冲区大小。
错误
GL_INVALID_VALUE:shader不是OpenGL生成的值。
GL_INVALID_OPERATION:shader不是着色器对象。
GL_INVALID_VALUE:bufSize小于0。
3.glShaderSource-替换着色器对象中的源代码
C规范
void glShaderSource(GLuint shader,GLsizei count,const GLchar * const string*,const GLint length*);
参数
shader
要被替换源代码的着色器对象的句柄(ID)。
count
指定字符串和长度数组中的元素数。
string
指定指向包含要加载到着色器的源代码的字符串的指针数组。
length
指定字符串长度的数组。
描述
对于支持着色器编译器的实现,glShaderSource将着色器中的源代码设置为string指定的字符串数组中的源代码。先前存储在着色器对象中的任何源代码都将被完全替换。数组中的字符串数由count指定。 如果length为NULL,则认为每个字符串都以null结尾。如果length不是NULL,则它指向包含字符串的每个相应元素的字符串长度的数组。length数组中的每个元素可以包含相应字符串的长度(空字符不计为字符串长度的一部分)或小于0的值以表示该字符串为空终止。此时不扫描或解析源代码字符串; 它们只是复制到指定的着色器对象中。
注意
着色器编译器支持是可选的,因此必须在使用之前通过使用参数GL_SHADER_COMPILER调用glGet来查询。glShaderSource,glCompileShader,glGetShaderPrecisionFormat,glReleaseShaderCompiler等在不支持着色器编译器的实现上都将生成GL_INVALID_OPERATION。这样的实现提供了glShaderBinary替代方案,用于提供预编译的着色器二进制文件。
调用glShaderSource时,OpenGL会复制着色器源代码字符串,因此应用程序可以在函数返回后立即释放源代码字符串的副本。
错误
GL_INVALID_OPERATION:不支持着色器编译器
GL_INVALID_VALUE:shader不是OpenGL生成的值
GL_INVALID_OPERATION:shader不是着色器对象
GL_INVALID_VALUE:count比0小
4.glCompileShader - 编译一个着色器对象
C规范
void glCompileShader(GLuint shader);
参数
shader
指定要编译的着色器对象。
描述
对于支持着色器编译器的实现,glCompileShader编译已存储在shader指定的着色器对象中的源代码字符串。
编译状态将存储为着色器对象的状态的一部分。 如果着色器编译时没有错误并且可以使用,则此值将设置为GL_TRUE,否则将设置为GL_FALSE。 可以通过使用参数shader和GL_COMPILE_STATUS调用glGetShaderiv来查询状态值。
由于OpenGL ES着色语言规范指定的多种原因,着色器的编译可能会失败。 无论编译是否成功,都可以通过调用glGetShaderInfoLog从着色器对象的信息日志中获取有关编译的信息。
注意
着色器编译器支持是可选的,因此必须在使用之前通过使用参数GL_SHADER_COMPILER调用glGet来查询。glShaderSource,glCompileShader,glGetShaderPrecisionFormat,glReleaseShaderCompiler等在不支持着色器编译器的实现上都将生成GL_INVALID_OPERATION。这样的实现提供了glShaderBinary替代方案,用于提供预编译的着色器二进制文件。
错误
GL_INVALID_OPERATION:不支持着色器编译器
GL_INVALID_VALUE:shader不是OpenGL生成的值
GL_INVALID_OPERATION:shader不是着色器对象
5.glCreateProgram- 创建一个program(建议不要翻译成“程序”,以免引起与APP的混淆)对象
C规范
GLuint glCreateProgram(void);
描述
glCreateProgram创建一个空program并返回一个可以被引用的非零值(program ID)。 program对象是可以附加着色器对象的对象。 这提供了一种机制来指定将链接已创建program的着色器对象。 它还提供了一种检查将用于创建program的着色器的兼容性的方法(例如,检查顶点着色器和片元着色器之间的兼容性)。 当不再需要作为program对象的一部分时,着色器对象就可以被分离了。
通过调用glCompileShader成功编译着色器对象,并且通过调用glAttachShader成功地将着色器对象附加到program 对象,并且通过调用glLinkProgram成功的链接program 对象之后,可以在program 对象中创建一个或多个可执行文件。
当调用glUseProgram时,这些可执行文件成为当前状态的一部分。 可以通过调用glDeleteProgram删除程序对象。 当program 对象不再是任何上下文的当前呈现状态的一部分时,将删除与program 对象关联的内存。
注意
与纹理对象一样,只要上下文的服务器端共享相同的地址空间,程序对象的名称空间就可以在一组上下文中共享。 如果名称空间跨上下文共享,则也会共享任何附加对象和与这些附加对象关联的数据。
当从不同的执行线程访问对象时,应用程序负责跨API调用提供同步。
错误
如果创建program 对象时发生错误,则此函数返回0。
6.glGenVertexArrays —生成顶点数组对象名称
C规范
void glGenVertexArrays(GLsizei n, GLuint *arrays);
参数
n 指定要生成的顶点数组对象名称的数量。
arrays 指定一个数组,在其中存储生成的顶点数组对象名称。
描述
glGenVertexArrays返回数组中的n个顶点数组对象名称。 不能保证名称形成连续的整数集。 但是,可以保证在调用glGenVertexArrays之前,不会立即使用任何返回的名称。
调用glGenVertexArrays返回的顶点数组对象名称不会被后续调用返回,除非首先使用glDeleteVertexArrays删除它们。
仅出于glGenVertexArrays的目的,将数组中返回的名称标记为已使用,但仅在首次绑定时才获取状态和类型。
错误
如果n为负,则生成GL_INVALID_VALUE。
7.glBindVertexArray — 绑定一个顶点数组对象
C规范
void glBindVertexArray( GLuint array);
参数
array 指定要绑定的顶点数组的名称。
描述
glBindVertexArray将顶点数组对象与名称数组绑定。 array是先前从glGenVertexArrays调用返回的顶点数组对象的名称,或者为0以绑定默认的顶点数组对象绑定。
如果不存在名称为array的顶点数组对象,则在第一次绑定array时创建一个对象。 如果绑定成功,则不会更改顶点数组对象的状态,并且任何先前的顶点数组对象绑定都会中断。
错误
如果array不为零或先前从调用glGenVertexArrays返回的顶点数组对象的名称,则生成GL_INVALID_OPERATION。
运行结果:
在这GLuint是OpenGL中的数据类型,表示的就是无符号整数,所以我用unsigned int。
-
#define numVAOs 1
:这行代码定义了一个宏numVAOs
,并将其值设置为1。这是为了在后面的代码中使用这个宏。 -
unsigned int renderingProgram;
:声明一个无符号整数类型的变量renderingProgram
,用于存储渲染程序的ID。 -
unsigned int vao[numVAOs];
:声明一个无符号整数类型的数组vao
,大小为numVAOs
,用于存储顶点着色器程序的ID。 -
unsigned int createShaderProgram()
:该函数用于创建一个渲染程序。 -
const char* vshaderSource
:用于存储顶点着色器的源代码。 -
const char* fshaderSource
:用于存储片段着色器的源代码。 -
unsigned int vShader = glCreateShader(GL_VERTEX_SHADER);
:调用OpenGL函数glCreateShader
创建一个顶点着色器,并将其赋值给变量vShader
。 -
unsigned int fShader = glCreateShader(GL_FRAGMENT_SHADER);
:调用OpenGL函数glCreateShader
创建一个片段着色器,并将其赋值给变量fShader
。 -
glShaderSource(vShader, 1, &vShaderSource, NULL);
:调用OpenGL函数glShaderSource
将顶点着色器的源代码传递给顶点着色器。 -
glShaderSource(fShader, 1, &fShaderSource, NULL);
:调用OpenGL函数glShaderSource
将片段着色器的源代码传递给片段着色器。 -
glCompileShader(vShader);
:调用OpenGL函数glCompileShader
编译顶点着色器。 -
glCompileShader(fShader);
:调用OpenGL函数glCompileShader
编译片段着色器。 -
unsigned int vfProgram = glCreateProgram();
:调用OpenGL函数glCreateProgram
创建一个渲染程序,并将其赋值给变量vfProgram
。 -
glAttachShader(vfProgram, vShader);
:调用OpenGL函数glAttachShader
将顶点着色器添加到渲染程序中。 -
glAttachShader(vfProgram, fShader);
:调用OpenGL函数glAttachShader
将片段着色器添加到渲染程序中。 -
glLinkProgram(vfProgram);
:调用OpenGL函数glLinkProgram
链接渲染程序。 -
glGenVertexArrays(numVAOs, vao);
:调用OpenGL函数glGenVertexArrays
创建一个顶点着色器数组,并将其赋值给变量vao[0]
。 -
glBindVertexArray(vao[0]);
:调用OpenGL函数glBindVertexArray
将顶点着色器数组绑定到当前的渲染程序中。
在片段着色器中同样有一个变量能让我们访问输入片段的坐标,叫作gl_FragCoord。让我们来使用它基于位置来设置每个像素的值。
const char* fShaderSource =
"#version 430 \n"
"out vec4 color; \n"
"void main(void) \n"
"{ if(gl_FragCoord.x < 255) color = vec4(1.0, 0.0, 0.0, 1.0); else color = vec4(0.0, 0.0, 1.0, 1.0);}";
同时我们需要将该点设置得更大一点。
glPointSize(430.0f);
运行结果: