1
0
mirror of https://github.com/LearnOpenGL-CN/LearnOpenGL-CN.git synced 2025-08-23 04:35:28 +08:00
Files
LearnOpenGL-CN/01 Getting started/04 Hello Triangle.md
Meow J db7a0d06a5 Mkdocs test
Hello Triangle is not completed
2015-07-11 20:38:58 +08:00

13 KiB
Raw Blame History

本文作者JoeyDeVriesDjango翻译自http://learnopengl.com

Hello Triangle

在OpenGL中任何事物都在3D空间中但是屏幕和窗口是一个2D像素阵列所以OpenGL的大部分工作都是关于如何把3D坐标转变为适应你的屏幕的2D像素。3D坐标转为2D坐标的处理过程是由OpenGL的图形输送管道(pipeline大多译为管线实际上指的是一堆原始图形数据途经一个输送管道期间经过各种变化处理最终出现在屏幕的过程)管理的。图形输送管道可以被划分为两个主要部分第一个部分把你的3D坐标转换为2D坐标第二部分是把2D坐标转变为实际的有颜色的像素。这个教程里我们会简单地讨论一下图形输送管道以及如何使用它创建一些像素这对我们来说是有好处的。

2D坐标和像素也是不同的2D坐标是在2D空间中的一个点的非常精确的表达2D像素是这个点的近似它受到你的屏幕/窗口解析度的限制。

图形输送管道接收一组3D坐标然后把它们转变为你屏幕上的有色2D像素。图形输送管道可以被划分为几个阶段每个阶段需要把前一个阶段的输出作为输入。所有这些阶段都是高度专门化的它们有一个特定的函数它们能简单地并行执行。由于它们的并行执行的特征当今大多数显卡都有成千上万的小处理核心在GPU上为每一个输送管道的阶段运行各自的小程序从而在图形输送管道中快速处理你的数据。这些小程序叫做着色器(Shader)。

有些着色器允许开发者自己配置我们可以用自己写的着色器替换默认的。这样我们就可以更细致地控制输送管道的特定的部分了因为它们运行在GPU上它们也会节约宝贵的CPU时间。着色器是用OpenGL着色器语言(OpenGL Shading Language, GLSL)写成的,我们在下节会花更多时间研究它。

在下面,你会看到一个图形输送管道的每个阶段的抽象表达。要注意蓝色部分代表的是我们可以自定义的着色器。

如你所见,图形输入管道包含很多部分,每个都是将你的顶点数据转变为最后渲染出来的像素这个大过程中的一个特定阶段。我们会概括性地解释输送管道的每个部分,从而使你对输送管道的工作方式有个大概了解。

我们以数组的形式传递3个3D坐标作为图形输送管道的输入它用来表示一个三角形这个数组叫做顶点数据Vertex Data这里顶点数据是一些顶点的集合。一个顶点是一个3D坐标的集合也就是x、y、z数据。而顶点数据是用顶点属性vertex attributes表示的它可以包含任何我们希望用的数据但是简单起见我们还是假定每个顶点只由一个3D位置①和几个颜色值组成的吧。

①译注:当我们谈论一个“位置”的时候它代表在一个“空间”中所处地点的这个特殊属性同时“空间”代表着任何一种坐标系比如x、y、z三维坐标系x、y二维坐标系或者一条直线上的x和y的线性关系只不过二维坐标系是一个扁扁的平面空间而一条直线是一个很瘦的长长的空间。

为了让OpenGL知道我们的坐标和颜色值构成的到底是什么OpenGL需要你去提示你希望这些数据所表示的是什么类型。我们是希望把这些数据渲染成一系列的点一系列的三角形还是仅仅是一个长长的线做出的这些提示叫做**基本图形**(Primitives)任何一个绘制命令的调用都必须把基本图形类型传递给OpenGL。这是其中的几个`GL_POINTS`、`GL_TRIANGLES`、`GL_LINE_STRIP`。

输送管道的第一个部分是顶点着色器(Vertex Shader)它把一个单独的顶点作为输入。顶点着色器主要的目的是把3D坐标转为另一种3D坐标后面会解释同时顶点着色器允许我们对顶点属性进行一些基本处理。

基本图形组装Primitive Assembly阶段把顶点着色器的表示为基本图形的所有顶点作为输入如果选择的是GL_POINTS,那么就是一个单独顶点),把所有点组装为特定的基本图形的形状;本节例子是一个三角形。

基本图形组装阶段的输出会传递给几何着色器Geometry Shader。几何着色器把基本图形形式的一系列顶点的集合作为输入它可以通过产生新顶点构造出新的或是其他的基本图形来生成其他形状。例子中它生成了另一个三角形。

细分着色器Tessellation Shaders拥有把给定基本图形细分为更多小基本图形的能力。这样我们就能在物体更接近玩家的时候通过创建更多的三角形的方式创建出更加平滑的视觉效果。

细分着色器的输出会进入像素化Rasterization也译为光栅化阶段这里它会把基本图形映射为屏幕上相应的像素生成供片段着色器Fragment Shader使用的片段(Fragment)②。在片段着色器运行之前,会执行裁切clipping。裁切会丢弃超出你的视图以外的那些像素来提升执行效率。

②译注Fragment通常译为片段但从根本上来说它就是带有一些额外信息的像素由于带有额外信息OpenGL就没有给它取名叫“像素”所以“片段”的中文译法比较不准确听起来就像一个fragment可能包含多个像素一样实际上一个fragment只包含一个像素现在你只要简单记住它是像素就行了。后面你会知道除了像素信息以外它还包含了什么。

OpenGL中的一个fragment是OpenGL渲染一个独立像素所需的所有数据。

片段着色器的主要目的是计算一个像素的最终颜色这也是OpenGL高级效果产生的地方。通常片段着色器包含用来计算像素最终颜色的3D场景的一些数据比如光照、阴影、光的颜色等等

在所有相应颜色值确定以后,最终它会传到另一个阶段,我们叫做alpha测试混合Blending阶段。这个阶段检测像素的相应的深度和Stencil后面会讲使用这些来检查这个像素是否在另一个物体的前面或后面如此做到相应取舍。这个阶段也会查看alpha值alpha值是一个物体的透明度值和物体之间的混合Blend。所以即使在片段着色器中计算出来了一个像素所输出的颜色最后的像素颜色在渲染多个三角形的时候也可能完全不同。

正如你所见的那样,图形输送管道非常复杂,它包含很多要配置的部分。然而,对于大多数场合,我们必须做的只是顶点和片段着色器。几何着色器和细分着色器是可选的,通常使用默认的着色器就行了。

在现代OpenGL中我们必须定义至少一个顶点着色器和一个片段着色器因为GPU中没有默认的顶点/片段着色器。出于这个原因开始学习现代OpenGL的时候非常困难因为在你能够渲染自己的第一个三角形之前需要一大堆知识。这章结束就是你可以最终渲染出你的三角形的时候你也会了解到很多图形编程知识。

顶点输入

开始绘制一些东西之前我们必须给OpenGL输入一些顶点数据。OpenGL是一个3D图形库所以我们在OpenGL中指定的所有坐标都是在3D坐标里x、y和z。OpenGL不是简单的把你所有的3D坐标变换为你屏幕上的2D像素OpenGL只是在当它们的3个轴x、y和z在特定的-1.0到1.0的范围内时才处理3D坐标。所有在这个范围内的坐标叫做标准化设备坐标Normalized Device Coordinates会最终显示在你的屏幕上所有出了这个范围的都不会显示

由于我们希望渲染一个三角形我们指定所有的这三个顶点都有一个3D位置。我们把它们以GLfloat数组的方式定义为标准化设备坐标也就是在OpenGL的可见区域中。

GLfloat vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

由于OpenGL是在3D空间中工作的我们渲染一个2D三角形它的每个顶点都要有一个z坐标0.0。在这样的方式中三角形的每一处的深度depth都一样从而使它看上去就像2D的③。

③译注通常深度可以理解为z坐标它代表一个像素在空间中和你的距离如果离你远就可能被别的像素遮挡你就看不到它了它会被丢弃以节省资源。

**标准化设备坐标**

一旦你的顶点坐标已经在顶点着色器中处理过,它们就应该是标准化设备坐标标准化设备坐标是一个x、y和z值在-1.0到1.0的一小段空间。任何落在范围外的坐标都会被丢弃/裁剪不会显示在你的屏幕上。下面你会看到我们定义的在标准化设备坐标中的三角形忽略z轴

与通常的屏幕坐标不同y轴正方向上的点和0, 0坐标是这个图像的中心而不是左上角。最后你希望所有变换过的坐标都在这个坐标空间中否则它们就不可见了。

你的标准化设备坐标接着会变换为屏幕空间坐标screen-space coordinates这是使用你通过glViewport函数提供的数据,进行视口变换viewport transform完成的。最后的屏幕空间坐标被变换为像素输入到片段着色器。

有了这样的顶点数据我们会把它作为输入发送给图形输送管道的第一个处理阶段顶点着色器。它会在GPU上创建储存空间用于储存我们的顶点数据还要配置OpenGL如何解释这些内存并且指定如何发送给显卡。顶点着色器接着会处理我们告诉它要处理内存中的顶点的数量。

我们通过顶点缓冲对象Vertex Buffer Objects, VBO管理这个内存它会在GPU内存储存大批顶点。使用这些缓冲对象的好处是我们可以一次性的发送一大批数据到显卡上而不是每个顶点发送一次。从CPU把数据发送到显卡相对较慢所以无论何处我们都要尝试尽量一次性发送尽可能多的数据。当数据到了显卡内存中时顶点着色器几乎立即就能获得顶点这非常快。

顶点缓冲对象VBO是我们在OpenGL教程中第一个出现的OpenGL对象。就像OpenGL中的其他对象一样这个缓冲有一个独一无二的ID所以我们可以使用glGenBuffers函数生成一个缓冲ID

GLuint VBO;
glGenBuffers(1, &VBO);  

OpenGL有很多缓冲对象类型GL_ARRAY_BUFFER是其中一个顶点缓冲对象的缓冲类型。OpenGL允许我们同时绑定多个缓冲只要它们是不同的缓冲类型。我们可以使用glBindBuffer函数把新创建的缓冲绑定到GL_ARRAY_BUFFER上:

glBindBuffer(GL_ARRAY_BUFFER, VBO);  

从这一刻起,我们使用的任何缓冲函数(在GL_ARRAY_BUFFER目标上)都会用来配置当前绑定的缓冲(VBO)。然后我们可以调用glBufferData函数,它会把之前定义的顶点数据复制到缓冲的内存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

glBufferData是一个用来把用户定义数据复制到当前绑定缓冲的函数。它的第一个参数是我们希望把数据复制到上面的缓冲类型:顶点缓冲对象当前绑定到GL_ARRAY_BUFFER目标上。第二个参数指定我们希望传递给缓冲的数据的大小(字节);用一个简单的sizeof计算出顶点数据就行。第三个参数是我们希望发送的真实数据。

第四个参数指定了我们希望显卡如何管理给定的数据。有三种形式:

  • GL_STATIC_DRAW数据不会或几乎不会改变。
  • GL_DYNAMIC_DRAW数据会被改变很多。
  • GL_STREAM_DRAW数据每次绘制时都会改变。

三角形的位置数据不会改变,每次渲染调用时都保持原样,所以它使用的类型最好是GL_STATIC_DRAW。如果,比如,一个缓冲中的数据将频繁被改变,那么使用的类型就是GL_DYNAMIC_DRAWGL_STREAM_DRAW。这样就能确保图形卡把数据放在高速写入的内存部分。

现在我们把顶点数据储存在显卡的内存中用VBO顶点缓冲对象管理。下面我们会创建一个顶点和片段着色器来处理这些数据所以我们开始创建它们吧。

顶点着色器