这一节,我觉得是相当有难度的。渲染一个三角形,就需要介绍GLSL语言,图形渲染管线(Graphics Pipeline)以及着色器(Shader),标准化设备坐标(NDC)等诸多概念。

图形渲染管线和坐标系统的变换当然很重要,但是我们现在还不需要懂,只要暂且弄懂这几件事就好了。

至少要知道这个吧:我们在干啥?

通过阅读这一节,你应该已经大概明白了,我们的任务就是把顶点数据(Vertex Data)输入进去,经过了好多道工序,这些数据最终被“画”在屏幕上。在本节,除了顶点着色器和片段着色器是我们自己定义的,其他“工序”都是自动进行的。

需要懂的三个名词:VAO,VBO,EBO

● 顶点数组对象:Vertex Array Object(VAO)

● 顶点缓冲对象:Vertex Buffer Object(VBO)

● 元素缓冲对象:Element Buffer Object(EBO)/ 索引缓冲对象:Index Buffer Object(IBO)

我们先从这三个东西的含义来讨论它们。VAO,VBO和EBO,指明了我们输入的顶点数据是“怎么样的”。一个顶点可能有很多属性,比如位置、颜色,法向量等等。现在我们假设我们要画一个矩形,矩形长这样:

因为任何在OpenGL中的物体都是三维的,所以我们再添加一维z=0。

VBO:数据具体是什么?

VBO存放了具体的数据,假如数据是float类型,那么VBO中存放的就是:

{ 0.0f, 0.0f, 0.0f, 2.0f, 0.0f, 0.0f, 2.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f }
VAO:数据是什么意思?

VBO只是单纯的一堆计算机中的01数据罢了,VAO承担起了解释的责任。函数glVertexAttribPointer的含义可以看Learn OpenGL,不要期待我再复制粘贴到这里哦(虽然确实可以水字数)。

glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

上面两行代码,规定了VBO的position属性。有人可能不服气了,你说position就是position啊?那我们提前瞅一眼vertex shader是怎么写的:

#version 330 core // 版本声明; 使用核心模式
layout (location = 0) in vec3 aPos; // 在顶点着色器中声明所有的输入顶点属性, 创建vec3输入变量aPos, 设定输入变量的位置值(location) void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

在这里,我们可以简单的认为,有个vec3类型的变量叫aPos,给了它一个location=0的值,而glVertexAttribPointer的第一个参数,就是去找location=0的变量,然后把对应的数据输入到这个变量里。

剩下参数的意思大致如下,(VAO清清嗓子开始说话:)这些数据都传到aPos里,数据的类型是float,一个顶点的属性包括3个float。对于输入的position属性,开头偏移量为0,也就是从第1个数据开始传,一口气传3个。

EBO:数据怎么用?

我们通过绘制两个三角形组成了一个矩形,但是现在顶点只有4个而不是6个对吧?EBO规定了按照怎样的方式绘制数据。EBO把绘制顶点的顺序放在缓冲里:

{ 0, 1, 3, 1, 2, 3 }

然后一顿操作猛如虎,在当前窗口上用6个索引对应的顶点绘制2个三角形

VAO,VBO,EBO绑定与解绑顺序

你可能对三者的绑定和解绑顺序感到有点迷糊(至少我当时有点),让我们回到这张图:

VAO不仅有指向VBO的指针, 也有指向EBO的指针。我们可以很容易想到,绑定顺序应该是这样:

一开始绑定了VAO,然后在VAO仍然被绑定的情况下,绑定VBO。VAO就指向了VBO。又在这样的情况下绑定了EBO,VAO又指向了EBO。最后,我们解除了VAO的绑定,但是VAO对VBO和EBO的俩指向并没有消失。下次再使用这个VAO,它仍然保持了对VBO和EBO的指向(这样就不用每次使用VAO都重新绑定一次相应的VBO和EBO了)。

但是,如果我们先解绑EBO和VBO,那么VAO对它们两个的指向也会消失。

比较官方的说法如下:

  • 当目标是GL_ELEMENT_ARRAY_BUFFER的时候,VAO会储存glBindBuffer的函数调用。这也意味着它也会储存解绑调用,所以确保你没有在解绑VAO之前解绑索引数组缓冲,否则它就没有这个EBO配置了。

  • 您可以在之后解除绑定VAO,这样其他VAO调用就不会意外地修改这个VAO,但这种情况很少发生。修改其他VAO需要调用glBindVertexArray(),所以我们通常不会在没有直接必要的情况下解绑定VAO(或VBO)。

具体的绑定/解绑VAO的代码如下, VBO和EBO也类似。

glBindVertexArray(VAO_1); // 绑定VAO_1
glBindVertexArray(VAO_2); // 一行代码直接实现解绑VAO_1, 并绑定VAO_2
glBindVertexArray(0); // 解绑VAO_2

在Render Loop里,也有绑定/解绑VAO的操作,我们一起来看下:

glBindVertexArray(0); // 首先,解绑
while (!glfwWindowShouldClose(window))
{
// [...]
// draw our first triangle
glUseProgram(shaderProgram);
// 在while循环开始之前, VAO解绑了。而在while循环中VAO一直是被绑着的,
// 但是为了规范性还是在render loop每次循环都绑定了
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
//glDrawArrays(GL_TRIANGLES, 0, 6);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
// 为啥no need呢, 看我写的官方说法第二条()
// glBindVertexArray(0); // no need to unbind it every time
}

下面是绑定/解绑的代码实现,可以稍微看一眼:

// 创建顺序: VAO->VBO->EBO
// VAO
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// VBO
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// EBO
glGenBuffers(1, &EBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 解绑顺序: VAO->VBO->EBO
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);

GLSL语言

和C语言类似,真的不算难,看懂本节的vertex shader和fragment shader应该还是轻松的,但是不难不代表它不重要哦。后面几节着色器会写成文件形式,然后将着色器程序(shader program)的操作都封装到一个头文件中,会方便很多。

// vertex shader
#version 330 core // 版本声明; 使用核心模式
layout (location = 0) in vec3 aPos; // 在顶点着色器中声明所有的输入顶点属性, 创建vec3输入变量aPos, 设定输入变量的位置值(location)
void main()
{
gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}
// fragment shader
#version 330 core
out vec4 FragColor; void main()
{
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}

代码实现与结果分析

我的代码可能和练习中的参考答案不太一样,如果有需要可以查阅。但是前两节的代码因为我之前重装系统没保存所以有些找不到了(目移)。从Texture开始的代码应该是都在的(应该)

● (仅存的)练习1-3代码:https://www.luogu.com.cn/paste/h5rvfget

本节代码

完成了!记录下一个小插曲,写代码的时候,写成了

#inlcude <GLFW/glfw3.h>
#include <glad/glad.h>

然后开始报错0.0,查了一下发现第二行的代码必须写在最前面QAQ(不好好看教程是这样的)

左图:EBO+线框模式。啊,右图用了VAO+填充,怎么变这样了。哦懂了,就像教程前面说的那样,我的vertices没有相应的改变,改成这样就可以输出六个点,然后正常输出矩形。

在VAO还active的时候给EBO解绑,果然神魔都没有了

练习1-1: 添加更多顶点到数据中,使用glDrawArrays,尝试绘制两个彼此相连的三角形

我的小疑惑:在哪里设置线条的颜色啊。哦,我知道了,是在这里啊。大声的念出来,out的名字是啥?FragColor!!

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"
"}\0";

wwww于是又画了一个,放在右图了(什么嘛我的配色还是蛮有艺术细菌的)。

练习1-2: 创建相同的两个三角形,但对它们的数据使用不同的VAO和VBO

练习1-3: 创建两个着色器程序,第二个程序使用一个不同的片段着色器,输出黄色;再次绘制这两个三角形,让其中一个输出为黄色

学到的知识:

// 同时生成两个可以这么写
glGenVertexArrays(2, VAOs);
glGenBuffers(2, VBOs);
// 同时清除两个可以这么写
glDeleteVertexArrays(2, VAOs);
glDeleteBuffers(2, VBOs);

1.2 HELLO 三角形的更多相关文章

  1. canvas快速绘制圆形、三角形、矩形、多边形

    想看前面整理的canvas常用API的同学可以点下面: canvas学习之API整理笔记(一) canvas学习之API整理笔记(二) 本系列文章涉及的所有代码都将上传至:项目代码github地址,喜 ...

  2. Android快乐贪吃蛇游戏实战项目开发教程-05虚拟方向键(四)四个三角形按钮

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.如何判断点击的是哪个方向键按钮 在上篇教程中我们实现了左边的三角形按钮效果, ...

  3. Android快乐贪吃蛇游戏实战项目开发教程-04虚拟方向键(三)三角形按钮效果

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.知识点讲解 当我们点击系统自带的按钮时,按钮的外观会发生变化.上篇博文中我们 ...

  4. Android快乐贪吃蛇游戏实战项目开发教程-03虚拟方向键(二)绘制一个三角形

    该系列教程概述与目录:http://www.cnblogs.com/chengyujia/p/5787111.html 一.绘制三角形 在上一篇文章中,我们已经新建了虚拟方向键的自定义控件Direct ...

  5. 酷酷的CSS3三角形运用

    概述 在早期的前端Web设计开发年代,完成一些页面元素时,我们必须要有专业的PS美工爸爸,由PS美工爸爸来切图,做一些圆角.阴影.锯齿或者一些小图标. 在CSS3出现后,借助一些具有魔力的CSS3属性 ...

  6. CSS制作三角形和按钮

    CSS制作三角形和按钮 用上一篇博文中关于边框样式的知识点,能制作出三角形和按钮. 我先说如何制作三角形吧,相信大家在平时逛网站的时候都会看到一些导航栏中的三角形吧,比如说: 网易首页的头部菜单栏中, ...

  7. Effective前端3:用CSS画一个三角形

    p { text-indent: 2em } .triangle-container p { text-indent: 0 } img { margin: 15px 0 } 三角形的场景很常见,打开一 ...

  8. 通过CSS的border绘制三角形

    通过css的border 可以绘制出三角形, 不同的样式组合,有着不同的效果,可以控制它的大小,颜色,方向.看下面各种图形,相信可能还有很多图形,大家都没见过. 先写出公共的样式: .border { ...

  9. 如何用CSS画三角形

    很多时候页面都需要一个或者多个小型三角形!多数人直接用PS扣个图片预览 下面用CSS简单画几个最终效果如下图 <div class="border-all-color"> ...

  10. [Modern OpenGL系列(三)]用OpenGL绘制一个三角形

    本文已同步发表在CSDN:http://blog.csdn.net/wenxin2011/article/details/51347008 在上一篇文章中已经介绍了OpenGL窗口的创建.本文接着说如 ...

随机推荐

  1. axios传递参数的使用

    今天在学习elasticsearch时,遇到一个问题:项目中前端采用的是Vue2+axios,后端的接口采用Restful风格来接收: 关于Resultful风格: 1. GET(SELECT):从服 ...

  2. 【RabbitMQ】12 日志监控 & 消息追踪

    一.日志和监控 RabbitMQ日志存放目录 [root@localhost ~]# ll /var/log/rabbitmq/ 总用量 176 -rw-r-----. 1 rabbitmq rabb ...

  3. 【Zookeeper】01 概述 & 基础部署

    背景: 随着互联网技术的发展,企业对计算机系统的计算,存储能力要求越来越高,各大IT企业都在追求高并发,海量存储的极致, 在这样的背景下,单纯依靠少量高性能单机来完成计算机,云计算的任务已经无法满足需 ...

  4. Electronics投稿指南

    原地址: https://m.peipusci.com/news/10593.html Electronics的自引率先增后减,2023年度为10.3%.

  5. 2024年世界体育界的第一大丑闻:利昂内尔·梅西 (The biggest scandal in the world of sports in 2024: Unethical player - Lionel Messi.)

    无德球员,梅西亲日辱华,不顾球迷感受,拒绝在中国的比赛中上场,并以所谓的伤病为借口,却在3天后的日本比赛中完全恢复如初,并进行了30分钟的高强度的对抗比赛并射门,可以说梅西的这一行径就是对中国亿万百姓 ...

  6. 使用 onBeforeRouteLeave 组合式函数提升应用的用户体验

    title: 使用 onBeforeRouteLeave 组合式函数提升应用的用户体验 date: 2024/8/14 updated: 2024/8/14 author: cmdragon exce ...

  7. Dijkstra单源最短路模板

    struct DIJ { using i64 = long long; using PII = pair<i64, i64>; vector<i64> dis; vector& ...

  8. Linux内核如何访问另外一个模块的函数和变量 原创

    一.问题整理 内核中两个模块,一个A,一个B,A模块中有操作函数,B模块要调用A模块的函数. 二.分析 这是一个驱动工程师经常遇到的一个问题,该问题其实是模块符号导出问题,实现该功能比较简单,借助EX ...

  9. Win32封装对话框类

    [主程序入口.cpp] #include <windows.h> #include <tchar.h> #include "resource.h" #inc ...

  10. Docker学习6-Docker镜像commit操作案例

    在上一篇中,我们知道了docker是基于联合文件系统的分层镜像.而且也知道了镜像是只读的,容器才是可以写的.那么,如果我们要修改镜像,修改之后,怎么提交呢?本文,凯哥将介绍,docker的提交命令 P ...