因为 opengl 是一个标准,各个显卡厂商的实现基于硬件,不一致 所以如果想使用现代的 opengl,需要从显卡驱动里找到那些最新的函数 因此需要 glew 或者 glad 等库帮你找(他们无非是帮你调 win32api 找一下) 虽然系统头文件里也有 gl.h,但是这个太古董了,不够现代,函数不全
按照我的理解。计算机最开始是冯诺依曼模型,只包含 cpu 内存硬盘。但是缺乏人机交互能力,因此引入了输出输出设备,比如鼠标和键盘。这些设备由 cpu 直接控制,cpu 通过轮询等机制获取鼠鼠的输入。但是因为鼠鼠硬件状态不一致,因此需要驱动程序。(大部分鼠鼠都符合默认标准,系统也提供了默认驱动,所以插上去能直接用)再后来,显卡出现了,显卡是更复杂的设备,具备了与 cpu 协作的能力。显卡可以理解为一个不同架构的 cpu,比如 x46,x68 这样子。它上面也执行代码,这些代码也需要像 C 那样写出来,然后编译。那么这个代码是什么呢,我的理解是 glsl 或者 hlsl,然后各个显卡厂商,会实现自己的编译器(就好比 intel 会实现编译器,但是因为显卡生态的原因,开源的不多),这个编译器就存在于显卡驱动里
DirectX 通过驱动程序(如显示驱动的用户模式部分)与硬件通信。它定义了一套标准的接口,硬件厂商需要实现这些接口(即编写驱动程序)来支持 DirectX。所以 DirectX 并不直接封装 OpenGL。它们都是与显卡硬件通信的“并列”的 API。
opengl 是一个状态机 你需要做的是设置一系列状态 你应该在 GPU VRAM 内设置顶点缓冲区,设置 Shader,它去绘制三角形 而不是告诉 opengl,给我画一个三角形,我给你一些参数 因为它知道自己需要画三角形
(其实从这里看,glsl 的函数不超过 100 个。按照 ai 说法,核心大概 100 个,每个厂家有自己的扩展)
绘制的流程大概是:(你把 gpu 当作 cpu 来看待)
我们把数据送到 GPU 的缓冲区中(GPU 内存)
然后 shader 着色器指明了每个点应该如何绘制
并且 shader 必须知道缓冲区内的内存布局,否则它不知道缓冲区里数据是怎么摆放的
(比如有没有法向、纹理等等)
布局的定义在 CPU 中,使用 C++定义
Vertex 顶点:顶点的属性之一是坐标,除此之外,还有颜色,纹理,法向
有时候我们即使不写着色器,三角形也出现了
这是因为 gpu 提供了默认的着色器
顶点着色器和片段着色器(也称之为像素着色器)
数据送给 gpu->顶点->片段->显示(这是最简化的流程)
我们送给 gpu 的每个顶点,都要走一次顶点着色器,决定一个顶点显示在哪
片段着色器会为显示器的每个像素调用一次,决定该像素的颜色
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
static unsigned int CompileShader(const std::string& source, unsigned int type)
{
unsigned int id = glCreateShader(type);
const char* src = source.c_str();
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
int result;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE)
{
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
char* message = new char[length];
glGetShaderInfoLog(id, length, &length, message);
std::cout << "Fail" << ((type == GL_VERTEX_SHADER) ? "vertex" : "fragment" )<< std::endl;
std::cout << message << std::endl;
glDeleteShader(id);
return 0;
}
return id;
}
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(vertexShader, GL_VERTEX_SHADER);
unsigned int fs = CompileShader(fragmentShader, GL_FRAGMENT_SHADER);
glAttachShader(program, vs); // shader 也是需要链接
glAttachShader(program, fs);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vs);
glDeleteShader(fs);
return program;
}
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
if (glewInit() != GLEW_OK)
{
return -1;
}
// 这是我们在 cpu 中的缓冲区
float position[6] = {
-0.5, -0.5,0, 0.5,0.5, -0.5
};
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);
// 通过以上函数,我们构造了一个在 gpu 中的缓冲区,但是此时,gpu 不知道这个缓冲区里的内容如何解释
glEnableVertexAttribArray(0);
// 为缓冲区指明内容如何解释
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * 4, 0);
/*
第一个参数是你自己指定的顶点的属性的索引,什么意思,就是之后你可以在 glsl 里,利用这个整数索引,拿到这块数据
第二个是这个属性值的数量,我这里是位置,所以是 2
第三个是类型
第四个是是否要单位化(对于颜色)
第五个是,每个顶点的所有属性占多少字节。因为复杂的时候,顶点不止有位置,还有颜色,那么它第一个顶点取完颜色,需要跳跃到第二个顶点取颜色
第六个是,当前这个属性,对于顶点开头的字节偏移量。因为我这里就一个位置,所以不会偏移
*/
std::string vertexShader =
"#version 330 core\n"
"\n"
"layout(location = 0) in vec4 position;"
"\n"
"void main()\n"
"{\n"
" gl_Position = position;\n"
"}\n";
std::string fragmentShader =
"#version 330 core\n"
"\n"
"layout(location = 0) out vec4 color;"
"\n"
"void main()\n"
"{\n"
" color = vec4(1,0,0,1);\n"
"}\n";
unsigned int shader = CreateShader(vertexShader, fragmentShader);
glUseProgram(shader);
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
/* Render here */
glClear(GL_COLOR_BUFFER_BIT);
/*glBegin(GL_TRIANGLES);
glVertex2f(-0.5, -0.5);
glVertex2f(0, 0.5);
glVertex2f(0.5, -0.5);
glEnd();*/
glDrawArrays(GL_TRIANGLES, 0, 3); // 这里应该就是设置图元,到底是把这几个点画成三角形还是离散的点
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}关于 glsl 语言
1、
core 表示我们正在使用核心模式。即现代化 opengl,实际上用老的 openglAPI 反而会报异常
#version 330 core2、
vec3 vec4 这种是 glsl 里预定义的结构体,是 float 型,由于它的长度固定,你可以直接访问其元素,元素依次是 x y z w (w 用于透视划分,并不是 3D 坐标,可以给 1)
gl_Position = vec4(position.x, position.y, position.z, 1.0);3、
in 关键字
大概就是从之前的布局里取数据
layout (location = id) in 类型 变量名字;
layout (location = 0) in vec3 position;out 关键字
应该就是做最终输出
因为每个着色器都需要有输入输出,这样才能构建管线
4、
uniform
类似全局变量,任何着色阶段的着色器都可用。从 cpu 发送
关于两个着色器:
vertex 着色器的目标是确定顶点位置,所以最终目标应该给全局变量 gl_Position 赋值
fragment 着色器的目标是确定像素的颜色,所以最终目标是 输出一个 vec4 类型的变量,我们例子里称之为了 color
两个着色器之间能够传递变量,同名同类型即可,不需要显式声明。只需要是 vertex 的输出,fragment 就能用
vbo 顶点缓冲区:如果画一个三角形,这很好
ibo 索引缓冲区:如果我们画正方形,它是两个三角形,但是实际上只有四个顶点。我们利用索引来共享顶点
调查错误(黑屏原因):
1、glGetError 函数 获取错误代码
如果多个标志记录了错误,glGetError则返回并清除任意错误标志值。因此,如果需要重置所有错误标志,glGetError则应始终循环调用 ,直到返回。GL_NO_ERROR
因为每个函数都要做,所以我们可以定义一个宏,每次调用函数都用宏调用
2、自 4.3,可以给 opengl 一个回调,当错误发生时,自动进入回调
顶点数组 vao(注意这不是顶点缓冲区)
当我们需要绘制多个东西时,每个东西,都有顶点缓冲,索引缓冲,指定布局,这很麻烦
现在把 绑定顶点缓冲区和指定布局 二合一,成为绑定顶点数组对象
#define GLEW_STATIC
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>
#include <sstream>
static void GLClearError()
{
while (glGetError()!= GL_NO_ERROR)
{
;
}
}
static void GLCheckError()
{
while (GLenum error = glGetError())
{
std::cout << error << std::endl;
}
}
struct ShaderProgramSource
{
std::string VertexSource;
std::string FragmentSource;
};
static ShaderProgramSource ParseShader(const std::string& filepath)
{
enum class ShaderType
{
NONE = -1,
VERTEX = 0,
FRAGMENT = 1
};
std::ifstream stream(filepath);
std::string line;
std::stringstream ss[2];
ShaderType type = ShaderType::NONE;
while (getline(stream, line))
{
if (line.find("#shader") != std::string::npos)
{
if (line.find("vertex") != std::string::npos)
{
type = ShaderType::VERTEX;
}
else if (line.find("fragment") != std::string::npos)
{
type = ShaderType::FRAGMENT;
}
}
else
{
ss[(int)type] << line << "\n";
}
}
return { ss[0].str(), ss[1].str() };
}
static unsigned int CompileShader(const std::string& source, unsigned int type)
{
unsigned int id = glCreateShader(type);
const char* src = source.c_str();
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
int result;
glGetShaderiv(id, GL_COMPILE_STATUS, &result);
if (result == GL_FALSE)
{
int length;
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
char* message = new char[length];
glGetShaderInfoLog(id, length, &length, message);
std::cout << "Fail" << ((type == GL_VERTEX_SHADER) ? "vertex" : "fragment") << std::endl;
std::cout << message << std::endl;
glDeleteShader(id);
return 0;
}
return id;
}
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{
unsigned int program = glCreateProgram();
unsigned int vs = CompileShader(vertexShader, GL_VERTEX_SHADER);
unsigned int fs = CompileShader(fragmentShader, GL_FRAGMENT_SHADER);
glAttachShader(program, vs); // shader 也是需要链接
glAttachShader(program, fs);
glLinkProgram(program);
glValidateProgram(program);
glDeleteShader(vs);
glDeleteShader(fs);
return program;
}
int main(void)
{
GLFWwindow* window;
/* Initialize the library */
if (!glfwInit())
return -1;
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
//我们设置为核心模式,这样它不会自己创建顶点数组
/* Create a windowed mode window and its OpenGL context */
window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);
if (!window)
{
glfwTerminate();
return -1;
}
/* Make the window's context current */
glfwMakeContextCurrent(window);
glfwSwapInterval(1);
if (glewInit() != GLEW_OK)
{
return -1;
}
// 这是我们在 cpu 中的缓冲区
float position[] = {
-0.5 , -0.5,
0.5 , -0.5,
0.5 , 0.5,
-0.5 , 0.5
};
unsigned int indices[] = {
0,1,2,
2,3,0
};
unsigned int vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
unsigned int buffer;
glGenBuffers(1, &buffer);
glBindBuffer(GL_ARRAY_BUFFER, buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(position), position, GL_STATIC_DRAW);
// 通过以上函数,我们构造了一个在 gpu 中的缓冲区,但是此时,gpu 不知道这个缓冲区里的内容如何解释
glEnableVertexAttribArray(0);
// 为缓冲区指明内容如何解释
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * 4, 0);
/*
第一个参数是你自己指定的顶点的属性的索引,什么意思,就是之后你可以在 glsl 里,利用这个整数索引,拿到这块数据
第二个是这个属性值的数量,我这里是位置,所以是 2
第三个是类型
第四个是是否要单位化(对于颜色)
第五个是,每个顶点的所有属性占多少字节。因为复杂的时候,顶点不止有位置,还有颜色,那么它第一个顶点取完颜色,需要跳跃到第二个顶点取颜色
第六个是,当前这个属性,对于顶点开头的字节偏移量。因为我这里就一个位置,所以不会偏移
当使用 VAO 时,我们不需要指明到底那个 vbo 应该怎么榜到那个 vao 里
只要在 glVertexAttribPointer 指定布局之前,绑定了 vao,此布局就会自动记录到 vao 里
*/
unsigned int ibo;
glGenBuffers(1, &ibo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
ShaderProgramSource source = ParseShader("D:\\Learn\\gl\\gl\\Basic.shader");
unsigned int shader = CreateShader(source.VertexSource, source.FragmentSource);
glUseProgram(shader);
// 当绑定一个着色器后,才可以设置 uniform,否则不知道给谁
int location = glGetUniformLocation(shader, "u_Color");
_ASSERT(location != -1);
glUniform4f(location, 0.2, 0.3, 0.8, 1);
glBindVertexArray(0);
glUseProgram(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
float r = 0;
float increment = 0.05;
/* Loop until the user closes the window */
while (!glfwWindowShouldClose(window))
{
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(shader);
glUniform4f(location, r, 0.3, 0.8, 1);
glBindVertexArray(vao);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
if (r > 1)
increment *= -1;
if(r <0)
increment *= -1;
r += increment;
/*glBegin(GL_TRIANGLES);
glVertex2f(-0.5, -0.5);
glVertex2f(0, 0.5);
glVertex2f(0.5, -0.5);
glEnd();*/
/*
* glDrawArrays
第一个参,这里应该就是设置图元,到底是把这几个点画成三角形还是离散的点
第二个参,从第几个开始画
第三个参,画几个
*/
/* Swap front and back buffers */
glfwSwapBuffers(window);
/* Poll for and process events */
glfwPollEvents();
}
glfwTerminate();
return 0;
}