OpenGL Chan视频学习-11 Uniforms in OpenGL
bilibili视频链接:
【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p=5&vd_source=44b77bde056381262ee55e448b9b1973
函数网站:
docs.gl
说明:
1.之后就不再单独整理网站具体函数了,网站直接翻译会更直观也会有更多注意点。直接通过csdn索引查找反而会慢。
2.代码区域会单独注释功能参数返回值和相关注意事项。
3.课程学习从4-本节,如果有些函数没有注释可以看专栏里面的前面发表的文章,一般有解释。
4.如果觉得代码注释白色字体不太直观可以直接copy到相应软件看。
5.有两种版本的可供查看:注释全面的和注释简洁版的,可以在索引里面找到相关代码查看。
6.希望能帮到你。
7.有错误请跟我说明一下,可能整理的时候没有检查好。
一、知识点整理
1.1获取数据方式
1.1.1分类
- 属性
- Uniforms
1.1.2区别
以下是两种从CPU到GPU获取数据的方式(属性、uniform)之间的联系和区别的表格形式总结:
(来自文心一言)
特性 | 属性(Attribute) | Uniform |
---|---|---|
定义 | 用于向顶点着色器传递每个顶点的独立数据,如位置、颜色、纹理坐标等。 | 用于向所有着色器阶段(顶点、片段等)传递全局一致的数据,如变换矩阵、光照参数等。 |
作用范围 | 仅作用于顶点着色器,每个顶点有独立的一份数据。 | 可作用于顶点着色器和片段着色器,所有顶点或片段共享同一份数据。 |
数据更新频率 | 通常在每次绘制调用(Draw Call)时更新,每个顶点可以有不同的值。 | 在渲染一帧或多次绘制调用期间保持不变,适合频繁使用但较少变化的数据。 |
性能影响 | 数据量随顶点数量增加而增加,频繁更新大量顶点属性可能影响性能。 | 数据量较小,更新频率低,性能开销相对较小。 |
使用场景 | 传递需要逐顶点变化的数据,如模型顶点坐标、顶点颜色、法线等。 | 传递需要全局共享的数据,如模型视图投影矩阵、光照参数、材质属性等。 |
API示例(OpenGL) | 使用glVertexAttribPointer 绑定顶点属性缓冲区,通过glVertexAttrib 系列函数启用或禁用属性。 | 使用glGetUniformLocation 获取uniform变量的位置,通过glUniform 系列函数设置uniform变量的值。 |
缓冲区类型 | 通常存储在顶点缓冲区对象(VBO)中。 | 通常直接存储在着色器程序中,或通过uniform缓冲区对象(UBO)或着色器存储缓冲区对象(SSBO)管理。 |
灵活性 | 灵活性高,适合处理每个顶点不同的数据。 | 灵活性较低,但适合处理全局一致的数据,且可以通过UBO/SSBO提高组织效率。 |
内存占用 | 内存占用随顶点数量线性增长。 | 内存占用固定,与顶点数量无关。 |
适用数据类型 | 适合存储每个顶点独立的数据,如向量、标量等。 | 适合存储全局一致的矩阵、向量、标量等数据。 |
联系
- 数据传递:两者都是从CPU向GPU传递数据的方式,用于着色器程序的运行。
- 着色器访问:两者都可以在着色器中被访问,但作用范围和生命周期不同。
- 优化目标:两者都旨在提高渲染性能,通过合理使用可以减少CPU-GPU之间的数据传输开销。
区别
- 作用范围:属性是逐顶点的,uniform是全局的。
- 数据更新频率:属性可能频繁更新,uniform通常较少更新。
- 性能开销:属性可能因数据量大而影响性能,uniform性能开销较小。
- 使用场景:属性适合逐顶点数据,uniform适合全局共享数据。
通过合理选择属性或uniform,可以优化渲染性能,减少不必要的CPU-GPU数据传输。
1.2Uniform
1.2.1是什么
从CPU获取数据的方式,希望能在CPU端口定义数据。在这里,从C++到我们的着色器,可以当成
也有可能通过顶点缓冲区从CPU获取数据到GPU。
1.2.2调用时机
每一次绘制时调用。在调用glDrawElements或glDrawArray或任何用来绘图的东西之前设置。
1.2.3注意点
- 在绘制之前就已经设置好了,每次绘制时设置差异很大
1.2.4应用
1.2.4.1代码+步骤
进入着色器文件Basic.shader
#shader vertex
#version 330 corelayout(location = 0) in vec4 position;void main()
{gl_Position = position;
};#shader fragment
#version 330 corelayout(location = 0) out vec4 color;//分配矩形的每个像素的每个片段的输出颜色为该统一值
uniform vec4 u_Color;void main()
{color = vec4 u_Color;
};
从Application.cpp中设置uniform
在着色器绑定之后调用glUseProgram
区别在我们实际发送的数据和我们有多少组件
//功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置//参数:1.shader: 着色器程序对象//参数:2.name: 变量名//返回; 如果 uniform 变量不存在,则返回 -1;存在,则返回它的位置,即它的索引。GLCall(int location = glGetUniformLocation(shader, "u_Color"));//着色器程序(vertex 或 fragment)中声明了某个 uniform 变量(例如 "u_Color"),// 但是在实际的 GLSL 代码中没有使用这个变量,编译器可能会优化掉这个变量,// 从而导致 glGetUniformLocation 返回 -1。//检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。ASSERT(location != -1);//功能:设置颜色// 参数:1.location: 着色器程序中 uniform 变量的位置// 参数:2.v0: 第一个分量// 参数:3.v1: 第二个分量// 参数:4.v2: 第三个分量// 参数:5.v3: 第四个分量// 作用:设置 uniform 变量的值,这里设置了 u_Color 的值为红色(0.2, 0.3, 0.8, 1.0)// GL_FLOAT_VEC4 表示该 uniform 变量是一个四维向量,每个分量的类型为 float。// 注意:这里的颜色值是通过 glGetUniformLocation 函数获取的,而不是直接写死的。// 这样做的好处是,当着色器程序中的 uniform 变量的名称发生变化时,// 我们只需要修改这里的名称,而不需要修改渲染代码。GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));
1.2.4.2运行结果
1.3变色动画
1.3.1代码+步骤(int main函数修改)
修改刷新频率
//设置刷新频率
//参数:1表示程序会等待一个垂直重绘周期之后再进行缓冲区交换。大概可以理解为每秒60帧。
// 如果显示器的刷新率不是 60 Hz,而是其他值(例如 75 Hz 或 120 Hz),
// glfwSwapInterval(1); 会根据显示器的实际刷新率来调整程序的帧率。
// glfwSwapInterval 函数通常只接受 0、1 或 -1 作为参数,分别代表无同步、垂直同步以及可自适应同步。
glfwSwapInterval(1);
设置r值使其能动态调整颜色
设置increment实现阶段等间隔变化
float r = 0.0f;float increment = 0.05f;
通过在while循环里直接设置颜色和颜色浮点值来改变
GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))//功能:绘制三角形GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));//更新颜色//实现颜色的循环变化if(r > 1.0f)increment = -0.05f;else if(r < 0.0f)increment = 0.05f;r += increment;
1.3.2运行效果
蓝色粉色渐变循环
二、完整代码
2.1 将shader数据从c++传入
2.1.1 完全注释代码
Basic.shader
#shader vertex
#version 330 corelayout(location = 0) in vec4 position;void main()
{gl_Position = position;
};#shader fragment
#version 330 corelayout(location = 0) out vec4 color;//分配矩形的每个像素的每个片段的输出颜色为该统一值
uniform vec4 u_Color;void main()
{color = u_Color;
};
Application.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>#include<iostream>
#include<fstream>
#include<string>
#include<sstream>//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\x;\ASSERT(GLLogError(#x,__FILE__,__LINE__));static void GLClearError()
{//功能:检测OpenGL错误while (glGetError() != GL_NO_ERROR);
}//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{//功能:检测OpenGL错误,输出错误信息//while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。while (GLenum error = glGetError()){std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << ":" << line << std::endl;return false;}return true;
}//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{std::string VertexSource;std::string FragmentSource;
};//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{//功能:打开文件流std::ifstream stream(filepath);//定义着色器类型enum class ShaderType{NONE=-1,VERTEX=0,FRAGMENT=1};//该变量用于存储着色器代码std::string line;//该变量用于存储着色器类型std::stringstream ss[2];//该变量是当前着色器类型ShaderType type = ShaderType::NONE;//功能:读取文件中的每一行内容,直到文件结束while (getline(stream, line)){//如果当前行包含#shader,则说明接下来是着色器代码if (line.find("#shader") != std::string::npos){//如果当前行包含vertex,则说明接下来是顶点着色器代码if (line.find("vertex") != std::string::npos){type = ShaderType::VERTEX;}else if (line.find("fragment") != std::string::npos){type = ShaderType::FRAGMENT;}}else{//否则,将当前行添加到对应着色器代码的stringstream中ss[(int)type] << line << '\n';}}//返回ShaderProgramSource结构体return { ss[0].str(), ss[1].str() };
}//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{//功能:创建着色器对象unsigned int id = glCreateShader(type);//功能:设置着色器源代码.const char* src = source.c_str();//功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串glShaderSource(id, 1, &src, nullptr);//功能:编译着色器对象的源代码glCompileShader(id);//设置返回着色器的对象IDint result;//功能:从着色器对象返回一个参数,表示编译是否成功。glGetShaderiv(id, GL_COMPILE_STATUS, &result);//如果编译失败,则输出错误信息if (result == GL_FALSE){int length;//功能:获取编译错误信息的长度glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);//分配内存,用于存储编译错误信息char* message = (char*)alloca(length*sizeof(char));//功能:获取编译错误信息glGetShaderInfoLog(id, length, &length, message);std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;std::cout << message << std::endl;//删除着色器对象glDeleteShader(id);return 0;}//TODO:错误处理ingreturn id;
}//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{//创建程序对象unsigned int program = glCreateProgram();//编译顶点着色器对象unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);//编译片段着色器对象unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);//功能:将编译好的着色器对象附加到程序对象中glAttachShader(program, vs);glAttachShader(program, fs);//功能:链接程序对象glLinkProgram(program);//功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。glValidateProgram(program);//删除着色器对象,因为它们已经被链接到程序对象中glDeleteShader(vs);glDeleteShader(fs);//返回着色器程序return program;
}int main(void)
{GLFWwindow* window;//初始化glfwif (!glfwInit())return -1;//创建一个窗口模式的窗口并设置OpenGL上下文window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);if (!window)//如果窗口创建失败,则终止程序{glfwTerminate();//释放glfw资源return -1;}//设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行glfwMakeContextCurrent(window);//初始化GLEWif (glewInit() != GLEW_OK)std::cout << "Error!" << std::endl;//打印OpenGL版本信息std::cout << glGetString(GL_VERSION) << std::endl;//准备数据float position[] = {-0.5f, -0.5f,0.5f, -0.5f,0.5f,0.5f,-0.5f,0.5f,};//定义顶点索引缓存,用于标定顶点顺序unsigned int indices[] = {0,1,2,2,3,0};//定义缓冲区对象unsigned int buffer;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &buffer));//功能:将缓冲区对象绑定到目标GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));//功能:配置顶点属性指针GLCall(glEnableVertexAttribArray(0));//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));//索引缓冲区对象unsigned int ibo;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &ibo));//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));//解析着色器代码文件ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");std::string vertexShader = source.VertexSource;std::string fragmentShader = source.FragmentSource;//创建着色器程序unsigned int shader = CreateShader(vertexShader, fragmentShader);//使用着色器程序GLCall(glUseProgram(shader));//功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置//参数:1.shader: 着色器程序对象//参数:2.name: 变量名//返回; 如果 uniform 变量不存在,则返回 -1;存在,则返回它的位置,即它的索引。GLCall(int location = glGetUniformLocation(shader, "u_Color"));//着色器程序(vertex 或 fragment)中声明了某个 uniform 变量(例如 "u_Color"),// 但是在实际的 GLSL 代码中没有使用这个变量,编译器可能会优化掉这个变量,// 从而导致 glGetUniformLocation 返回 -1。//检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。ASSERT(location != -1);//功能:设置颜色// 参数:1.location: 着色器程序中 uniform 变量的位置// 参数:2.v0: 第一个分量// 参数:3.v1: 第二个分量// 参数:4.v2: 第三个分量// 参数:5.v3: 第四个分量// 作用:设置 uniform 变量的值,这里设置了 u_Color 的值为红色(0.2, 0.3, 0.8, 1.0)// GL_FLOAT_VEC4 表示该 uniform 变量是一个四维向量,每个分量的类型为 float。// 注意:这里的颜色值是通过 glGetUniformLocation 函数获取的,而不是直接写死的。// 这样做的好处是,当着色器程序中的 uniform 变量的名称发生变化时,// 我们只需要修改这里的名称,而不需要修改渲染代码。GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));//渲染循环,直到窗口被关闭while (!glfwWindowShouldClose(window)){//清除颜色缓冲区GLCall(glClear(GL_COLOR_BUFFER_BIT));//功能:绘制三角形GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));//刷新缓冲区并交换窗口GLCall(glfwSwapBuffers(window));//处理窗口事件,如键盘输入、鼠标移动等GLCall(glfwPollEvents());}//删除着色器程序//glDeleteProgram(shader);//释放 GLFW 库占用的所有资源。glfwTerminate();return 0;
}
2.1.2简洁注释代码
shader同上,略
Application.cpp
#include <GL/glew.h>
#include <GLFW/glfw3.h>#include<iostream>
#include<fstream>
#include<string>
#include<sstream>//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\x;\ASSERT(GLLogError(#x,__FILE__,__LINE__));static void GLClearError()
{//功能:检测OpenGL错误while (glGetError() != GL_NO_ERROR);
}//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{//功能:检测OpenGL错误,输出错误信息//while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。while (GLenum error = glGetError()){std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << ":" << line << std::endl;return false;}return true;
}//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{std::string VertexSource;std::string FragmentSource;
};//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{//功能:打开文件流std::ifstream stream(filepath);//定义着色器类型enum class ShaderType{NONE=-1,VERTEX=0,FRAGMENT=1};//该变量用于存储着色器代码std::string line;//该变量用于存储着色器类型std::stringstream ss[2];//该变量是当前着色器类型ShaderType type = ShaderType::NONE;//功能:读取文件中的每一行内容,直到文件结束while (getline(stream, line)){//如果当前行包含#shader,则说明接下来是着色器代码if (line.find("#shader") != std::string::npos){//如果当前行包含vertex,则说明接下来是顶点着色器代码if (line.find("vertex") != std::string::npos){type = ShaderType::VERTEX;}else if (line.find("fragment") != std::string::npos){type = ShaderType::FRAGMENT;}}else{//否则,将当前行添加到对应着色器代码的stringstream中ss[(int)type] << line << '\n';}}//返回ShaderProgramSource结构体return { ss[0].str(), ss[1].str() };
}//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{//功能:创建着色器对象unsigned int id = glCreateShader(type);//功能:设置着色器源代码.const char* src = source.c_str();//功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串glShaderSource(id, 1, &src, nullptr);//功能:编译着色器对象的源代码glCompileShader(id);//设置返回着色器的对象IDint result;//功能:从着色器对象返回一个参数,表示编译是否成功。glGetShaderiv(id, GL_COMPILE_STATUS, &result);//如果编译失败,则输出错误信息if (result == GL_FALSE){int length;//功能:获取编译错误信息的长度glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);//分配内存,用于存储编译错误信息char* message = (char*)alloca(length*sizeof(char));//功能:获取编译错误信息glGetShaderInfoLog(id, length, &length, message);std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;std::cout << message << std::endl;//删除着色器对象glDeleteShader(id);return 0;}//TODO:错误处理ingreturn id;
}//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{//创建程序对象unsigned int program = glCreateProgram();//编译顶点着色器对象unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);//编译片段着色器对象unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);//功能:将编译好的着色器对象附加到程序对象中glAttachShader(program, vs);glAttachShader(program, fs);//功能:链接程序对象glLinkProgram(program);//功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。glValidateProgram(program);//删除着色器对象,因为它们已经被链接到程序对象中glDeleteShader(vs);glDeleteShader(fs);//返回着色器程序return program;
}int main(void)
{GLFWwindow* window;//初始化glfwif (!glfwInit())return -1;//创建一个窗口模式的窗口并设置OpenGL上下文window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);if (!window)//如果窗口创建失败,则终止程序{glfwTerminate();//释放glfw资源return -1;}//设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行glfwMakeContextCurrent(window);//初始化GLEWif (glewInit() != GLEW_OK)std::cout << "Error!" << std::endl;//打印OpenGL版本信息std::cout << glGetString(GL_VERSION) << std::endl;//准备数据float position[] = {-0.5f, -0.5f,0.5f, -0.5f,0.5f,0.5f,-0.5f,0.5f,};//定义顶点索引缓存,用于标定顶点顺序unsigned int indices[] = {0,1,2,2,3,0};//定义缓冲区对象unsigned int buffer;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &buffer));//功能:将缓冲区对象绑定到目标GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));//功能:配置顶点属性指针GLCall(glEnableVertexAttribArray(0));//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));//索引缓冲区对象unsigned int ibo;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &ibo));//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));//解析着色器代码文件ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");std::string vertexShader = source.VertexSource;std::string fragmentShader = source.FragmentSource;//创建着色器程序unsigned int shader = CreateShader(vertexShader, fragmentShader);//使用着色器程序GLCall(glUseProgram(shader));//功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置GLCall(int location = glGetUniformLocation(shader, "u_Color"));//检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。ASSERT(location != -1);//功能:设置颜色GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));//渲染循环,直到窗口被关闭while (!glfwWindowShouldClose(window)){//清除颜色缓冲区GLCall(glClear(GL_COLOR_BUFFER_BIT));//功能:绘制三角形GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));//刷新缓冲区并交换窗口GLCall(glfwSwapBuffers(window));//处理窗口事件,如键盘输入、鼠标移动等GLCall(glfwPollEvents());}//删除着色器程序//glDeleteProgram(shader);//释放 GLFW 库占用的所有资源。glfwTerminate();return 0;
}
2.1.3运行结果
2.12动画效果
2.1.1 完全注释代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>#include<iostream>
#include<fstream>
#include<string>
#include<sstream>//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\x;\ASSERT(GLLogError(#x,__FILE__,__LINE__));static void GLClearError()
{//功能:检测OpenGL错误while (glGetError() != GL_NO_ERROR);
}//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{//功能:检测OpenGL错误,输出错误信息//while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。while (GLenum error = glGetError()){std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << ":" << line << std::endl;return false;}return true;
}//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{std::string VertexSource;std::string FragmentSource;
};//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{//功能:打开文件流std::ifstream stream(filepath);//定义着色器类型enum class ShaderType{NONE=-1,VERTEX=0,FRAGMENT=1};//该变量用于存储着色器代码std::string line;//该变量用于存储着色器类型std::stringstream ss[2];//该变量是当前着色器类型ShaderType type = ShaderType::NONE;//功能:读取文件中的每一行内容,直到文件结束while (getline(stream, line)){//如果当前行包含#shader,则说明接下来是着色器代码if (line.find("#shader") != std::string::npos){//如果当前行包含vertex,则说明接下来是顶点着色器代码if (line.find("vertex") != std::string::npos){type = ShaderType::VERTEX;}else if (line.find("fragment") != std::string::npos){type = ShaderType::FRAGMENT;}}else{//否则,将当前行添加到对应着色器代码的stringstream中ss[(int)type] << line << '\n';}}//返回ShaderProgramSource结构体return { ss[0].str(), ss[1].str() };
}//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{//功能:创建着色器对象unsigned int id = glCreateShader(type);//功能:设置着色器源代码.const char* src = source.c_str();//功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串glShaderSource(id, 1, &src, nullptr);//功能:编译着色器对象的源代码glCompileShader(id);//设置返回着色器的对象IDint result;//功能:从着色器对象返回一个参数,表示编译是否成功。glGetShaderiv(id, GL_COMPILE_STATUS, &result);//如果编译失败,则输出错误信息if (result == GL_FALSE){int length;//功能:获取编译错误信息的长度glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);//分配内存,用于存储编译错误信息char* message = (char*)alloca(length*sizeof(char));//功能:获取编译错误信息glGetShaderInfoLog(id, length, &length, message);std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;std::cout << message << std::endl;//删除着色器对象glDeleteShader(id);return 0;}//TODO:错误处理ingreturn id;
}//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{//创建程序对象unsigned int program = glCreateProgram();//编译顶点着色器对象unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);//编译片段着色器对象unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);//功能:将编译好的着色器对象附加到程序对象中glAttachShader(program, vs);glAttachShader(program, fs);//功能:链接程序对象glLinkProgram(program);//功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。glValidateProgram(program);//删除着色器对象,因为它们已经被链接到程序对象中glDeleteShader(vs);glDeleteShader(fs);//返回着色器程序return program;
}int main(void)
{GLFWwindow* window;//初始化glfwif (!glfwInit())return -1;//创建一个窗口模式的窗口并设置OpenGL上下文window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);if (!window)//如果窗口创建失败,则终止程序{glfwTerminate();//释放glfw资源return -1;}//设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行glfwMakeContextCurrent(window);//设置刷新频率//参数:1表示程序会等待一个垂直重绘周期之后再进行缓冲区交换。大概可以理解为每秒60帧。// 如果显示器的刷新率不是 60 Hz,而是其他值(例如 75 Hz 或 120 Hz),// glfwSwapInterval(1); 会根据显示器的实际刷新率来调整程序的帧率。// glfwSwapInterval 函数通常只接受 0、1 或 -1 作为参数,分别代表无同步、垂直同步以及可自适应同步。glfwSwapInterval(1);//初始化GLEWif (glewInit() != GLEW_OK)std::cout << "Error!" << std::endl;//打印OpenGL版本信息std::cout << glGetString(GL_VERSION) << std::endl;//准备数据float position[] = {-0.5f, -0.5f,0.5f, -0.5f,0.5f,0.5f,-0.5f,0.5f,};//定义顶点索引缓存,用于标定顶点顺序unsigned int indices[] = {0,1,2,2,3,0};//定义缓冲区对象unsigned int buffer;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &buffer));//功能:将缓冲区对象绑定到目标GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));//功能:配置顶点属性指针GLCall(glEnableVertexAttribArray(0));//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));//索引缓冲区对象unsigned int ibo;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &ibo));//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));//解析着色器代码文件ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");std::string vertexShader = source.VertexSource;std::string fragmentShader = source.FragmentSource;//创建着色器程序unsigned int shader = CreateShader(vertexShader, fragmentShader);//使用着色器程序GLCall(glUseProgram(shader));//功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置GLCall(int location = glGetUniformLocation(shader, "u_Color"));//检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。ASSERT(location != -1);//功能:设置颜色GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));float r = 0.0f;float increment = 0.05f;//渲染循环,直到窗口被关闭while (!glfwWindowShouldClose(window)){//清除颜色缓冲区GLCall(glClear(GL_COLOR_BUFFER_BIT));GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))//功能:绘制三角形GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));//更新颜色//实现颜色的循环变化if(r > 1.0f)increment = -0.05f;else if(r < 0.0f)increment = 0.05f;r += increment;//刷新缓冲区并交换窗口GLCall(glfwSwapBuffers(window));//处理窗口事件,如键盘输入、鼠标移动等GLCall(glfwPollEvents());}//删除着色器程序//glDeleteProgram(shader);//释放 GLFW 库占用的所有资源。glfwTerminate();return 0;
}
2.1.2简洁注释代码
#include <GL/glew.h>
#include <GLFW/glfw3.h>#include<iostream>
#include<fstream>
#include<string>
#include<sstream>//功能:定义宏,用于在调试过程中进行条件断言,检测OpenGL错误
#define ASSERT(x) if(!(x)) __debugbreak();
//功能:定义了一个宏 GLCall(x),用于调试 OpenGL 应用程序时检查和处理 OpenGL 错误。
#define GLCall(x) GLClearError();\x;\ASSERT(GLLogError(#x,__FILE__,__LINE__));static void GLClearError()
{//功能:检测OpenGL错误while (glGetError() != GL_NO_ERROR);
}//参数:1.function: 发生错误的函数名
static bool GLLogError(const char* function, const char* file, int line)
{//功能:检测OpenGL错误,输出错误信息//while循环,直到glGetError()返回GL_NO_ERROR,表示没有错误发生。while (GLenum error = glGetError()){std::cout << "[OpenGL Error] (" << error << "): " << function << " " << file << ":" << line << std::endl;return false;}return true;
}//功能:定义ShaderProgramSource结构体,用于存储着色器代码
struct ShaderProgramSource
{std::string VertexSource;std::string FragmentSource;
};//功能:解析着色器代码文件。
static ShaderProgramSource ParseShader(const std::string& filepath)
{//功能:打开文件流std::ifstream stream(filepath);//定义着色器类型enum class ShaderType{NONE=-1,VERTEX=0,FRAGMENT=1};//该变量用于存储着色器代码std::string line;//该变量用于存储着色器类型std::stringstream ss[2];//该变量是当前着色器类型ShaderType type = ShaderType::NONE;//功能:读取文件中的每一行内容,直到文件结束while (getline(stream, line)){//如果当前行包含#shader,则说明接下来是着色器代码if (line.find("#shader") != std::string::npos){//如果当前行包含vertex,则说明接下来是顶点着色器代码if (line.find("vertex") != std::string::npos){type = ShaderType::VERTEX;}else if (line.find("fragment") != std::string::npos){type = ShaderType::FRAGMENT;}}else{//否则,将当前行添加到对应着色器代码的stringstream中ss[(int)type] << line << '\n';}}//返回ShaderProgramSource结构体return { ss[0].str(), ss[1].str() };
}//功能:编译着色器代码
static unsigned int CompilesShader(unsigned int type, const std::string& source)
{//功能:创建着色器对象unsigned int id = glCreateShader(type);//功能:设置着色器源代码.const char* src = source.c_str();//功能:替换着色器对象中的源代码。将该id的指定着色器的源代码设置为src指针指向的字符串glShaderSource(id, 1, &src, nullptr);//功能:编译着色器对象的源代码glCompileShader(id);//设置返回着色器的对象IDint result;//功能:从着色器对象返回一个参数,表示编译是否成功。glGetShaderiv(id, GL_COMPILE_STATUS, &result);//如果编译失败,则输出错误信息if (result == GL_FALSE){int length;//功能:获取编译错误信息的长度glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);//分配内存,用于存储编译错误信息char* message = (char*)alloca(length*sizeof(char));//功能:获取编译错误信息glGetShaderInfoLog(id, length, &length, message);std::cout << "Failed to compile shader!" << (type == GL_VERTEX_SHADER? "Vertex" : "Fragment") << "shader!" << std::endl;std::cout << message << std::endl;//删除着色器对象glDeleteShader(id);return 0;}//TODO:错误处理ingreturn id;
}//功能:创建着色器程序
static unsigned int CreateShader(const std::string& vertexShader, const std::string& fragmentShader)
{//创建程序对象unsigned int program = glCreateProgram();//编译顶点着色器对象unsigned int vs = CompilesShader(GL_VERTEX_SHADER, vertexShader);//编译片段着色器对象unsigned int fs = CompilesShader(GL_FRAGMENT_SHADER, fragmentShader);//功能:将编译好的着色器对象附加到程序对象中glAttachShader(program, vs);glAttachShader(program, fs);//功能:链接程序对象glLinkProgram(program);//功能:验证着色器程序对象是否可以在当前OpenGL状态中执行。检查着色器程序的完整性和可执行性。glValidateProgram(program);//删除着色器对象,因为它们已经被链接到程序对象中glDeleteShader(vs);glDeleteShader(fs);//返回着色器程序return program;
}int main(void)
{GLFWwindow* window;//初始化glfwif (!glfwInit())return -1;//创建一个窗口模式的窗口并设置OpenGL上下文window = glfwCreateWindow(640, 480, "Hello World", NULL, NULL);if (!window)//如果窗口创建失败,则终止程序{glfwTerminate();//释放glfw资源return -1;}//设置当前窗口的上下文,之后所有的OpenGL调用都会在这个上下文中进行glfwMakeContextCurrent(window);//设置刷新频率glfwSwapInterval(1);//初始化GLEWif (glewInit() != GLEW_OK)std::cout << "Error!" << std::endl;//打印OpenGL版本信息std::cout << glGetString(GL_VERSION) << std::endl;//准备数据float position[] = {-0.5f, -0.5f,0.5f, -0.5f,0.5f,0.5f,-0.5f,0.5f,};//定义顶点索引缓存,用于标定顶点顺序unsigned int indices[] = {0,1,2,2,3,0};//定义缓冲区对象unsigned int buffer;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &buffer));//功能:将缓冲区对象绑定到目标GLCall(glBindBuffer(GL_ARRAY_BUFFER, buffer));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ARRAY_BUFFER, 2 * 4 * sizeof(float), position, GL_STATIC_DRAW));//功能:配置顶点属性指针GLCall(glEnableVertexAttribArray(0));//功能:指定顶点属性数组的索引、大小、数据类型、是否归一化、偏移量、数据指针GLCall(glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 2, 0));//索引缓冲区对象unsigned int ibo;//功能:生成缓冲区对象,并将数据写入缓冲区GLCall(glGenBuffers(1, &ibo));//功能:将缓冲区对象绑定到目标.没有绑定为数组缓冲区GLCall(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo));//功能:将数据写入缓冲区GLCall(glBufferData(GL_ELEMENT_ARRAY_BUFFER, 6 * sizeof(unsigned int), indices, GL_STATIC_DRAW));//解析着色器代码文件ShaderProgramSource source = ParseShader("res/shaders/Basic.shader");std::string vertexShader = source.VertexSource;std::string fragmentShader = source.FragmentSource;//创建着色器程序unsigned int shader = CreateShader(vertexShader, fragmentShader);//使用着色器程序GLCall(glUseProgram(shader));//功能:获取着色器程序中 "u_Color" 的 uniform 变量的位置GLCall(int location = glGetUniformLocation(shader, "u_Color"));//检查返回值是否为 -1,避免在后续对一个不存在的 uniform 变量操作。ASSERT(location != -1);//功能:设置颜色GLCall(glUniform4f(location, 0.8f, 0.3f, 0.8f, 1.0f));float r = 0.0f;float increment = 0.05f;//渲染循环,直到窗口被关闭while (!glfwWindowShouldClose(window)){//清除颜色缓冲区GLCall(glClear(GL_COLOR_BUFFER_BIT));GLCall(glUniform4f(location, r, 0.3f, 0.8f, 1.0f))//功能:绘制三角形GLCall(glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr));//更新颜色if(r > 1.0f)increment = -0.05f;else if(r < 0.0f)increment = 0.05f;r += increment;//刷新缓冲区并交换窗口GLCall(glfwSwapBuffers(window));//处理窗口事件,如键盘输入、鼠标移动等GLCall(glfwPollEvents());}//删除着色器程序//glDeleteProgram(shader);//释放 GLFW 库占用的所有资源。glfwTerminate();return 0;
}
2.1.3运行结果
蓝色粉色渐变循环
相关文章:

OpenGL Chan视频学习-11 Uniforms in OpenGL
bilibili视频链接: 【最好的OpenGL教程之一】https://www.bilibili.com/video/BV1MJ411u7Bc?p5&vd_source44b77bde056381262ee55e448b9b1973 函数网站: docs.gl 说明: 1.之后就不再单独整理网站具体函数了,网站直接翻译…...
Flink系列文章列表
把写的文章做一个汇总,会陆续更新的。 Flink流处理原理与实践:状态管理、窗口操作与容错机制-CSDN博客...

GitLab 从 17.10 到 18.0.1 的升级指南
本文分享从 GitLab 中文本 17.10.0 升级到 18.0.1 的完整过程。 升级前提 查看当前安装实例的版本。有多种方式可以查看: 方式一: /help页面 可以直接在 /help页面查看当前实例的版本。以极狐GitLab SaaS 为例,在浏览器中输入 https://ji…...

产业集群间的专利合作关系
需要准备的文件: 全国的专利表目标集群间的企业名单 根据专利的共同申请人,判断这两家企业之间存在专利合作关系。 利用1_filter_patent.py,从全国的3000多万条专利信息中,筛选出与目标集群企业相关的专利。 只要专利的申请人包…...
PyQt学习系列02-模型-视图架构与数据管理
PyQt学习系列笔记(Python Qt框架) 第二课:PyQt的模型-视图架构与数据管理 一、模型-视图架构概述 1.1 什么是模型-视图架构? 模型-视图(Model-View)是Qt框架中用于数据展示和交互的核心设计模式。它将数…...
redis主从复制架构安装与部署
redis主从复制架构安装与部署 1、Redis 一主两从架构的优势2、环境准备3、下载redis4、解压缩文件5、编辑配置文件6、创建数据目录并启动Redis7、检查主从状态8、 Redis Sentinel 模式 1、Redis 一主两从架构的优势 Redis 采用一主两从(1个主节点 2个从节点&#…...
Kotlin 中 Lambda 表达式的语法结构及简化推导
在 Kotlin 编程中,Lambda 表达式是一项非常实用且强大的功能。今天,我们就来深入探讨一下 Lambda 表达式的语法结构,以及它那些令人 “又爱又恨” 的简化写法。 一、Lambda 表达式完整语法结构 Lambda 表达式最完整的语法结构定义为{参数名…...
YOLOv2 深度解析:目标检测领域的进阶之路
在计算机视觉领域,目标检测一直是研究和应用的热点方向。YOLO(You Only Look Once)系列算法以其快速高效的特点,在目标检测领域占据了重要地位。YOLOv2 作为 YOLO 系列算法的重要迭代版本,在 YOLOv1 的基础上进行了诸多…...

KT6368A通过蓝牙芯片获取手机时间详细说明,对应串口指令举例
一、功能简介 KT6368A双模蓝牙芯片支持连接手机,获取手机的日期、时间信息,可以同步RTC时钟 1、无需安装任何app,直接使用系统蓝牙即可实现 2、同时它不影响音频蓝牙,还支持一些简单的AT指令进行操作 3、实现的方式࿱…...

计算机网络实验课(二)——抓取网络数据包,并实现根据条件过滤抓取的以太网帧,分析帧结构
文章目录 一、添加控件二、代码分析2.1 代码2.2 控件初始化2.3 打开和关闭设备2.4 开始和结束捕获2.5 设置捕获条件2.6 捕获数据包 三、运行程序四、结果分析 提要:如果你通过vs打开.sln文件,然后代码界面或者前端界面都没找到,视图里面也没找…...
自动生成提示技术突破:AUTOPROMPT重塑语言模型应用
AUTOPROMPT 预训练语言模型的显著成功促使人们研究这些模型在预训练期间学习了哪些类型的知识。将任务重新表述为填空题(例如,完形填空测试)是衡量此类知识的自然方法 但是,它的使用受到编写合适提示所需的手动工作和猜测的限制。为了解决这个问题,我们开发了 AUTOPROMP…...

78. Subsets和90. Subsets II
目录 78.子集 方法一、迭代法实现子集枚举 方法二、递归法实现子集枚举 方法三、根据子集元素个数分情况收集 方法四、直接回溯法 90.子集二 方法一、迭代法实现子集枚举 方法二、递归法实现子集枚举 方法三、根据子集元素个数分情况收集 方法四、直接回溯法 78.子集…...
VSCode 插件 GitLens 破解方法
文章目录 1. 安装指定版本2. 修改插件文件3. 重启 VSCode 1. 安装指定版本 在 VSCode 中打开扩展(Ctrl Shift X),搜索 GitLens,右键点击 安装特定版本,在弹出的窗口中选择 17.0.2,然后等待安装完成。 2…...
linux 通过命令将 MinIO 桶的权限设置为 Custom(自定义策略)
在 Ubuntu 下,如果要通过命令将 MinIO 桶的权限设置为 Custom(自定义策略),可以使用 mc(MinIO Client)、AWS CLI 或直接调用 MinIO API(如 curl)。以下是几种方法: 方法 …...
模型评价指标介绍
模型评价指标介绍 **在机器学习与数据科学领域,构建模型仅是工作的一部分,更为关键的是要精准评估模型的性能。模型评价指标作为衡量模型表现的标准,有助于数据科学家、分析师等从业者判断模型的优劣,进而进行优化与改进。不同类…...

ElasticSearch整合SpringBoot
ElasticSearch 整合SpringBoot ES官方提供了各种不同语言的客户端。用来操作ES。这些客户端的本质就是组装DSL语句,通过HTTP请求发送给ES。 设计索引库 跟据数据库的表结构进行ES索引库的创建时。如果字段需要进行倒排索引的时候请为它指定分词器。如果该字段不是…...
ArcGIS Pro 3.4 二次开发 - 知识图谱
环境:ArcGIS Pro SDK 3.4 + .NET 8 文章目录 知识图谱1 知识图谱数据存储1.1 打开与知识图谱的连接1.2 从KnowledgeGraphLayer获取连接1.3 检索GDB要素类和定义1.4 检索GDB表和定义1.5 从知识图谱数据存储中获取服务 Uri1.6 将一组对象ID转换为实体的ID1.7 将一组ID转换为实体…...

2025上半年软考高级系统架构设计师经验分享
笔者背景 笔者在成都工作近7年, 一直担任研发大头兵,平日工作主要涵盖应用开发(Java)与数仓开发,对主流数据库、框架等均有涉猎,但谈不上精通。 最近有一些职业上的想法,了解到软考有那么一丁点…...

uni-app学习笔记十二-vue3中创建组件
通过组件,可以很方便地实现页面复用,减少重复页面的创建,减少重复代码。一个页面可以引入多个组件。下面介绍在HBuilder X中创建组件的方法: 一.组件的创建 1.选中项目,右键-->新建目录(文件夹),并将文…...
React 虚拟dom
虚拟dom react核心机制 内存中轻量级JS对象树模拟真实DOM,主要目的是减少操作真实dom的开销 具体是通过diff算法计算最小的变更,批处理更新真实dom元素 diff算法 特点 同级去进行比较,不涉及跨层的一个比较 使用key值优化列表遍历过程 …...
互联网大厂Java求职面试:AI与大模型应用集成中的架构难题与解决方案-1
互联网大厂Java求职面试:AI与大模型应用集成中的架构难题与解决方案-1 场景描述 郑薪苦,一个看似不靠谱但技术潜力巨大的程序员,在一次针对AI与大模型应用集成的面试中,被一位技术总监级别的人物提问。面试官以严肃专业的态度&a…...
《算法笔记》13.2小节——专题扩展->树状数组(BIT) 问题 D: 数列-训练套题T10T3
数列(sequence.pas/c/cpp) - 问题描述 一个简单的数列问题:给定一个长度为n的数列,求这样的三个元素ai, aj, ak的个数,满足ai < aj > ak,且i < j < k。 - 输入数据 第一行是一个整数n(n < 50000)。 第二行n个整…...

一键启动多个 Chrome 实例并自动清理的 Bash 脚本分享!
目录 一、📦 脚本功能概览 二、📜 脚本代码一览 三、🔍 脚本功能说明 (一)✅ 支持批量启动多个 Chrome 实例 (二)✅ 每个实例使用独立用户数据目录 (三)✅ 启动后自…...

4 月 62100 款 App 被谷歌下架!环比增长 28%
大家好,我是牢鹅!上周刚刚结束的 2025 年 Google I/O 开发者大会, Google Play 带来了一系列的更新,主要围绕提升优质 App 的"发现"、"互动"和"收入"三大核心内容。 这或许正是谷歌生态的一个侧影…...
图像分割全路线学习(结合论文)
本篇文章参考自开源大佬的文章并结合自己的思考而来,欢迎大家提出意见,论文代码同样来自开源,文中已注明 文章目录 图像分割图像分割算法分类?传统的基于CNN的分割方法缺点?FCN详解FCN改变了什么?FCN网络结构&#x…...
Go语言之定义结构体(Struct)-《Go语言实战指南》
结构体(struct)是 Go 中的一种复合数据类型,它允许你将多个不同类型的字段组合成一个类型,类似于 C 语言的结构体或面向对象语言中的类。 一、结构体的基本定义 type 结构体名 struct {字段名 字段类型... } 示例: …...

mediapipe标注视频姿态关键点(基础版加进阶版)
前言 手语视频流的识别有两种大的分类,一种是直接将视频输入进网络,一种是识别了关键点之后再进入网络。所以这篇文章我就要来讲讲如何用mediapipe对手语视频进行关键点标注。 代码 需要直接使用代码的,我就放这里了。环境自己配置一下吧&…...

PCtoLCD2002如何制作6*8字符
如何不把“等比缩放”前的打勾取消,则无法修改为对应英文字符为6*8。 取消之后就可以更改了!...

SmartPlayer与VLC播放RTMP:深度对比分析延迟、稳定性与功能
随着音视频直播技术的发展,RTMP(实时消息传输协议)成为了广泛应用于实时直播、在线教育、视频会议等领域的重要协议。为了确保优质的观看体验,RTMP播放器的选择至关重要。大牛直播SDK的SmartPlayer和VLC都是在行业中广受欢迎的播放…...

Qt QPaintEvent绘图事件painter使用指南
绘制需在paintEvent函数中实现 用图片形象理解 如果加了刷子再用笔就相当于用笔画过的区域用刷子走 防雷达: 源文件 #include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QPainter> Widget::Widget(QWidget…...