当前位置: 首页 > news >正文

OpenGL(三)管线介绍和三角形绘制

目录

  • 一、图形渲染管线概述
    • 管线阶段详解
  • 二、核心概念:VAO、VBO、EBO
    • 2.1 顶点缓冲对象(Vertex Buffer Object, VBO)
    • 2.2 顶点数组对象(Vertex Array Object, VAO)
    • 2.3 元素缓冲对象(Element Buffer Object, EBO / IBO)
  • 三、绘制三角形完整实现
    • 3.1 顶点数据定义
    • 3.2 顶点着色器(vertex_shader.glsl)
    • 3.3 片段着色器(fragment_shader.glsl)
    • 3.4 着色器编译与链接
    • 3.5 顶点属性配置
    • 3.6 渲染循环
  • 四、使用EBO绘制矩形
    • 4.1 顶点与索引数据
    • 4.2 EBO配置
    • 4.3 绘制调用
  • 五、线框模式与调试技巧
    • 5.1 开启线框模式
    • 5.2 调试着色器
  • 六、效果图
  • 七、完整代码

一、图形渲染管线概述

在OpenGL中,图形渲染管线(Graphics Pipeline)是将3D坐标转换为屏幕2D像素的核心处理流程。整个过程分为两个主要阶段:

  1. 顶点处理阶段:将3D坐标转换为标准化设备坐标(Normalized Device Coordinates, NDC)
  2. 片段处理阶段:将2D坐标转换为实际像素颜色

管线阶段详解

在这里插入图片描述

  1. 顶点着色器(Vertex Shader)

    • 处理单个顶点
    • 核心任务:3D坐标 → 标准化设备坐标
    • 可自定义的着色器程序
  2. 几何着色器(Geometry Shader,可选)

    • 处理图元(点/线/三角形)
    • 生成新几何体
  3. 图元装配(Primitive Assembly)

    • 将顶点组装为指定图元
  4. 光栅化(Rasterization)

    • 将图元映射为屏幕像素
    • 生成片段(Fragment)
  5. 片段着色器(Fragment Shader)

    • 计算像素最终颜色
    • 处理光照/阴影等高级效果
  6. 测试与混合(Tests and Blending)

    • 深度测试(Depth Test)
    • 透明度混合(Alpha Blending)

二、核心概念:VAO、VBO、EBO

2.1 顶点缓冲对象(Vertex Buffer Object, VBO)

  • 作用:在GPU显存中存储顶点数据

  • 优势:减少CPU到GPU的数据传输次数

  • 创建流程:

    unsigned int VBO;
    glGenBuffers(1, &VBO);          // 生成VBO
    glBindBuffer(GL_ARRAY_BUFFER, VBO); // 绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); // 传输数据
    

2.2 顶点数组对象(Vertex Array Object, VAO)

  • 作用:存储顶点属性配置

  • 优势:简化状态管理

  • 创建流程:

    unsigned int VAO;
    glGenVertexArrays(1, &VAO);     // 生成VAO
    glBindVertexArray(VAO);         // 绑定
    // 配置顶点属性指针...
    

2.3 元素缓冲对象(Element Buffer Object, EBO / IBO)

  • 作用:存储顶点索引,避免重复顶点数据

  • 优势:减少内存占用

  • 创建流程:

    unsigned int EBO;
    glGenBuffers(1, &EBO); 
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
    

三、绘制三角形完整实现

3.1 顶点数据定义

float vertices[] = {
    -0.5f, -0.5f, 0.0f,  // 左下角
     0.5f, -0.5f, 0.0f,  // 右下角
     0.0f,  0.5f, 0.0f   // 顶部
};

3.2 顶点着色器(vertex_shader.glsl)

#version 330 core
layout (location = 0) in vec3 aPos;

void main() {
    gl_Position = vec4(aPos, 1.0); // 直接传递坐标
}

3.3 片段着色器(fragment_shader.glsl)

#version 330 core
out vec4 FragColor;

void main() {
    FragColor = vec4(1.0, 0.5, 0.2, 1.0); // 固定橘色
}

3.4 着色器编译与链接

// 编译顶点着色器
unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);

// 编译片段着色器
unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

// 创建着色器程序
unsigned int shaderProgram = glCreateProgram();
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);

// 删除临时着色器对象
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

3.5 顶点属性配置

glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

// 配置位置属性
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

3.6 渲染循环

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();
}

四、使用EBO绘制矩形

4.1 顶点与索引数据

float vertices[] = {
     0.5f,  0.5f, 0.0f,  // 右上
     0.5f, -0.5f, 0.0f,  // 右下
    -0.5f, -0.5f, 0.0f,  // 左下
    -0.5f,  0.5f, 0.0f   // 左上
};

unsigned int indices[] = {
    0, 1, 3,  // 第一个三角形
    1, 2, 3   // 第二个三角形
};

4.2 EBO配置

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

4.3 绘制调用

glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

五、线框模式与调试技巧

5.1 开启线框模式

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

5.2 调试着色器

// 检查编译错误
int success;
char infoLog[512];
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
    glGetShaderInfoLog(shader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::COMPILATION_FAILED\n" << infoLog << std::endl;
}

六、效果图

在这里插入图片描述

七、完整代码

#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);

#ifdef __APPLE__
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

	// 创建 GLFW 窗口
	GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
	if (window == NULL)
	{
		std::cout << "创建 GLFW 窗口失败" << std::endl;
		glfwTerminate();
		return -1;
	}
	glfwMakeContextCurrent(window);
	glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

	// 初始化 GLAD,加载 OpenGL 函数指针
	if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
	{
		std::cout << "GLAD 初始化失败" << std::endl;
		return -1;
	}

	// 创建并编译着色器程序
	// 顶点着色器
	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 << "错误::着色器::顶点::编译失败\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 << "错误::着色器::片段::编译失败\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 << "错误::着色器::程序::链接失败\n" << infoLog << std::endl;
	}
	glDeleteShader(vertexShader);
	glDeleteShader(fragmentShader);

	// 设置顶点数据(以及缓冲区)并配置顶点属性
	float vertices[] = {
		-0.5f, -0.5f, 0.0f, // 左下角
		 0.5f, -0.5f, 0.0f, // 右下角
		 0.0f,  0.5f, 0.0f  // 顶部
	};

	unsigned int VBO, VAO;
	glGenVertexArrays(1, &VAO);
	glGenBuffers(1, &VBO);

	// 绑定 VAO(顶点数组对象)
	glBindVertexArray(VAO);

	// 绑定 VBO(顶点缓冲对象)并传输数据
	glBindBuffer(GL_ARRAY_BUFFER, VBO);
	glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

	// 配置顶点属性
	glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
	glEnableVertexAttribArray(0);

	// 解绑 VBO(可以安全解绑)
	glBindBuffer(GL_ARRAY_BUFFER, 0);

	// 解绑 VAO(一般情况下不解绑)
	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);
		// glBindVertexArray(0); // 不需要每次都解绑

		// 交换缓冲区并处理事件
		glfwSwapBuffers(window);
		glfwPollEvents();
	}

	// 释放资源
	glDeleteVertexArrays(1, &VAO);
	glDeleteBuffers(1, &VBO);
	glDeleteProgram(shaderProgram);

	// 关闭 GLFW,释放资源
	glfwTerminate();
	return 0;
}

// 处理输入:查询 GLFW 是否检测到按键按下
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)
{
	// 重新设置 OpenGL 视口大小
	glViewport(0, 0, width, height);
}

相关文章:

  • C++特殊类的设计
  • 二叉树相关算法实现:判断子树与单值二叉树
  • 线程未关闭导致资源泄漏
  • Halcon找圆心
  • c++some
  • 如何为你的github开源项目选择合适的开源协议?
  • Go 1.24 新特性解析:泛型类型别名、弱指针与终结器改进
  • HTTP抓包Websocket抓包(Fiddler)
  • tar包部署rabbitMQ
  • 进阶版孟德尔随机化方法!遗传变异聚类+异质性检验,避免水平多效性带来的假阳性结果(PCMR)
  • C++ 命名空间
  • 【neo4j数据导出并在其他电脑导入】
  • SQL问题分析与诊断(8)——前提
  • 动态路由机制MoE专家库架构在多医疗AI专家协同会诊中的应用探析
  • 登山第二十一梯:点云补全——零样本、跨激光分布的“泥瓦匠”
  • 计算机二级(C语言)考试高频考点总汇(一)—— C语言通识、数据类型和运算符、位运算、进制转换、进制转换方法
  • LabVIEW柔性机械臂减振控制系统
  • LeetCode算法题(Go语言实现)_12
  • 今日行情明日机会——20250326
  • 微信小程序pdf预览
  • 中办、国办关于持续推进城市更新行动的意见
  • 工商银行杭州金融研修院原院长蒋伟被“双开”
  • 当番茄霸总遇上晋江古言,短剧IP小变局
  • 白玉兰奖征片综述丨动画的IP生命力
  • 山东市监局回应“盒马一批次‘无抗’鸡蛋抽检不合格后复检合格”:系生产商自行送检
  • 四部门:强化汛期农业防灾减灾,奋力夺取粮食和农业丰收