LearnOpenGL 笔记 - 入门 04 你好,三角形

news2025/1/8 19:14:30

系列文章目录

  • LearnOpenGL 笔记 - 入门 01 OpenGL
  • LearnOpenGL 笔记 - 入门 02 创建窗口
  • LearnOpenGL 笔记 - 入门 03 你好,窗口

文章目录

  • 系列文章目录
  • 前言
  • 你好,三角形
  • 顶点输入
  • 顶点着色器(Vertex Shader)
  • 编译着色器
  • 片段着色器(Fragment Shader)
  • 着色器程序
  • 链接顶点属性
  • 顶点数组对象(VAO)
  • 三角形!
  • 元素缓冲对象


前言

  • 原文链接:你好,三角形
  • 本文代码:2_1_hello_triangle.cpp

本文难度较大,学习曲线突然陡峭了起来。但没有关系,我将以一个初学者的视角来讲述自己的理解,帮助你学习 VAO、VBO、EBO、Shader 等概念。首先,仍然先以知识点列表的形式总结全文。

你好,三角形

  • 先记住三个单词
    • 顶点数组对象:Vertex Array Object,VAO
    • 顶点缓冲对象:Vertex Buffer Object,VBO
    • 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
  • OpenGL 中所有事物都在 3D 空间,而屏幕是 2D。3D坐标转为2D坐标的处理过程是由OpenGL的图形渲染管线
  • 图形渲染管线分为两个主要部分
    • 3D坐标转换为2D坐标
    • 2D坐标转变为实际的有颜色的像素
  • 图形渲染管线可分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。这些阶段容易并行执行。显卡中的核心,运行着每个阶段的小程序,这些小程序被叫做着色器(Shader)
  • 有些阶段的 Shader 可以有开发者来编写。OpenGL 着色器是用 OpenGL 着色器语言(OpenGL Shading Language, GLSL)写成的
  • 下图为渲染管线的每个阶段的抽象展示。蓝色部分可以输入自定义的 shader
    + 在这里插入图片描述
  • 管线的第一部分是顶点着色器(Vertex Shader),它输入一个顶点。作用是把 3D 坐标转为另一种 3D 坐标(后面会解释),同时顶点着色器允许我们对顶点属性进行一些基本处理。
  • 图元装配(Primitive Assembly)阶段将顶点着色器输出的所有顶点作为输入(如果是GL_POINTS,那么就是一个顶点),并所有的点装配成指定图元的形状;本节例子中是一个三角形。
  • 图元装配阶段的输出会传递给几何着色器(Geometry Shader)。几何着色器把图元形式的一系列顶点的集合作为输入,它可以通过产生新顶点构造出新的(或是其它的)图元来生成其他形状。例子中,它生成了另一个三角形。
  • 几何着色器的输出会被传入光栅化阶段(Rasterization Stage),这里它会把图元映射为最终屏幕上相应的像素,生成供片段着色器(Fragment Shader)使用的片段(Fragment)。在片段着色器运行之前会执行裁切(Clipping)。裁切会丢弃超出你的视图以外的所有像素,用来提升执行效率。
  • 片段着色器的主要目的是计算一个像素的最终颜色,这也是所有OpenGL高级效果产生的地方。通常,片段着色器包含3D场景的数据(比如光照、阴影、光的颜色等等),这些数据可以被用来计算最终像素的颜色。
  • 最终的对象将会被传到最后一个阶段,我们叫做Alpha测试和混合(Blending)阶段。这个阶段检测片段的对应的深度(和模板(Stencil))值(后面会讲),用它们来判断这个像素是其它物体的前面还是后面,决定是否应该丢弃。这个阶段也会检查alpha值(alpha值定义了一个物体的透明度)并对物体进行混合(Blend)。所以,即使在片段着色器中计算出来了一个像素输出的颜色,在渲染多个三角形的时候最后的像素颜色也可能完全不同。
  • 在现代OpenGL中,我们必须定义至少一个顶点着色器和一个片段着色器。

顶点输入

  • 绘制图形需要顶点,OpenGL 中顶点采用 3D 坐标,即 x,y 和 z,范围在 [-1, 1] 之间,我们称这个范围叫 标准化设备坐标(Normalized Device Coordinates)。在范围外的点不会显示。
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};
  • 在这里插入图片描述
  • 标准化设备坐标通过 glViewport 函数进行视口变化,转换为屏幕空间坐标。
  • vertices 中的点现在存放在内存中(你的代码内存中),我们需要将它们送至顶点着色器。顶点着色器会在显存中开辟一块空间来存放这些点。同时你要告诉 OpenGL 如何解释这些数据。
  • 顶点缓冲对象(Vertex Buffer Objects, VBO)负责管理这个显存。
unsigned int VBO;
glGenBuffers(1, &VBO);
  • 使用 glGenBuffers 创建 VBO
  • glBindBuffer(GL_ARRAY_BUFFER, VBO); 将这个 VBO 绑定到 OpenGL Context 中的顶点缓冲对象。
  • glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW); 将内存中数据复制到显存中
    • GL_STATIC_DRAW :数据不会或几乎不会改变。
    • GL_DYNAMIC_DRAW:数据会被改变很多。
    • GL_STREAM_DRAW :数据每次绘制时都会改变。

顶点着色器(Vertex Shader)

  • 现代OpenGL需要我们至少设置一个顶点和一个片段着色器。使用 GLSL(OpenGL Shading Language)编写顶点着色器。
#version 330 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
  • #version 330 core 声明版本和核心模式。OpenGL 3.3以及和更高版本中,GLSL版本号和OpenGL的版本是匹配的。
  • in 声明输入顶点属性(Input Vertex Attribute)
  • vec3 向量类型,vec.x、vec.y、vec.z 获取不同分量
  • gl_Position 是 Vertex Shader 的输出。我们必须把位置数据赋值给它。它是一个 vec4 类型

编译着色器

const char *vertexShaderSource = R"(
    #version 330
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 bPos;
    void main()
    {
        gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
)";

auto vertexShader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
glCompileShader(vertexShader);
glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
if (!success) {
    glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
    std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n"
              << infoLog << std::endl;
    return -1;
}
  • glCreateShader(GL_VERTEX_SHADER) 创建一个顶点着色器,类型是 GL_VERTEX_SHADER 表明其类型。
  • glShaderSource(vertexShader, 1,&vertexShaderSource, NULL); 设置着色器对象的源码。
  • glCompileShader(vertexShader); 编译该着色器。
  • glGetShaderiv 检查是否编译成功

片段着色器(Fragment Shader)

  • 片段着色器所做的是计算像素最后的颜色输出
#version 330 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 
  • 片段着色器只需要输出一个变量。使用 out 关键字定义该变量。
  • 编译片段着色器与编译顶点着色器类似
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

着色器程序

  • 着色器程序对象(Shader Program Object)是多个着色器合并之后并最终链接完成的版本。
  • 我们必须将多个编译好的着色器链接(Link)为一个着色器程序对象,在渲染时激活它。
unsigned int shaderProgram;
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;
    return -1;
}
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

glUseProgram(shaderProgram);
  • glCreateProgram 创建着色器程序对象
  • glAttachShader 将顶点着色器和片段着色器附加到程序对象上(程序对象属性的修改)
  • glLinkProgram(shaderProgram); 链接它们。
  • glUseProgram(shaderProgram); 将该程序对象绑定至 OpenGL Context,激活它。
  • glDeleteShader,Link 以后记得删除着色器对象,我们不再需要它们了。

链接顶点属性

  • 之前,我们通过 VBO 将一堆数据送给了顶点着色器。允许我们指定任何以顶点属性为形式的输入。
  • 这具有很强的灵活性,还意味着我们必须手动指定输入数据的哪一个部分对应顶点着色器的哪一个顶点属性。
    在这里插入图片描述
  • glVertexAttribPointer 告诉 OpenGL 如何解释 VBO 中的数据
  • glEnableVertexAttribArray 启用顶点属性;顶点属性默认是禁用的。

顶点数组对象(VAO)

  • OpenGL的核心模式要求我们使用VAO,所以它知道该如何处理我们的顶点输入。如果我们绑定VAO失败,OpenGL会拒绝绘制任何东西。
  • 一个顶点数组对象会储存以下这些内容:
    • glEnableVertexAttribArray和glDisableVertexAttribArray的调用。
    • 通过glVertexAttribPointer设置的顶点属性配置。
    • 通过glVertexAttribPointer调用与顶点属性关联的顶点缓冲对象。
  • glGenVertexArrays 创建 VAO
  • 使用 VAO 和 VBO 绘制图形流程,如下:
// ..:: 初始化代码(只运行一次 (除非你的物体频繁改变)) :: ..
// 1. 绑定VAO
glBindVertexArray(VAO);
// 2. 把顶点数组复制到缓冲中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 设置顶点属性指针
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 绘制代码(渲染循环中) :: ..
// 4. 绘制物体
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

三角形!

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
  • glDrawArrays函数,它使用当前激活的着色器,之前定义的顶点属性配置,和VBO的顶点数据(通过VAO间接绑定)来绘制图元

元素缓冲对象

  • 绘制一个矩形需要 4 个顶点,但 glDrawArrays 只能绘制点、线、和三角形。并不能绘制矩形。幸运的是,你可以绘制两个三角形来达成这个目的。顶点如下:
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,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};
  • 指定右下角和左上角两次!一个矩形只有4个而不是6个顶点,这样就产生50%的额外开销。更好的解决方案是只储存不同的顶点,并设定绘制这些顶点的顺序。这样子我们只要储存4个顶点就能绘制矩形了。EBO 就是做这个的。
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开始! 
    // 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
    // 这样可以由下标代表顶点组合成矩形

    0, 1, 3, // 第一个三角形
    1, 2, 3  // 第二个三角形
};
unsigned int EBO;
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
  • 使用 glDrawElements 使用当前绑定的索引缓冲对象中的索引进行绘制。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/336782.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot集成Flink-CDC 采集PostgreSQL变更数据发布到Kafka

一、业务价值 监听数据变化&#xff0c;进行异步通知&#xff0c;做系统内异步任务。 架构方案&#xff08;懒得写了&#xff0c;看图吧&#xff09;&#xff1a; 二、修改数据库配置 2.1、更改配置文件postgresql.conf # 更改wal日志方式为logical&#xff08;必须&#xf…

PCB设计是不是该去除孤铜

你知道PCB设计是不是该去除孤铜? PCB设计的技巧需要注意很多问题&#xff0c;各个器件的兼容问题&#xff0c;以及成品问题等等都是需要考虑的重要因素。 我们今天的主题是PCB设计的时候是不是该去除孤铜的问题?有人说应该除去&#xff0c;原因大概是&#xff1a; 会造成EMI问…

学习深入理解JVM虚拟机及JavaGuide后的学习笔记

JVM虚拟机 一、JVM组成部分&#xff1a; 1.程序计数器 作用&#xff0c;是记住下一条JVM指令的内存地址&#xff1b;1.多线程情况下&#xff0c;程序计数器用于记录当前线程执行的位置&#xff0c;从而线程切换回来的时候能够知道线程上次运行到哪儿了。2.字节码解释器通过改变…

6 前缀、中缀、后缀表达式

文章目录1 前缀表达式 1. 1 缀表达式的计算机求值 2 中缀表达式3 后缀表达式 3. 1 后缀表达式的计算机求值 3. 2 中缀表达式转换为后缀表达式1 前缀表达式 前缀表达式又称波兰式&#xff0c;前缀表达式的运算符位于操作数之前 举例说明&#xff1a; (34)5-6 对应的前缀表达式就…

Orin + SC16IS752+SP3072 SPI转串口485

文章目录 1.前言2.修改过程2.1 sc16is752 芯片2.1.1引脚说明2.1.2 设备树配置2.2.1 源码分析3 调试1.前言 Orin 有四路串口,对于多数设备来说已经够用。 通过SPI 转串口再转RS485在Orin平台应该属于极个例,所以记录一下。 串口扩展芯片: SC16IS752RS485收发器: SP3072通信…

初次尝试-注册openai并使用chatGPT

1 环境 本次我打算在win11虚拟机下进行(不打算动真机的时区啦)。 2 科学上网 这里就不多介绍了&#xff0c;使用科学上网工具连接外网即可。由于软件可连接限制&#xff0c;我这里选择美国网络。 3 更改时区 这里的时区最好和上述的所连接的地区一致。 3 登录网站 1、…

HOMER docker版本安装详细流程

概述 HOMER是一款100%开源的针对SIP/VOIP/RTC的抓包工具和监控工具。 HOMER是一款强大的、运营商级、可扩展的数据包和事件捕获系统&#xff0c;是基于HEP/EEP协议的VoIP/RTC监控应用程序&#xff0c;并可以使用即时搜索、处理和存储大量的信令、RTC事件、日志和统计信息。 …

Word控件Spire.Doc 【Table】教程(13): 如何在 C# 中向现有的 word 表添加一行

Spire.Doc for .NET是一款专门对 Word 文档进行操作的 .NET 类库。在于帮助开发人员无需安装 Microsoft Word情况下&#xff0c;轻松快捷高效地创建、编辑、转换和打印 Microsoft Word 文档。拥有近10年专业开发经验Spire系列办公文档开发工具&#xff0c;专注于创建、编辑、转…

LwIP系列--软件定时器(超时处理)详解

一、目的在TCP/IP协议栈中ARP缓存的更新、IP数据包的重组、TCP的连接超时和超时重传等都需要超时处理模块&#xff08;软件定时器&#xff09;的参与。本篇主要介绍LwIP中超时处理的实现细节。上图为超时定时器链表&#xff0c;升序排序&#xff0c;其中next_timeout为链表头&a…

pyLoad远程代码执行漏洞(CVE-2023-0297)复现以及原理流量特征分析

声明&#xff1a; 请勿用于非法入侵&#xff0c;仅供学习。传送门 -》中华人民共和国网络安全法 文章目录声明&#xff1a;pyLoad介绍漏洞介绍影响版本不受影响版本漏洞原理漏洞环境搭建以及复现流量特征分析pyLoad介绍 pyLoad是一个用 Python 编写的免费和开源下载管理器&am…

计算GPS两个点之间的距离

参考&#xff1a;Https://blog.csdn.net/u011339749/article/details/125048180任意两点对应的经纬度A(lat0,long0),B(lat1,long1)则C(lat1,long0),D(lat0,long1)。通过A、B、C、D四个点可以确定一个四边形平面。同一纬度相互平行&#xff0c;可知连接ACBD四点构成了一个等腰梯…

干货|PCB板上的丝印位号与极性符号的组装性设计

PCB板上的字符很多&#xff0c;那么字符在后期起着那些非常重要的作用呢&#xff1f;一般常见的字符:“R”代表着电阻&#xff0c;"C”代表着电容&#xff0c;“RV”表示的是可调电阻&#xff0c;“L”表示的是电感&#xff0c;“Q”表示的是三极管&#xff0c;“D”表示的…

剑指Offer 第27天 JZ75 字符流中第一个不重复的字符

字符流中第一个不重复的字符_牛客题霸_牛客网 描述 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如&#xff0c;当从字符流中只读出前两个字符 "go" 时&#xff0c;第一个只出现一次的字符是 "g" 。当从该字符流中读出前六个字符 “google&…

MDS75-16-ASEMI三相整流模块MDS75-16

编辑-Z MDS75-16在MDS封装里采用的6个芯片&#xff0c;是一款工业焊机专用大功率整流模块。MDS75-16的浪涌电流Ifsm为920A&#xff0c;漏电流(Ir)为5mA&#xff0c;其工作时耐温度范围为-40~150摄氏度。MDS75-16采用GPP硅芯片材质&#xff0c;里面有6颗芯片组成。MDS75-16的电…

ThreadPoolExecutor原理解析

1. 工作原理1.1 流程图1.2 执行示意图从上图得知如果当前运行的线程数小于corePoolSize(核心线程数)&#xff0c;则会创建新线程作为核心线程来执行任务(注意&#xff0c;执行这一步需要获取全局锁)。如果运行的线程等于或多于corePoolSize&#xff0c;则将任务加入BlockingQue…

C语言const的用法详解

有时候我们希望定义这样一种变量&#xff0c;它的值不能被改变&#xff0c;在整个作用域中都保持固定。例如&#xff0c;用一个变量来表示班级的最大人数&#xff0c;或者表示缓冲区的大小。为了满足这一要求&#xff0c;可以使用const关键字对变量加以限定&#xff1a;constin…

大型智慧校园系统源码 智慧班牌 智慧安防 家校互联 智慧校园小程序源码

一款针对中小学研发的智慧校园系统源码&#xff0c;智慧学校源码&#xff0c;系统有演示&#xff0c;可正常上线运营正版授权。 技术架构&#xff1a; 后端&#xff1a;Java 框架&#xff1a;springboot 前端页面&#xff1a;vue element-ui 小程序&#xff1a;小程序原生…

【CDP】CDP集群修改solr 存储路径 引发组件的ranger-audit 大量报错的解决方案

前言 我们生产上公司是使用的CDP集群&#xff0c;一次管理员通知&#xff0c;Solr 组件的数据存放路径磁盘空间不够。 我们的solr 组件时为 Ranger 服务提供日志审计功能&#xff0c; 在我们更改了磁盘路径&#xff0c;并重启了Solr 组件&#xff0c;然后发现相关组件&#…

立创eda专业版学习笔记(6)(pcb板移动节点)

先要看一个设置方面的东西&#xff1a; 进入设置-pcb-通用 我鼠标放到竖着的线上面&#xff0c;第一次点左键是这样选中的&#xff1a; 再点一次左键是这样选中的&#xff1a; 这个时候&#xff0c;把鼠标放到转角的地方&#xff0c;点右键&#xff0c;就会出现对于节点的选项…

关于VSCode安装go插件问题

比较常见的go开发编辑工具有VSCode、GoLand等&#xff0c;其中&#xff0c;使用VSCode需要下载相关的go语言插件。但是大多数情况都会下载失败&#xff0c;因为有些资源需要翻墙的原因&#xff0c;有时候翻墙了还是会报错。   本文将介绍一种帮助大家成功下载go插件的方法&am…