OpenGL原理与实践——核心模式(一):VBO、VAO等原理解析及项目初始设置

2023-11-11 02:40

本文主要是介绍OpenGL原理与实践——核心模式(一):VBO、VAO等原理解析及项目初始设置,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

序言——OpenGL在是什么?为什么?做什么?

OpenGL实现了什么

OpenGL内模型数据的本质——顶点数据 

我们需要研究什么——三角形,一个图形基元

MVP变换

OpenGL渲染流程的关键——摄像机变换

OpenGL渲染管线概览

准备——项目配置

项目初始代码框架及注释

初识——三角形绘制 

OpenGL中的顶点数据格式——float数组

OpenGL中shader如何从CPU中获取数据——layout(锚点)

Shader

VBO:Vertex Buffer Object

VAO:解决锚点问题,记录了VBO的锚点信息

编译shader

设定VAO并进行渲染

整体源码


序言——OpenGL在是什么?为什么?做什么?

OpenGL实现了什么

将三维物体映射到视线方向上的一个裁剪空间(屏幕)上 

OpenGL内模型数据的本质——顶点数据 

我们需要研究什么——三角形,一个图形基元

MVP变换

OpenGL渲染流程的关键——摄像机变换

OpenGL渲染管线概览

准备——项目配置

GLFW

Download | GLFW

GLAD

https://glad.dav1d.de

 下载后,进行相应配置。

项目初始代码框架及注释

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>void framebuffer_size_callback(GLFWwindow* window, int width, int height) {glViewport(0, 0, width, height);
}void processInput(GLFWwindow* window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {glfwSetWindowShouldClose(window, true);}
}int main() {//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态glfwInit(); //,3版本以上glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//用OpenGL核心开发模式glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);//创建窗体GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);if (window == nullptr) {std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}//把当前上下文绑定至当前窗口glfwMakeContextCurrent(window);//通过glad绑定各种函数指针if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}//视口:需要渲染的东西在哪里glViewport(0, 0, 800, 600);//当Frame大小变动,调用回调函数调整视口大小glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);//防止窗口结束退出while (!glfwWindowShouldClose(window)) {processInput(window);//擦除画布,用定义的颜色填充glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);//双缓冲glfwSwapBuffers(window);glfwPollEvents();}//结束,释放资源glfwTerminate();return 0;}

运行结果如下:

初识——三角形绘制 

OpenGL中的顶点数据格式——float数组

看向-Z方向

OpenGL中shader如何从CPU中获取数据——layout(锚点)

  • CPU将float顶点数据数组传入GPU
  • CPU告诉GPU如何解析这个数组
  • 调用渲染指令进行绘制

GPU显存中的布局:layout;可以理解为“锚点”,指明在这一锚点代表的区域,存放了什么样的数据。

Shader

直白来说,Shader就是跑在GPU上的一种语言,用来操作GPU。

我们先写好两个shader的内容,先大致了解一番:

vertexShader:

#version 330 core//在layout=0,这块区域放置了一个vec3
layout (location = 0 ) in vec3 aPos;//操作
void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
  • vertexShader中的数据gl_Position,会自动流入下一个阶段中,也就是fragmentShader 
  • vertexShader会被调用多少次?有多少顶点就会调用多少次

 fragmentShader:

#version 330 core
out vec4 FragColor;
void main(){FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
  • fragmentShader的目的是为了输出一个数据,这里是vec4 FragColor,被定义为out类型,会被输出到下一个管线流程中。
  • fragmentShader会被调用多少次?简单来说有多少像素就会调用多少次

流程:

  • 将顶点数据转入到vertexShader,进行空间变换等操作(注意是并行的
  • 数据从vertexShader传入到fragmentShader,进行像素插值等操作(处理一堆像素点)

VBO:Vertex Buffer Object

在上面那个图中,其中的“GPU shader”就是所谓的VBO,也就是我们开辟的一块区域。

在开辟的这块空间,存储顶点数据。

那么在OpenGL中如何做这件事?

  • 获取VBO的index(由OpenGL状态机分配的index
  • 绑定VBO的index
  • 给VBO分配显存空间,并传输数据
  • 告诉shader数据的解析方式
  • 激活锚点,按照解析方式取读取数据

具体代码如下,我们在mian.cpp中添加如下函数:

//构建模型数据:VBO,
void initModel() {float vertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f};glGenBuffers(1, &VBO);//绑定哪一种buffer, glBindBuffer(GL_ARRAY_BUFFER, VBO);//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//打开锚点:激活glEnableVertexAttribArray(0);//解绑glBindBuffer(GL_ARRAY_BUFFER, 0);
}

每个函数的作用和参数意义,这里我用注释详细标明。方便后时查阅复习。

VAO:解决锚点问题,记录了VBO的锚点信息

编译shader

VAO是与shader密切相关的一个内容,所以在此之前需要进行shader的一系列操作:

首先声明一个全局变量:

unsigned int shaderProgram = 0;

初始化Shader,并进行编译链接。 

void initShader(const char* _vertexPath, const char* _fragPath) {//shader的代码读取std::string _vertexCode("");std::string _fragCode("");std::ifstream _vShaderFile;std::ifstream _fShaderFile;_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);try {_vShaderFile.open(_vertexPath);_fShaderFile.open(_fragPath);std::stringstream _vShaderStream, _fShaderStream;_vShaderStream << _vShaderFile.rdbuf();_fShaderStream << _fShaderFile.rdbuf();_vertexCode = _vShaderStream.str();_fragCode = _fShaderStream.str();}catch(std::ifstream::failure e) {std::string errStr = "read shader fail";std::cout << errStr << ": " << e.what() << std::endl;}const char* _vShaderStr = _vertexCode.c_str();const char* _fShaderStr = _fragCode.c_str();//shader的编译链接unsigned int _vertexID = 0, _fragID = 0;char _infoLog[512];int _successFlag = 0;//编译_vertexID = glCreateShader(GL_VERTEX_SHADER);glShaderSource(_vertexID, 1, &_vShaderStr, nullptr);glCompileShader(_vertexID);//捕捉编译过程中的状态信息glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(_vertexID, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}_fragID = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(_fragID, 1, &_vShaderStr, nullptr);glCompileShader(_fragID);//捕捉编译过程中的状态信息glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(_fragID, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}//链接//创建一个程序shaderProgram = glCreateProgram();glAttachShader(shaderProgram, _vertexID);glAttachShader(shaderProgram, _fragID);glLinkProgram(shaderProgram);glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(shaderProgram, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}//删除中间文件glDeleteShader(_vertexID);glDeleteShader(_fragID);}

设定VAO并进行渲染

//构建模型数据:VBO,VAO
void initModel() {float vertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f};glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);//之后的VBO便属于了VAO的管理范围glGenBuffers(1, &VBO);//绑定哪一种buffer, glBindBuffer(GL_ARRAY_BUFFER, VBO);//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//打开锚点:激活glEnableVertexAttribArray(0);//解绑//glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);}
//渲染
void render() {glBindVertexArray(VAO);glUseProgram(shaderProgram);//以三角形模式绘制,从第0个顶点开始,起作用的有3个点glDrawArrays(GL_TRIANGLES, 0, 3);glUseProgram(0);
}

渲染结果:

整体源码

main.cpp

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
#include <string>
#include <fstream>
#include <sstream>void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow* window);
void initModel();
void initShader(const char* _vertexPath, const char* _fragPath);
void render();unsigned int VBO = 0;
unsigned int VAO = 0;
unsigned int shaderProgram = 0;int main() {//初始化OpenGL上下文环境,OpenGL是一个状态机,会保存当前状态下的渲染状态以及管线的状态glfwInit(); //,3版本以上glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);//用OpenGL核心开发模式glfwWindowHint(GLFW_OPENGL_PROFILE,GLFW_OPENGL_CORE_PROFILE);//创建窗体GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGl Core", nullptr, nullptr);if (window == nullptr) {std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();return -1;}//把当前上下文绑定至当前窗口glfwMakeContextCurrent(window);//通过glad绑定各种函数指针if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;return -1;}//视口:需要渲染的东西在哪里glViewport(0, 0, 800, 600);//当Frame大小变动,调用回调函数调整视口大小glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);initModel();initShader("vertexShader.glsl", "fragmentShader.glsl");//防止窗口结束退出while (!glfwWindowShouldClose(window)) {processInput(window);//擦除画布,用定义的颜色填充glClearColor(0.2f, 0.3f, 0.3f, 1.0f);glClear(GL_COLOR_BUFFER_BIT);render();//双缓冲glfwSwapBuffers(window);glfwPollEvents();}//结束,释放资源glfwTerminate();return 0;}void framebuffer_size_callback(GLFWwindow* window, int width, int height) {glViewport(0, 0, width, height);
}void processInput(GLFWwindow* window) {if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS) {glfwSetWindowShouldClose(window, true);}
}//渲染
void render() {glBindVertexArray(VAO);glUseProgram(shaderProgram);//以三角形模式绘制,从第0个顶点开始,起作用的有3个点glDrawArrays(GL_TRIANGLES, 0, 3);glUseProgram(0);
}//构建模型数据:VBO,VAO
void initModel() {float vertices[] = {-0.5f, -0.5f, 0.0f,0.5f, -0.5f, 0.0f,0.0f, 0.5f, 0.0f};glGenVertexArrays(1, &VAO);glBindVertexArray(VAO);//之后的VBO便属于了VAO的管理范围glGenBuffers(1, &VBO);//绑定哪一种buffer, glBindBuffer(GL_ARRAY_BUFFER, VBO);//分配显存:分配哪种buffer,分配显存大小,分配地址,使用数据的方式glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);//对哪个锚点进行操作:layout=0的锚点,读3个顶点,类型为float,不需要归一化,每次步长为3个float大小,从0处开始读glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);//打开锚点:激活glEnableVertexAttribArray(0);//解绑//glBindBuffer(GL_ARRAY_BUFFER, 0);glBindVertexArray(0);}//
void initShader(const char* _vertexPath, const char* _fragPath) {//shader的代码读取std::string _vertexCode("");std::string _fragCode("");std::ifstream _vShaderFile;std::ifstream _fShaderFile;_vShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);_fShaderFile.exceptions(std::ifstream::failbit | std::ifstream::badbit);try {_vShaderFile.open(_vertexPath);_fShaderFile.open(_fragPath);std::stringstream _vShaderStream, _fShaderStream;_vShaderStream << _vShaderFile.rdbuf();_fShaderStream << _fShaderFile.rdbuf();_vShaderFile.close();_fShaderFile.close();_vertexCode = _vShaderStream.str();_fragCode = _fShaderStream.str();}catch(std::ifstream::failure e) {std::string errStr = "read shader fail";std::cout << errStr << ": " << e.what() << std::endl;}const char* _vShaderStr = _vertexCode.c_str();const char* _fShaderStr = _fragCode.c_str();//shader的编译链接unsigned int _vertexID = 0, _fragID = 0;char _infoLog[512];int _successFlag = 0;//编译_vertexID = glCreateShader(GL_VERTEX_SHADER);glShaderSource(_vertexID, 1, &_vShaderStr, nullptr);glCompileShader(_vertexID);//捕捉编译过程中的状态信息glGetShaderiv(_vertexID, GL_COMPILE_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(_vertexID, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}_fragID = glCreateShader(GL_FRAGMENT_SHADER);glShaderSource(_fragID, 1, &_fShaderStr, nullptr);glCompileShader(_fragID);//捕捉编译过程中的状态信息glGetShaderiv(_fragID, GL_COMPILE_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(_fragID, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}//链接//创建一个程序shaderProgram = glCreateProgram();glAttachShader(shaderProgram, _vertexID);glAttachShader(shaderProgram, _fragID);glLinkProgram(shaderProgram);glGetProgramiv(shaderProgram, GL_LINK_STATUS, &_successFlag);if (!_successFlag) {glGetShaderInfoLog(shaderProgram, 512, nullptr, _infoLog);std::string errStr(_infoLog);std::cout << errStr << std::endl;}//删除中间文件glDeleteShader(_vertexID);glDeleteShader(_fragID);}

vertexShader.glsl 

#version 330 core
layout (location = 0) in vec3 aPos;
void main()
{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
};

fragmentShader.glsl 

#version 330 core
out vec4 FragColor;
void main()
{FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
};

这篇关于OpenGL原理与实践——核心模式(一):VBO、VAO等原理解析及项目初始设置的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/386999

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

深入探索协同过滤:从原理到推荐模块案例

文章目录 前言一、协同过滤1. 基于用户的协同过滤(UserCF)2. 基于物品的协同过滤(ItemCF)3. 相似度计算方法 二、相似度计算方法1. 欧氏距离2. 皮尔逊相关系数3. 杰卡德相似系数4. 余弦相似度 三、推荐模块案例1.基于文章的协同过滤推荐功能2.基于用户的协同过滤推荐功能 前言     在信息过载的时代,推荐系统成为连接用户与内容的桥梁。本文聚焦于

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

hdu4407(容斥原理)

题意:给一串数字1,2,......n,两个操作:1、修改第k个数字,2、查询区间[l,r]中与n互质的数之和。 解题思路:咱一看,像线段树,但是如果用线段树做,那么每个区间一定要记录所有的素因子,这样会超内存。然后我就做不来了。后来看了题解,原来是用容斥原理来做的。还记得这道题目吗?求区间[1,r]中与p互质的数的个数,如果不会的话就先去做那题吧。现在这题是求区间[l,r]中与n互质的数的和

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)