OPenGL学习(1)显示一个三角形
主要内容
- 图形渲染管线
- 安装OpenGL
- OpenGL基本用法
图形渲染管线
图形渲染管线是渲染的核心,是通过给定虚拟相机、3D场景物体 以及光源等场景要素来产生或者渲染一副2D的图像。
渲染有很多个步骤,其中某些是可以并行的,因此叫流水管线。
图形渲染管线的步骤基本上是确定的,但是不同的书上分类的规则不同。这里按照实时渲染这本书上来分类。一共有3个阶段
- 应用程序阶段
- 几何阶段
- 光栅化阶段
应用阶段
应用阶段的任务:将需要绘制到屏幕的图元数据输入到几何阶段。图元指的是绘制图形的基本单位。在OpenGL中,一共有7种图元:点,线,三角形,线带,线环,三角形带,三角形环。
应用还可以处理下面这些:
碰撞检测、加速算法、输入检测,动画,力反馈以及纹理动画,变换仿真、几何变形,层次视锥裁剪
应用阶段的输出是:摄像机的位置,光照,模型的图元。
几何阶段:
几何阶段的任务是:将图元从3维空间映射到2维屏幕上
主要包括下面几个步骤:
- 模型视图变换(MV),模型变换是将图元的坐标从local坐标转换成世界坐标,视图变换是将
- 顶点着色
- 投影 (P)
- 裁剪,投影之后得到了
- 屏幕映射。
其中,MVP和顶点着色是写在顶点着色器中的。屏幕映射是用硬件实现的,属于完全不可编程。
光栅化阶段
三角形设定,三角形遍历,像素着色,合并阶段
安装OpenGL
现在安装OpenGL我们只安装GLAD和GLFW就行了。
- GLAD是用来管理OpenGL的函数指针的。OpenGL是一套接口,通常我们使用函数指针来调用这些,但是因为不同的显卡驱动获取OpenGL函数地址不同,程序就需要针对不同的驱动单独编写适应性代码。GLAD提供了一套封装,可以方便的获取不同驱动下的OpenGL的函数地址
- GLFW是一个专门针对OpenGL的C语言库,它提供了一些渲染物体所需的最低限度的接口。它允许用户 创建OpenGL上下文,定义窗口参数以及处理用户输入。
glfw可以在官网页面下载,可以直接下载用vs编译好的dll和lib。里面有vs2022以及之前的很多版本,基本够用了。
glad也可以在官网下载,但是要配置下;
- API选最新的
- Profile设置为Core(核心模式)
然后点击生成得到一个glad.c文件和一堆头文件。不过这个属于第三方的代码,我喜欢打包成一个库使用。所以先创建一个vs项目,然后导入glad.c文件,并且配置头文件目录,把设置生成exe改成静态库lib(这个在项目设置里面)。然后ctr+f5生成得到glad.lib。
最后新建一个项目,这个项目就是我们写opengl的地方。首先设置头文件目录,把刚刚glad的目录包括进来,然后添加静态库目录和文件(在项目配置里)
到此,应该就没什么问题了。如果对静态库,动态库这些理解不深的话,可以先复习一下。再就是vs这个IDE的配置和使用,可以好好学学,Windows下我个人还是更喜欢vs。
OpenGL基本用法
- VAO,
- VBO,GPU中的一块显存区域
- EBO
在GPU显存中,用glGenBuffer()创建一块缓冲区,用来存放顶点数据。然后将这个缓冲区绑定到GL_ARRAY_BUFFER。调用glBindBuffer()这个函数。具体的参数什么的放到最后的代码里面说,这里先看流程。
在OpenGL中有很多个缓冲区,这里我们用到的是GL_ARRAY_BUFFER这个缓冲区来存放顶点。我们说把创建的VBO(也就是显存中的一块空间)绑定到这个缓冲区,意思是,以后我们并不操作VBO,而是操作缓冲区GL_ARRAY_BUFFER。
OpenGL是面向过程的C语言程序设计方法,所以不能像面向对象那样去思考。
接着往下,在绑定了缓冲区后,需要调用glBufferData()加载数据了。
数据加载完了以后需要写Shader,Shader(着色器)是一个代码块,需要编译链接然后使用。不过这些OpenGL都帮我们封装好了,只需要用GLSL语言写就行了(很简单的语言),GLSL是openGL Shader Language,除此以外还有别的什么SL,不过都大同小异。
在OpenGL中,我们写两个着色器,顶点着色器和片段着色器。
编写的步骤是:
- 用一个字符串保存着色器代码
- 创建Shader对象 glCreateShader(),如果是顶点着色器,就带个参数告知(后面会谈到)
- 将着色器代码加载到shader对象中,glShaderSource()
- 编译glCompileShader()
- 创建一个程序对象 glCreateProgram()
- 附加并链接两个着色器,glAttachShader(),glLinkProgram()
- 使用着色器程序 glUseProgram()
Shader写好以后,GPU就可以进行渲染了。下一步是要告知GPU如何理解传进去的数据。这里需要用到VAO,顶点数组对象。
下面是代码和我的注释
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
// 屏幕设置
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;
// 顶点着色器的代码
const char* vertexShaderSource = "#version 330 core\n"
"layout (location = 0) in vec3 aPos;\n"
"void main()\n"
"{\n"
" gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
// 片段着色器的代码
const char* fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\n"
"}\n\0";
int main()
{
// glfw初始化
glfwInit();
// 这个函数是设置一些参数的
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
// 创建一个窗口
GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
glfwMakeContextCurrent(window);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// 加载glad,glad是用来管理函数指针的。
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
// 编译,链接,生成Shader程序,这些工作在进行渲染前就要完成。
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
// 查看编译是否有问题
int success;
char infoLog[512];
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
// 同上,看是否编译成成功
glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
if (!success)
{
glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
}
// 链接
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
// 看链接成功没
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if (!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
}
// 程序生成了及时删除shader
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
// 顶点数据
float vertices[] = {
-0.5f, -0.5f, 0.0f, // left
0.5f, -0.5f, 0.0f, // right
0.0f, 0.5f, 0.0f // top
};
unsigned int VBO, VAO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
// 将VAO绑定到Vertex Array上
glBindVertexArray(VAO);
// 将VBO绑定到GL_ARRAY_BUFFER上
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 告知如何处理Vertex Array,(VAO)
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 数据传完了,也告知了如何解读,就需要解绑了
// 解绑GL_ARRYA_BUFFER
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 解绑Vertex Array
glBindVertexArray(0);
// 主事件循环
while (!glfwWindowShouldClose(window))
{
// 处理输入
processInput(window);
// 设置颜色
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// 显示三角形
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // 这里其实不需要每次都绑定,我们只显示一个三角形,绑定一次,一直用就行了。
glDrawArrays(GL_TRIANGLES, 0, 3);
// 窗口有关的,不需要管
glfwSwapBuffers(window);
glfwPollEvents();
}
// 释放内存
glDeleteVertexArrays(1, &VAO);
glDeleteBuffers(1, &VBO);
glDeleteProgram(shaderProgram);
// 结束程序
glfwTerminate();
return 0;
}
// 处理输入
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
glfwSetWindowShouldClose(window, true);
}
// 每次窗口大小修改后会调用这个
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
// 视口是我们看到的屏幕的大小
glViewport(0, 0, width, height);
}
窗口和事件处理
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!