本文主要还是解决图元绘制中的渲染问题-着色器,着色器的使用包含两个部分:(1)OpenGL的API调用,(2)GLSL语言及其语法;本文主要解释OpenGL的API使用(通过grew扩展使用);GLSL那是另外一个话题。
1. OpenGL着色器使用过程与例子;
2. OpenGL着色器相关函数的解释;
主要过程为:
1.编写Shader:
1)顶点Shader(vetexShader);
2)片Shader(ragmentShader);
2.创建顶点着色器对象
1)创建vertexShader对象;glCreateShader
2)加载vertexShader脚本到vertexShader对象;glShaderSource
3)编译vertexShader脚本;glCompileShader
3.创建片着色器
1)创建fragmentShader对象;glCreateShader
2)加载fragmentShader脚本到fragmentShader对象;glShaderSource
3)编译fragmentShader脚本;glCompileShader
4.创建着色器程序
1)创建程序对象;glCreateProgram
2)添加vertexShader对象到着色器程序;glAttachShader
3)添加fragmentShader对象到着色器程序;glAttachShader
4)链接着色器程序,以备渲染管道调用;glLinkProgram
5.在开启顶点属性之前需要创建一个顶点数组:(用于顶点属性的处理)
加载OpenGL4.2的版本
- 提前的说明
在其他系统中,实际上绘制点、线段、三角形等,都可以输出效果; 在Mac的OpenGL中,如果没有使用着色器,则线段、三角形看不见输出;
在默认情况下,加载的OpenGL的库版本是2.1,但这里讲解的是OpenGL使用4.1
本文重点不是GLSL,旦会用到GLSL,可以直接放过。
获取当前系统的OpenGL的版本
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
int main(int argc, char const *argv[]){
glfwInit();
GLFWwindow *window = glfwCreateWindow(800, 600, "顶点:Vertex", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK){
printf("OpenGL初始化失败:glew\n");
exit(-1);
}
// ********************************
const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
const GLubyte* video = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
const GLubyte* OpenGLVersion = glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现厂商的名字:%s\n", name);
printf("显示器显卡:%s\n", video);
printf("OpenGL实现的版本号:%s\n", OpenGLVersion);
// 释放
glfwTerminate();
return 0;
}
// 编译命令:g++ -omain gl00_opengl_version.cpp -lglfw -lglew -framework opengl
- 说明:
- 尽管上面不需要Window,但是需要创建上下文,在glfw模块中创建上下文与创建窗体是一起的。
- 本文使用的系统的OpenGL环境如下:
OpenGL实现厂商的名字:Intel Inc.
显示器显卡:Intel(R) Iris(TM) Graphics 6100
OpenGL实现的版本号:2.1 INTEL-12.9.22
系统安装的OpenGL版本查看
- 如果想知道硬件支持与系统安装的OpenGL版本,可以在硬件与系统提供商的官网查看。比如苹果的OpenGL版本可以官网查看:
https://support.apple.com/zh-cn/HT202823
设置最高的OpenGL版本
- 在创建上下文之前设置,通过窗体创建前的提示设置即可
glfwWindowHint
。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
int main(int argc, char const *argv[]){
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // 主版本
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // 副版本(可选)
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 设置苹果系统向后兼容
GLFWwindow *window = glfwCreateWindow(800, 600, "顶点:Vertex", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK){
printf("OpenGL初始化失败:glew\n");
exit(-1);
}
// ********************************
const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
const GLubyte* video = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
const GLubyte* OpenGLVersion = glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现厂商的名字:%s\n", name);
printf("显示器显卡:%s\n", video);
printf("OpenGL实现的版本号:%s\n", OpenGLVersion);
// 释放
glfwTerminate();
return 0;
}
// 编译命令:g++ -omain gl00_opengl_version.cpp -lglfw -lglew -framework opengl
- 设置后输出位设置的版本:
OpenGL实现厂商的名字:Intel Inc.
显示器显卡:Intel(R) Iris(TM) Graphics 6100
OpenGL实现的版本号:4.1 INTEL-12.9.22
绘制基本图元
绘制图元的基本模式
- 定义顶点数据:类型float数组,
- 创建顶点缓冲区对象:
glGenBuffers
函数; - 指定顶点缓冲区数据类型:
glBindBuffer
函数; - 设置顶点缓冲区的数据:
glBufferData
函数; - 启用指定索引的顶点属性:
glEnableVertexAttribArray
函数; - 定义通用顶点属性数据数组:
glVertexAttribPointer
函数; - 绘制图元:
glDrawArrays
函数
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
int main(int argc, char const *argv[]){
glfwInit();
GLFWwindow *window = glfwCreateWindow(800, 600, "三角形", NULL, NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK){
exit(-1);
}
// 1. 定义顶点数据;
float vertices[] = {
-0.5f, -0.5f, 0.0f, // 第一个点
0.5f, -0.5f, 0.0f, // 第二个点
0.0f, 0.5f, 0.0f // 第三个点
};
// 2. 生成缓冲区对象名;
GLuint vertex_buffer_object;
glGenBuffers(1, &vertex_buffer_object);
// 3. 绑定命名缓冲区对象;
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer_object);
// 4. 创建并初始化缓冲区对象的数据存储;
glBufferData(
GL_ARRAY_BUFFER,
sizeof(vertices),
vertices,
GL_STATIC_DRAW);
// 5. 开启顶点属性;
glEnableVertexAttribArray(0);
// 6. 定义通用顶点属性数据数组;
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
while (! glfwWindowShouldClose(window)){
glClear(GL_COLOR_BUFFER_BIT);
// 7. 绘制;
glDrawArrays(GL_TRIANGLES, 0, 3);
glfwSwapBuffers(window);
glfwPollEvents();
}
glDeleteBuffers(1, &vertex_buffer_object);
glfwTerminate();
return 0;
}
// 编译命令:g++ -omain gl02_vertexes.cpp -lglfw -lglew -framework opengl
- 运行是看不到三角形滴。
使用着色器
- 着色器使用了GLSL语言,这是一个类似C或者C++的语言,着色器主要用来处理两个事情:
- 多输入的顶点坐标进行变换处理(因为输入的3D坐标,但实际屏幕是2D,这个需要转换)
- 把坐标最终转换为像素(RGBA构成的像素)
- 除了GLSL语言外,剩下的就是套路(俗称编程模式)。
着色器编程模式
-
编写Shader:
- 顶点Shader(vetexShader);
- 片Shader(ragmentShader);
-
创建顶点着色器对象
- 创建vertexShader对象;
glCreateShader
- 加载vertexShader脚本到vertexShader对象;
glShaderSource
- 编译vertexShader脚本;
glCompileShader
- 创建vertexShader对象;
-
创建片着色器
- 创建fragmentShader对象;
glCreateShader
- 加载fragmentShader脚本到fragmentShader对象;
glShaderSource
- 编译fragmentShader脚本;
glCompileShader
- 创建fragmentShader对象;
-
创建着色器程序
- 创建程序对象;
glCreateProgram
- 添加vertexShader对象到着色器程序;
glAttachShader
- 添加fragmentShader对象到着色器程序;
glAttachShader
- 链接着色器程序,以备渲染管道调用;
glLinkProgram
- 创建程序对象;
-
在开启顶点属性之前需要创建一个顶点数组:(用于顶点属性的处理)
- 创建一个顶点数组对象;
glGenVertexArrays
- 绑定顶点数组对象;
glBindVertexArray
- 注意:这个与顶点缓冲区不同,不需要绑定数据,因为这个顶点数组内部存放数据;
- 创建一个顶点数组对象;
- 注意:
- 着色器程序的创建与顶点缓冲区的创建没有先后关系,但是使用着色器程序的顶点在渲染过程需要顶点数组。
着色器与OpenGL对应版本关系
/*
OpenGL Version GLSL Version
2.0 110
2.1 120
3.0 130
3.1 140
3.2 150
3.3 330
4.0 400
4.1 410
4.2 420
4.3 430
*/
着色器使用代码
- 代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
/*
OpenGL Version GLSL Version
2.0 110
2.1 120
3.0 130
3.1 140
3.2 150
3.3 330
4.0 400
4.1 410
4.2 420
4.3 430
*/
// 苹果的OpenGL版本可以官网查看:https://support.apple.com/zh-cn/HT202823
// Intel(R) Iris(TM) Graphics 6100:
// MacBook Pro(视网膜显示屏,13 英寸,2015 年初) Intel Iris Graphics 6100 4.1(OpenGL版本)
// GLSL着色器语言
/*
#version 410 core
layout (location = 0) in vec3 aPos;
void main(){
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
*/
const char *vertexShaderSource = ""
"#version 410 core\n" // OpenGL版本,核心模式
"layout (location = 0) in vec3 aPos;\n" // 顶点属性(输入)
"void main(){\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0"; // 空字符
const char *fragmentShaderSource = ""
"#version 410 core\n"
"out vec4 FragColor;\n" // 颜色属性(输出变量)
"void main(){\n"
" FragColor = vec4(1.0f, 0.0f, 0.0f, 1.0f);\n" // 固定颜色输出
"}\n\0";
int main(int argc, char const *argv[]){
glfwInit();
// 下面代码使用最新版本的OpenGL(一定需要glfw初始化后调用,下面版本不设置,会导致某些功能不支持,会导致段错误)
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); // 主版本
// glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); // 副版本(可选)
GLFWwindow *window = glfwCreateWindow(
800,
600,
"着色器",
NULL, //glfwGetPrimaryMonitor(),
NULL);
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK){
printf("OpenGL初始化失败:glew\n");
exit(-1);
}
/////////////////////////////
// ********************************
// 1. 顶点着色器对象
unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);
// 2. 编译顶点着色器
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// --------------------------------
// 1. 片着色器对象
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
// 2. 片着色器
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// --------------------------------
// 1. 着色器程序对象
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
// 2. 链接着色器程序
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// ---------------------------------
// 1. 激活着色器程序
// glUseProgram(shaderProgram);
// 2. 激活后,释放前面分配的内存
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// ********************************
const GLubyte* name = glGetString(GL_VENDOR); //返回负责当前OpenGL实现厂商的名字
const GLubyte* video = glGetString(GL_RENDERER); //返回一个渲染器标识符,通常是个硬件平台
const GLubyte* OpenGLVersion = glGetString(GL_VERSION); //返回当前OpenGL实现的版本号
printf("OpenGL实现厂商的名字:%s\n", name);
printf("显示器显卡:%s\n", video);
printf("OpenGL实现的版本号:%s\n", OpenGLVersion);
///////////////////////////
// 1. 定义顶点数据;
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
// 2. 生成缓冲区对象名;
GLuint vertex_buffer_object; // 定义存储缓冲区对象名的变量
glGenBuffers(
1, // 缓冲区个数
&vertex_buffer_object); // 返回的缓冲区对象名
// 3. 绑定命名缓冲区对象;
glBindBuffer(
GL_ARRAY_BUFFER, // 缓冲区类型是:顶点属性(Vertex attributes)
vertex_buffer_object); // 缓冲区对象名
// 4. 创建并初始化缓冲区对象的数据存储;
glBufferData(
GL_ARRAY_BUFFER, // 指定缓冲区对象绑定的目标
sizeof(vertices), // 指定缓冲区对象的新数据存储区的大小
vertices, // 定指向将复制到数据存储中进行初始化的数据的指针,如果不复制任何数据,则指定为空。
GL_STATIC_DRAW); // 数据存储内容将重复修改并多次使用,并用作GL绘图和图像操作的源。
/////////////////////////////////////////////
// ------------------------------------
unsigned int VAO;
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// ------------------------------------
// 1. 开启顶点属性;
glEnableVertexAttribArray(
0); // 启用的顶点属性的索引。;
// 2. 定义通用顶点属性数据数组;
glVertexAttribPointer(
0, // 指定通用顶点属性的索引;
3, // 指定每个顶点属性的维数;
GL_FLOAT, // 指定数组中每个组件的数据类型;
GL_FALSE, // 指定访问固定点数据值时,是否规范化;
3 * sizeof(float), // 指定顶点属性之间的步长;
0); // 指定当前绑定到GL_ARRAY_BUFFER目标的存储区地址的偏移地址;
glBindBuffer(GL_ARRAY_BUFFER, 0);
// glBindVertexArray(0); // 解除顶点数组对象的绑定,便于后面绑定使用;
///////////////////////////////////////////
while (! glfwWindowShouldClose(window)){
// glClearColor(1.0f, 1.0f, 1.0f, 1.0f); // 可以设置清屏颜色;
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shaderProgram);
// 3. 绘制;
glDrawArrays(
GL_TRIANGLES, // 渲染的基元类型(点);
0, // 指定已启用数组中的起始索引;
3); // 渲染的点的个数;
///////////////////////////
glfwSwapBuffers(window);
glFlush();
// glfwPollEvents();
glfwWaitEvents();
}
// 释放
// 4. 绘制完成,禁用顶点缓冲,这是为了性能;
glDisableVertexAttribArray(
0 // 指定禁用的顶点属性的索引;
);
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &vertex_buffer_object);
glfwTerminate();
return 0;
}
// 编译命令:g++ -omain gl03_shader_step.cpp -lglfw -lglew -framework opengl
- 运行结果
着色器编程使用的函数说明
glCreateShader函数
- 函数定义
- glCreateShader创建一个空的shader对象,并返回一个可以引用它的非零值。
- Shader对象用于维护定义Shader的源代码字符串。
- shaderType指示要创建的Shader的类型。
- OpenGL支持五种类型的Shader。
GLuint glCreateShader( GLenum shaderType);
- 函数参数:
- GLenum shaderType:指定Shader的类型,GL可以申明的类型包含:
- GL_COMPUTE_SHADER
- GL_VERTEX_SHADER
- GL_TESS_CONTROL_SHADER 与 GL_TESS_EVALUATION_SHADER
- GL_GEOMETRY_SHADER
- GL_FRAGMENT_SHADER.
- 注意:
- 这里创建两种最基本的Shader。
- GLenum shaderType:指定Shader的类型,GL可以申明的类型包含:
glShaderSource函数
- 函数定义
void glShaderSource(
GLuint shader,
GLsizei count,
const GLchar **string,
const GLint *length);
-
函数参数
-
GLuint shader
:Shader对象; -
GLsizei count
:字符串个数; -
const GLchar **string
:字符串数组(不是数组字符串:双指针); -
const GLint *length
:字符串长度的数组;如果是空,则表示字符串使用空表示结束;
-
-
说明:
- 当调用glShaderSource时,OpenGL复制Shader源代码字符串,因此应用程序可以在函数返回后立即释放其源代码字符串的副本。
glCompileShader函数
- 函数定义
void glCompileShader( GLuint shader);
-
函数参数
- GLuint shader:已经创建,并copy了Shader源代码的Shader对象。
-
说明:
glCompileShader编译存储在Shader对象中的源代码字符串。
编译状态将作为着色器对象状态的一部分存储。如果在没有错误的情况下编译了明暗器并准备好使用,则此值将设置为GL_TRUE,否则设置为GL_FALSE。
编译状态可以通过调用带有shade参数r和GL_COMPILE_STATUS参数的glGetShader函数进行查询。
由于OpenGL着色语言规范指定的许多原因,着色程序的编译可能会失败。无论编译是否成功,都可以通过调用glGetShaderInfo从着色器对象的信息日志中获取有关编译的信息(包含编译错误的位置)。
glCreateProgram函数
- 函数定义
GLuint glCreateProgram( void);
-
函数返回值
- GLuint:返回创建的程序对象。
-
说明:
- 创建的程序是空的,可以指定Shader,并链接成程序。
glAttachShader函数
- 函数定义
void glAttachShader(
GLuint program,
GLuint shader);
-
函数参数
- GLuint program:创建好的程序对象;
- GLuint shader:需要添加到程序的Shader
-
说明
- 删除Shader使用
glDetachShader
函数。
- 删除Shader使用
glLinkProgram函数
- 函数定义
void glLinkProgram( GLuint program);
- 函数参数
- GLuint program:创建好,并添加Shader的程序对象。
glGenVertexArrays函数
- 函数定义
void glGenVertexArrays(
GLsizei n,
GLuint *arrays);
- 函数参数
-
GLsizei n
:创建的顶点数组个数; -
GLuint *arrays
:返回创建的顶点数组对象数组;
-
glBindVertexArray函数
- 函数定义
void glBindVertexArray( GLuint array);
-
函数参数
- GLuint array:需要绑定的顶点数组对象;
-
说明:
glBindVertexArray使用名称数组绑定顶点数组对象。参数array是先前从调用glGenVertexArrays返回的顶点数组对象的名称
或者为零以中断现有的顶点数组对象绑定。
如果不存在名为array的顶点数组对象,则在数组第一次绑定时创建一个顶点数组对象。
如果绑定成功,则不会更改顶点数组对象的状态,并且任何先前的顶点数组对象绑定都将断开。