mirror of
https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git
synced 2025-08-23 04:35:28 +08:00
校对 01-04 你好,三角形
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
---|---
|
||||
作者 | JoeyDeVries
|
||||
翻译 | [Django](http://bullteacher.com/), Krasjet, Geequlim
|
||||
校对 | 暂未校对
|
||||
校对 | Kang Lin <kl222@126.com>
|
||||
|
||||
!!! note "译注"
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
- 顶点数组对象:Vertex Array Object,VAO
|
||||
- 顶点缓冲对象:Vertex Buffer Object,VBO
|
||||
- 索引缓冲对象:Element Buffer Object,EBO或Index Buffer Object,IBO
|
||||
- 元素缓冲对象:Element Buffer Object,EBO 或 索引缓冲对象 Index Buffer Object,IBO
|
||||
|
||||
当指代这三个东西的时候,可能使用的是全称,也可能用的是英文缩写,翻译的时候和原文保持的一致。由于没有英文那样的分词间隔,中文全称的部分可能不太容易注意。但请记住,缩写和中文全称指代的是一个东西。
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
|
||||
图形渲染管线接受一组3D坐标,然后把它们转变为你屏幕上的有色2D像素输出。图形渲染管线可以被划分为几个阶段,每个阶段将会把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的(它们都有一个特定的函数),并且很容易并行执行。正是由于它们具有并行执行的特性,当今大多数显卡都有成千上万的小处理核心,它们在GPU上为每一个(渲染管线)阶段运行各自的小程序,从而在图形渲染管线中快速处理你的数据。这些小程序叫做<def>着色器</def>(Shader)。
|
||||
|
||||
有些着色器允许开发者自己配置,这就允许我们用自己写的着色器来替换默认的。这样我们就可以更细致地控制图形渲染管线中的特定部分了,而且因为它们运行在GPU上,所以它们可以给我们节约宝贵的CPU时间。OpenGL着色器是用<def>OpenGL着色器语言</def>(OpenGL Shading Language, <def>GLSL</def>)写成的,在下一节中我们再花更多时间研究它。
|
||||
有些着色器可以由开发者配置,因为允许用自己写的着色器来代替默认的,所以能够更细致地控制图形渲染管线中的特定部分了。因为它们运行在GPU上,所以节省了宝贵的CPU时间。OpenGL着色器是用<def>OpenGL着色器语言</def>(OpenGL Shading Language, <def>GLSL</def>)写成的,在下一节中我们再花更多时间研究它。
|
||||
|
||||
下面,你会看到一个图形渲染管线的每个阶段的抽象展示。要注意蓝色部分代表的是我们可以注入自定义的着色器的部分。
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
## 顶点输入
|
||||
|
||||
开始绘制图形之前,我们必须先给OpenGL输入一些顶点数据。OpenGL是一个3D图形库,所以我们在OpenGL中指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把**所有的**3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上都为-1.0到1.0的范围内时才处理它。所有在所谓的<def>标准化设备坐标</def>(Normalized Device Coordinates)范围内的坐标才会最终呈现在屏幕上(在这个范围以外的坐标都不会显示)。
|
||||
开始绘制图形之前,我们需要先给OpenGL输入一些顶点数据。OpenGL是一个3D图形库,所以在OpenGL中我们指定的所有坐标都是3D坐标(x、y和z)。OpenGL不是简单地把**所有的**3D坐标变换为屏幕上的2D像素;OpenGL仅当3D坐标在3个轴(x、y和z)上-1.0到1.0的范围内时才处理它。所有在这个范围内的坐标叫做<def>标准化设备坐标</def>(Normalized Device Coordinates),此范围内的坐标最终显示在屏幕上(在这个范围以外的坐标则不会显示)。
|
||||
|
||||
由于我们希望渲染一个三角形,我们一共要指定三个顶点,每个顶点都有一个3D位置。我们会将它们以标准化设备坐标的形式(OpenGL的可见区域)定义为一个`float`数组。
|
||||
|
||||
@@ -76,7 +76,7 @@ float vertices[] = {
|
||||
};
|
||||
```
|
||||
|
||||
由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。这样子的话三角形每一点的**深度**(Depth,译注2)都是一样的,从而使它看上去像是2D的。
|
||||
由于OpenGL是在3D空间中工作的,而我们渲染的是一个2D三角形,我们将它顶点的z坐标设置为0.0。这样子的话三角形每一点的*深度*(Depth,译注2)都是一样的,从而使它看上去像是2D的。
|
||||
|
||||
!!! note "译注2"
|
||||
|
||||
@@ -92,7 +92,7 @@ float vertices[] = {
|
||||
|
||||
与通常的屏幕坐标不同,y轴正方向为向上,(0, 0)坐标是这个图像的中心,而不是左上角。最终你希望所有(变换过的)坐标都在这个坐标空间中,否则它们就不可见了。
|
||||
|
||||
你的标准化设备坐标接着会变换为<def>屏幕空间坐标</def>(Screen-space Coordinates),这是使用你通过<fun>glViewport</fun>函数提供的数据,进行<def>视口变换</def>(Viewport Transform)完成的。所得的屏幕空间坐标又会被变换为片段输入到片段着色器中。
|
||||
通过使用由<fun>glViewport</fun>函数提供的数据,进行<def>视口变换</def>(Viewport Transform),<def>标准化设备坐标</def>(Normalized Device Coordinates)会变换为<def>屏幕空间坐标</def>(Screen-space Coordinates)。所得的屏幕空间坐标又会被变换为片段输入到片段着色器中。
|
||||
|
||||
定义这样的顶点数据以后,我们会把它作为输入发送给图形渲染管线的第一个处理阶段:顶点着色器。它会在GPU上创建内存用于储存我们的顶点数据,还要配置OpenGL如何解释这些内存,并且指定其如何发送给显卡。顶点着色器接着会处理我们在内存中指定数量的顶点。
|
||||
|
||||
@@ -127,7 +127,7 @@ glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
|
||||
|
||||
三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它的使用类型最好是<var>GL_STATIC_DRAW</var>。如果,比如说一个缓冲中的数据将频繁被改变,那么使用的类型就是<var>GL_DYNAMIC_DRAW</var>或<var>GL_STREAM_DRAW</var>,这样就能确保显卡把数据放在能够高速写入的内存部分。
|
||||
|
||||
现在我们已经把顶点数据储存在显卡的内存中,用<var>VBO</var>这个顶点缓冲对象管理。下面我们会创建一个顶点和片段着色器来真正处理这些数据。现在我们开始着手创建它们吧。
|
||||
现在我们已经把顶点数据储存在显卡的内存中,用<var>VBO</var>这个顶点缓冲对象管理。下面我们会创建一个顶点着色器和片段着色器来真正处理这些数据。现在我们开始着手创建它们吧。
|
||||
|
||||
## 顶点着色器
|
||||
|
||||
@@ -407,9 +407,9 @@ glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
如果你的输出和这个看起来不一样,你可能做错了什么。去查看一下源码,检查你是否遗漏了什么东西,或者你也可以在评论区提问。
|
||||
|
||||
## 索引缓冲对象
|
||||
## 元素缓冲对象
|
||||
|
||||
在渲染顶点这一话题上我们还有最后一个需要讨论的东西——索引缓冲对象(Element Buffer Object,EBO,也叫Index Buffer Object,IBO)。要解释索引缓冲对象的工作方式最好还是举个例子:假设我们不再绘制一个三角形而是绘制一个矩形。我们可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:
|
||||
在渲染顶点这一话题上我们还有最后一个需要讨论的东西——元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)。要解释元素缓冲对象的工作方式最好还是举个例子:假设我们不再绘制一个三角形而是绘制一个矩形。我们可以绘制两个三角形来组成一个矩形(OpenGL主要处理三角形)。这会生成下面的顶点的集合:
|
||||
|
||||
```c++
|
||||
float vertices[] = {
|
||||
@@ -426,7 +426,7 @@ float vertices[] = {
|
||||
|
||||
可以看到,有几个顶点叠加了。我们指定了`右下角`和`左上角`两次!一个矩形只有4个而不是6个顶点,这样就产生50%的额外开销。当我们有包括上千个三角形的模型之后这个问题会更糟糕,这会产生一大堆浪费。更好的解决方案是只储存不同的顶点,并设定绘制这些顶点的顺序。这样子我们只要储存4个顶点就能绘制矩形了,之后只要指定绘制的顺序就行了。如果OpenGL提供这个功能就好了,对吧?
|
||||
|
||||
很幸运,索引缓冲对象的工作方式正是这样的。和顶点缓冲对象一样,EBO也是一个缓冲,它专门储存索引,OpenGL调用这些顶点的索引来决定该绘制哪个顶点。所谓的<def>索引绘制</def>(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:
|
||||
值得庆幸的是,元素缓冲区对象的工作方式正是如此。 EBO是一个缓冲区,就像一个顶点缓冲区对象一样,它存储 OpenGL 用来决定要绘制哪些顶点的索引。这种所谓的<def>索引绘制</def>(Indexed Drawing)正是我们问题的解决方案。首先,我们先要定义(不重复的)顶点,和绘制出矩形所需的索引:
|
||||
|
||||
```c++
|
||||
float vertices[] = {
|
||||
@@ -436,13 +436,17 @@ float vertices[] = {
|
||||
-0.5f, 0.5f, 0.0f // 左上角
|
||||
};
|
||||
|
||||
unsigned int indices[] = { // 注意索引从0开始!
|
||||
unsigned int indices[] = {
|
||||
// 注意索引从0开始!
|
||||
// 此例的索引(0,1,2,3)就是顶点数组vertices的下标,
|
||||
// 这样可以由下标代表顶点组合成矩形
|
||||
|
||||
0, 1, 3, // 第一个三角形
|
||||
1, 2, 3 // 第二个三角形
|
||||
};
|
||||
```
|
||||
|
||||
你可以看到,当使用索引的时候,我们只定义了4个顶点,而不是6个。下一步我们需要创建索引缓冲对象:
|
||||
你可以看到,当使用索引的时候,我们只定义了4个顶点,而不是6个。下一步我们需要创建元素缓冲对象:
|
||||
|
||||
```c++
|
||||
unsigned int EBO;
|
||||
@@ -456,7 +460,7 @@ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
|
||||
```
|
||||
|
||||
要注意的是,我们传递了<var>GL_ELEMENT_ARRAY_BUFFER</var>当作缓冲目标。最后一件要做的事是用<fun>glDrawElements</fun>来替换<fun>glDrawArrays</fun>函数,来指明我们从索引缓冲渲染。使用<fun>glDrawElements</fun>时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:
|
||||
注意:我们传递了<var>GL_ELEMENT_ARRAY_BUFFER</var>当作缓冲目标。最后一件要做的事是用<fun>glDrawElements</fun>来替换<fun>glDrawArrays</fun>函数,表示我们要从索引缓冲区渲染三角形。使用<fun>glDrawElements</fun>时,我们会使用当前绑定的索引缓冲对象中的索引进行绘制:
|
||||
|
||||
```c++
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
|
||||
@@ -465,7 +469,7 @@ glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
||||
|
||||
第一个参数指定了我们绘制的模式,这个和<fun>glDrawArrays</fun>的一样。第二个参数是我们打算绘制顶点的个数,这里填6,也就是说我们一共需要绘制6个顶点。第三个参数是索引的类型,这里是<var>GL_UNSIGNED_INT</var>。最后一个参数里我们可以指定EBO中的偏移量(或者传递一个索引数组,但是这是当你不在使用索引缓冲对象的时候),但是我们会在这里填写0。
|
||||
|
||||
<fun>glDrawElements</fun>函数从当前绑定到<var>GL_ELEMENT_ARRAY_BUFFER</var>目标的EBO中获取索引。这意味着我们必须在每次要用索引渲染一个物体时绑定相应的EBO,这还是有点麻烦。不过顶点数组对象同样可以保存索引缓冲对象的绑定状态。VAO绑定时正在绑定的索引缓冲对象会被保存为VAO的元素缓冲对象。绑定VAO的同时也会自动绑定EBO。
|
||||
<fun>glDrawElements</fun>函数从当前绑定到<var>GL_ELEMENT_ARRAY_BUFFER</var>目标的EBO中获取其索引。这意味着我们每次想要使用索引渲染对象时都必须绑定相应的EBO,这又有点麻烦。碰巧顶点数组对象也跟踪元素缓冲区对象绑定。在绑定VAO时,绑定的最后一个元素缓冲区对象存储为VAO的元素缓冲区对象。然后,绑定到VAO也会自动绑定该EBO。
|
||||
|
||||

|
||||
|
||||
@@ -494,7 +498,7 @@ glEnableVertexAttribArray(0);
|
||||
// ..:: 绘制代码(渲染循环中) :: ..
|
||||
glUseProgram(shaderProgram);
|
||||
glBindVertexArray(VAO);
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
|
||||
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
|
||||
glBindVertexArray(0);
|
||||
```
|
||||
|
||||
|
@@ -20,7 +20,7 @@
|
||||
- **标准化设备坐标(Normalized Device Coordinates, NDC)**: 顶点在通过在剪裁坐标系中剪裁与透视除法后最终呈现在的坐标系。所有位置在NDC下-1.0到1.0的顶点将不会被丢弃并且可见。
|
||||
- **顶点缓冲对象(Vertex Buffer Object)**: 一个调用显存并存储所有顶点数据供显卡使用的缓冲对象。
|
||||
- **顶点数组对象(Vertex Array Object)**: 存储缓冲区和顶点属性状态。
|
||||
- **索引缓冲对象(Element Buffer Object)**: 一个存储索引供索引化绘制使用的缓冲对象。
|
||||
- **元素缓冲对象(Element Buffer Object,EBO),也叫索引缓冲对象(Index Buffer Object,IBO)**: 一个存储元素索引供索引化绘制使用的缓冲对象。
|
||||
- **Uniform**: 一个特殊类型的GLSL变量。它是全局的(在一个着色器程序中每一个着色器都能够访问uniform变量),并且只需要被设定一次。
|
||||
- **纹理(Texture)**: 一种包裹着物体的特殊类型图像,给物体精细的视觉效果。
|
||||
- **纹理缠绕(Texture Wrapping)**: 定义了一种当纹理顶点超出范围(0, 1)时指定OpenGL如何采样纹理的模式。
|
||||
|
Reference in New Issue
Block a user