这一节,我觉得是相当有难度的。渲染一个三角形,就需要介绍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. Gymnasium 环境搭建

    [默认在链接公网环境]!!!! 一.      Conda虚拟环境搭建[安装则忽略] 1.1 检查本地适配python版本 >python -V 1.2根据版本下载并安装aconda[这里默认使 ...

  2. 9、SpringMVC之处理静态资源

    9.1.环境搭建 9.1.1.在project创建新module 9.1.2.选择maven 9.1.3.设置module名称和路径 9.1.4.module初始状态 9.1.5.配置打包方式和引入依 ...

  3. git clone 如何通过proxy进行远程代码仓库拷贝下载

    git使用proxy的方式和ssh的情况是差不多的,给出借鉴: SSH如何通过proxy进行服务器连接 ------------------------------------------------ ...

  4. [COCI2015-2016#1] UZASTOPNI 题解

    前言 题目链接:洛谷. 题意简述 一棵有根树,节点数 \(n \leq 10^5\),每个点有权值 \(v_i \leq 2000\),现在选出一些点,满足: 一个点的父亲点若未被选择则其不能被选择. ...

  5. 新版的Django Docker部署方案,多阶段构建、自动处理前端依赖

    前言 前几天的文章中,我们已经把使用 pdm 的项目用 docker 搞定了,那么下一步就是把完整的 DjangoStarter v3 版本用 docker 部署. 现在不像之前那么简单直接一把梭了, ...

  6. mysql8.0 主从架构模式【0到1架构系列】

    前提条件 准备3,4,5台虚拟机 祼装mysql8.0 主从架构 常见两种模式"一主多从"和"级联复制"两种,基本都很简单,都是依赖binlog日志文件进行同步 ...

  7. Lambert cos 定律再积分无穷级数求和

    设有能量为 \(I\) 的一束光射向表面 \(s\),发生理想的漫反射.设反射率为 \(a\),则 \(s\) 向在 \(\phi\) 方向反射的能量 \(R\) 可由 Lambert cos 定律给 ...

  8. BST 二叉搜索树 BinarySearchTree C++实现(递归/非递归)

    目录 二叉搜索树 基本概念 常用结论 用途 二叉搜索树的性能分析 二叉搜索树的操作 查找 插入 删除 代码实现 BSTree.hpp test.cc 二叉搜索树 基本概念 二叉搜索树(BST,Bina ...

  9. 为什么重写hashCode一定也要重写equals方法?

    这是一个经典的问题,我们先从==开始看起 == "==" 是运算符 如果比较的对象是基本数据类型,则比较的是其存储的值是否相等: 如果比较的是引用数据类型,则比较的是所指向对象的地 ...

  10. python png 转 icon文件

    pip install pillow from PIL import Image def png_to_ico(png_path, ico_path): img = Image.open(png_pa ...