http://www.arcsynthesis.org/gltut/Basics/Tut01%20Following%20the%20Data.html
Following the Data
In the basic background section, we described the functioning of the OpenGL pipeline. We will now revisit this pipeline in the context of the code in tutorial 1. This will give us an understanding about the specifics of how OpenGL goes about rendering data.
The first stage in the rasterization pipeline is transforming vertices to clip space. Before OpenGL can do this however, it must receive a list of vertices. So the very first stage of the pipeline is sending triangle data to OpenGL.
This is the data that we wish to transfer:
const float vertexPositions[] = { 0.75f, 0.75f, 0.0f, 1.0f, 0.75f, -0.75f, 0.0f, 1.0f, -0.75f, -0.75f, 0.0f, 1.0f, };
Each line of 4 values represents a 4D position of a vertex. These are four dimensional because, as you may recall, clip-space is 4D as well. These vertex positions are already in clip space. What we want OpenGL to do is render a triangle based on this vertex data. Since every 4 floats represents a vertex's position, we have 3 vertices: the minimum number for a triangle.
Even though we have this data, OpenGL cannot use it directly. OpenGL has some limitations on what memory it can read from. You can allocate vertex data all you want yourself; OpenGL cannot directly see any of your memory. Therefore, the first step is to allocate some memory that OpenGL can see, and fill that memory with our data. This is done with something called a buffer object.
虽然我们现在有了这个数据,但OpenGL不能直接使用它.OpenGL对于它能读取的数据来源有些限制.你可以完全自己指定顶点数据;OpenGL看不到你内存的数据.因此,第一步的工作是使用我们定义的数据来指定OpenGL能看到的数据,这是通过buffer object来完成.
A buffer object is a linear array of memory, managed and allocated by OpenGL at the behest of the user. The content of this memory is controlled by the user, but the user has only indirect control over it. Think of a buffer object as an array of GPU memory. The GPU can read this memory quickly, so storing data in it has performance advantages.
一个buffer object是内存中的一个线性数组,用户可以管理和定位它.用户可以管理这一部分内存的内容,但是只是间接管理.可以把buffer object看做GPU内存上的一个数组.GPU可以快速读取它,所以把数据存储在buffer object会有性能的提高.
The buffer object in the tutorial was created during initialization. Here is the code responsible for creating the buffer object:
下面的代码是创建一个buffer object:
Example 1.2. Buffer Object Initialization
void InitializeVertexBuffer() { glGenBuffers(1, &positionBufferObject); glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject); glBufferData(GL_ARRAY_BUFFER, sizeof(vertexPositions), vertexPositions, GL_STATIC_DRAW); glBindBuffer(GL_ARRAY_BUFFER, 0); }
The first line creates the buffer object, storing the handle to the object in the global variable positionBufferObject
. Though the object now exists, it does not own any memory yet. That is because we have not allocated any with this object.
glGenBuffers函数创建一个buffer object,并将buffer object的handle保存在全局变量 positionBufferObject
中.虽然现在buffer object已经存在了,但它还没有自己的内存区域.因为我们没有为这个buffer object指定数据.
The glBindBuffer
function binds the newly-created buffer object to the GL_ARRAY_BUFFER
binding target. As mentioned in the introduction, objects in OpenGL usually have to be bound to the context in order for them to do anything, and buffer objects are no exception.
glBindBuffer
函数绑定一个新产生的buffer object到GL_ARRAY_BUFFER所绑定的目的地.前文提到的,OpenGL中的object通常会绑定到context以方便他们进行接下来的工作,buffer objects 没有异常处理机制.
The glBufferData
function performs two operations. It allocates memory for the buffer currently bound to GL_ARRAY_BUFFER
, which is the one we just created and bound. We already have some vertex data; the problem is that it is in our memory rather than OpenGL's memory. Thesizeof(vertexPositions)
uses the C++ compiler to determine the byte size of the vertexPositions
array. We then pass this size toglBufferData
as the size of memory to allocate for this buffer object. Thus, we allocate enough GPU memory to store our vertex data.
glBufferData
函数执行两个操作.第一个:它为我们刚才创建和绑定到 GL_ARRAY_BUFFER
的buffer指定内存.我们已经有顶点数据,问题是它是在我们主机的内存而不是OpenGL的能看到的GPU内存(让我想起了cuda...)
.sizeof(vertexPositions)
返回 vertexPositions
数组的大小.然后我们将 vertexPositions
的size传递给glBufferData用来指定buffer object的大小.这样我们就指定了足够的GPU内存来存储顶点数据.
The other operation that glBufferData
performs is copying data from our memory array into the buffer object. The third parameter controls this. If this value is not NULL, as in this case, glBufferData
will copy the data referenced by the pointer into the buffer object. After this function call, the buffer object stores exactly what vertexPositions
stores.
glBufferData函数执行的第二个操作是将我们的CPU内存的数据拷贝到GPU内存的buffer object.第三个参数来控制这个.如果这个值不是NULL,想这个例子中,glBufferData将会复制pointer所关联的数据到GPU上的buffer object.函数调用结束后,buffer object将会存储着
vertexPositions
数组所存储的内容.
The fourth parameter is something we will look at in future tutorials.第四个参数待会回讲到.
The second bind buffer call is simply cleanup. By binding the buffer object 0 to GL_ARRAY_BUFFER
, we cause the buffer object previously bound to that target to become unbound from it. Zero in this cases works a lot like the NULL pointer. This was not strictly necessary, as any later binds to this target will simply unbind what is already there. But unless you have very strict control over your rendering, it is usually a good idea to unbind the objects you bind.
第二个glBindBuffer是一个清理函数,通过给GL_ARRAY_BUFFER
绑定0,我们将会使前面绑定的buffer object解除绑定.0在这里有点像NULL指针.这一步不是必须操作,接下来的下一个绑定将会自动解除刚才的绑定.但是如果你需要严格控制rendering,这是一个好主意.
This is all just to get the vertex data in the GPU's memory. But buffer objects are not formatted; as far as OpenGL is concerned, all we did was allocate a buffer object and fill it with random binary data. We now need to do something that tells OpenGL that there is vertex data in this buffer object and what form that vertex data takes.
这些全部是如何在GPU内存中获得顶点数据.但是buffer object 不是格式化的;目前OpenGL关心的,我们所做的只是指定一个buffer object然后赋值为随机的二进制数据.我们现在需要告诉OpenGL现在在buffer object中有数据和数据的形式.我们会在rendering函数中进行,也就是以下代码的目的.
We do this in the rendering code. That is the purpose of these lines:
glBindBuffer(GL_ARRAY_BUFFER, positionBufferObject); glEnableVertexAttribArray(0); glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
The first function we have seen before. It simply says that we are going to use this buffer object.
第一个函数我们已经见过,它表示我们将会使用这个buffer object.
The second function, glEnableVertexAttribArray
is something we will explain in the next section. Without this function, the next one is unimportant.
第二个函数glEnableVertexAttribArray
函数接下来会讲到.没有这个函数,下面的函数将不起作用.
The third function is the real key. glVertexAttribPointer
, despite having the word “Pointer” in it, does not deal with pointers. Instead, it deals with buffer objects.
第三个函数是是重点.glVertexAttribPointer函数,不管它中的"Pointer"单词,它没有操作指针.相反,它操作buffer object.
When rendering, OpenGL pulls vertex data from arrays stored in buffer objects. What we need to tell OpenGL is what format our vertex array data in the buffer object is stored in. That is, we need to tell OpenGL how to interpret the array of data stored in the buffer.
当rendering时候,OpenGL将数据从数组中存储到buffer object中.我们需要做的是告诉OpenGL在buffer object中存储的顶点数组的存储形式.也就是,我们需要告诉OpenGL怎样对存储在buffer中的数组进行interpret(解释).
In our case, our data is formatted as follows:
-
Our position data is stored in 32-bit floating point values using the C/C++ type float.
-
Each position is composed of 4 of these values.
-
There is no space between each set of 4 values. The values are tightly packed in the array.
-
The first value in our array of data is at the beginning of the buffer object.
在我们的例子中,数据有如下形式:
-
position数据存储使用C/C++ 中的float类型存储在32位浮点类型值中.
-
每个position有4个这种值.
-
4个值之间没有空隙,他们在数组中是紧密排列的.
-
数据数组的第一个值是buffer object的起始位置.
The
glVertexAttribPointer
function tells OpenGL all of this. The third parameter specifies the base type of a value. In this case, it isGL_FLOAT
, which corresponds to a 32-bit floating-point value. The second parameter specifies how many of these values represent a single piece of data. In this case, that is 4. The fifth parameter specifies the spacing between each set of values. In our case, there is no space between values, so this value is 0. And the sixth parameter specifies the byte offset from the value in the buffer object is at the front, which is 0 bytes from the beginning of the buffer object.glVertexAttribPointer
函数告诉OpenGL全部情况.第三个参数标识value的基本类型.这个例子中是GL_FLOAT,对应一个32位浮点类型数据.第二个参数标识多少个这样的value表示一块单独的数据.这个例子中是4.第五个参数标识这一系列数据之间的空隙,这个例子中没有空隙,所以是0.第六个参数标识buffer object的起始位置是什么,这里从头开始,为0.
The fourth parameter is something that we will look at in later tutorials. The first parameter is something we will look at in the next section.
第四个参数在以后的文章会降到,第一个参数下一个部分会讲到.
One thing that appears absent is specifying which buffer object this data comes from. This is an implicit association rather than an explicit one.
glVertexAttribPointer
always refers to whatever buffer is bound toGL_ARRAY_BUFFER
at the time that this function is called. Therefore it does not take a buffer object handle; it simply uses the handle we bound previously.This function will be looked at in greater detail in later tutorials.
看起来我们没有讲到这个buffer object数据从哪里来.这是一个隐含的关联,而非明确的.glVertexAttribPointer总是与函数调用开始时与
GL_ARRAY_BUFFER绑定的buffer关联.因此我们不需要一个buffer object的handle,它简单的使用我们前面绑定的buffer object 的 handle.
Once OpenGL knows where to get its vertex data from, it can now use that vertex data to render.
glDrawArrays(GL_TRIANGLES, 0, 3);
This function seems very simple on the surface, but it does a great deal. The second and third parameters represent the start index and the number of indices to read from our vertex data. The 0th index of the vertex array (defined with
glVertexAttribPointer
) will be processed, followed by the 1st and 2nd indices. That is, it starts with the 0th index, and reads 3 vertices from the arrays.第二个和第三个参数代表起始索引和从顶点数据中读取的索引个数.glVertexAttribPointer定义的顶点数组的第0个将会被处理,后面跟着第1和第2个.也就是说,从0开始,从数组中读取3个索引.
The first parameter to
glDrawArrays
tells OpenGL that it is to take every 3 vertices that it gets as an independent triangle. Thus, it will read just 3 vertices and connect them to form a triangle.第一个参数告诉OpenGL取3个顶点当做一个单独的三角形.然后读取三个顶点形成一个三角形.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
void glDrawArrays(int mode, int first,int count)参数1:有三种取值
1.GL_TRIANGLES:每三个顶之间绘制三角形,之间不连接
2.GL_TRIANGLE_FAN:以V0V1V2,V0V2V3,V0V3V4,……的形式绘制三角形
3.GL_TRIANGLE_STRIP:顺序在每三个顶点之间均绘制三角形。这个方法可以保证从相同的方向上所有三角形均被绘制。以V0V1V2,V1V2V3,V2V3V4……的形式绘制三角形
参数2:从数组缓存中的哪一位开始绘制,一般都定义为0
参数3:顶点的数量
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Again, we will go into details in another tutorial.
Now that we can tell OpenGL what the vertex data is, we come to the next stage of the pipeline: vertex processing. This is one of two programmable stages that we will cover in this tutorial, so this involves the use of a shader.
现在我们可以告诉OpenGL顶点数据是什么,我们将会开始管线的下一个阶段:vertex processing.这是两个可编程阶段中的一个,下面介绍进入shader.
A shader is nothing more than a program that runs on the GPU. There are several possible shader stages in the pipeline, and each has its own inputs and outputs. The purpose of a shader is to take its inputs, as well as potentially various other data, and convert them into a set of outputs.
shader只不过是一个运行在GPU上的program.在管线中有许多possible shader 阶段,每个都有自己的输入输出.shader的目的就是接受输入或者其他各种数据,然后将他们转换为一系列outputs.
Each shader is executed over a set of inputs. It is important to note that a shader, of any stage, operates completely independently of any other shader of that stage. There can be no crosstalk between separate executions of a shader. Execution for each set of inputs starts from the beginning of the shader and continues to the end. A shader defines what its inputs and outputs are, and it is illegal for a shader to complete without writing to all of its outputs (in most cases).
每个shader都执行在一系列的输入之上.任何阶段中的一个shader的operates都与那个阶段中的其他shader的operates相独立. There can be no crosstalk between separate executions of a shader. 一个系列的输入值的执行过程从shder的开始处开始,执行到结束.一个shader定义了输入输出是什么,大多数情况下,一个shader完成后没有对它的任何output进行写入是非法的.
Vertex shaders, as the name implies, operate on vertices. Specifically, each invocation of a vertex shader operates on a single vertex. These shaders must output, among any other user-defined outputs, a clip-space position for that vertex. How this clip-space position is computed is entirely up to the shader.
顶点shader,像它的名称所表示的,作用于顶点上.特别的,一个顶点shader的每个 invocation操作作用于一个单独的顶点.这些shaders必须有输出:用户定义的输出,或者该顶点在clip-space 中的位置.这个clip-space的位置怎么样计算完全由shader决定.
Shaders in OpenGL are written in the OpenGL Shading Language (GLSL). This language looks suspiciously like C, but it is very much not C. It has far too many limitations to be C (for example, recursion is forbidden). This is what our simple vertex shader looks like:
Example 1.3. Vertex Shader
#version 330 layout(location = 0) in vec4 position; void main() { gl_Position = position; }
This looks fairly simple. The first line states that the version of GLSL used by this shader is version 3.30. A version declaration is required for all GLSL shaders.
The next line defines an input to the vertex shader. The input is a variable named position
and is of type vec4: a 4-dimensional vector of floating-point values. It also has a layout location of 0; we'll explain that a little later.
第二行命令定义了顶点shader的一个输入.输入变量命名为position
,是一个vec4型数据.它同样有个 layout location 0,待会讲到.
As with C, a shader's execution starts with the main
function. This shader is very simple, copying the input position
into something calledgl_Position
. This is a variable that is not defined in the shader; that is because it is a standard variable defined in every vertex shader. If you see an identifier in a GLSL shader that starts with “gl_”, then it must be a built-in identifier. You cannot make an identifier that begins with“gl_”; you can only use ones that already exist.
像C一样,shader的执行从main()函数开始.这个shader非常简单,将数据从输入变量position拷贝到被称作
看到一个标示符以“gl_”开头,那么它肯定是一个内置标示符.gl_Position
的变量.是个变量不是不是在shader中定义的,它是被定义在每个顶点shader中的标准变量.如果你在GLSL shader中
gl_Position
is defined as:
out vec4 gl_Position;
Recall that the minimum a vertex shader must do is generate a clip-space position for the vertex. That is what gl_Position
is: the clip-space position of the vertex. Since our input position data is already a clip-space position, this shader simply copies it directly into the output.
顶点shader必须做的是为每个顶点产生一个clip-space位置.那也是 gl_Position
的含义所在:顶点在 clip-space 的位置.因为我们的输入数据已经是一个 clip-space 的位置,这个例子中只要简单把它拷贝到output.
Vertex Attributes. Shaders have inputs and outputs. Think of these like function parameters and function return values. If the shader is a function, then it is called with input values, and it is expected to return a number of output values.
Vertex Attributes. Shaders 有输入输出.把这个看做是函数参数和函数的返回值.如果shader是一个函数,那么就是与输入值一起开始调用,期望返回一系列output值.
Inputs to and outputs from a shader stage come from somewhere and go to somewhere. Thus, the input position
in the vertex shader must be filled in with data somewhere. So where does that data come from? Inputs to a vertex shader are called vertex attributes.
shader的输入和输出有来源地与目的地.这样顶点shader中的输入变量position
必须在某地被装入某些数据.问题是数据从哪里来?输入到顶点shader的我们称之为 vertex attributes.
You might recognize something similar to the term “vertex attribute.” For example, “glEnableVertexAttribArray” or “glVertexAttribPointer.”
This is how data flows down the pipeline in OpenGL. When rendering starts, vertex data in a buffer object is read based on setup work done byglVertexAttribPointer
. This function describes where the data for an attribute comes from. The connection between a particular call toglVertexAttribPointer
and the string name of an input value to a vertex shader is somewhat complicated.
这就是数据如何在管线中流动的.当rendering开始,buffer object中读入顶点数据取决于glVertexAttribPointer所做的工作.这个函数被描述了一个 attribute 的数据从哪里来. glVertexAttribPointer函数调用和输入到顶点shader中的值的名称的关系有些复杂.
Each input to a vertex shader has an index location called an attribute index. The input in this shader was defined with this statement:
顶点shader中的每个输入都有一个索引位置,称为attribute index. 这个例子中我们使用如下语句:
layout(location = 0) in vec4 position;
The layout location part assigns the attribute index of 0 to position
. Attribute indices must be greater than or equal to zero, and there is a hardware-based limit on the number of attribute indices that can be in use at any one time[2].
location=0语句将 attribute 索引0赋给 position
.attribute 索引必须大于等于0,而且一次使用的attribute index的个数有硬件上的限制.
In code, when referring to attributes, they are always referred to by attribute index. The functions glEnableVertexAttribArray
,glDisableVertexAttribArray
, and glVertexAttribPointer
all take as their first parameter an attribute index. We assigned the attribute index of the position
attribute to 0 in the vertex shader, so the call to glEnableVertexAttribArray(0)
enables the attribute index for the position
attribute.
代码中,attribute index全被允许使用. glEnableVertexAttribArray
,glDisableVertexAttribArray
, glVertexAttribPointer三个函数的第一个参数均为attribute index.在顶点shader中我们将
position
attribute的索引赋为0,这样glEnableVertexAttribArray(0)函数开启了position
attribute的attribute index .
Here is a diagram of the data flow to the vertex shader:
Without the call to glEnableVertexAttribArray
, calling glVertexAttribPointer
on that attribute index would not mean much. The enable call does not have to be called before the vertex attribute pointer call, but it does need to be called before rendering. If the attribute is not enabled, it will not be used during rendering.
如果没有调用 glEnableVertexAttribArray,而调用glVertexAttribPointer并没有什么意义.enable调用不必在
glVertexAttribPointer
之前调用,但是需要在rendering之前调用.否则无法使用.
All that has happened thus far is that 3 vertices have been given to OpenGL and it has transformed them with a vertex shader into 3 positions in clip-space. Next, the vertex positions are transformed into normalized-device coordinates by dividing the 3 XYZ components of the position by the W component. In our case, W for our 3 positions was 1.0, so the positions are already effectively in normalized-device coordinate space.
目前为止3个顶点已经被转换为3个 clip-space空间的位置并传送给了OpenGL.下一步就是将顶点位置进行单位化.这个例子中3个W都是1.0,这样positions已经被转换到了单位化坐标系中.
After this, the vertex positions are transformed into window coordinates. This is done with something called the viewport transform. This is so named because of the function used to set it up, glViewport
. The tutorial calls this function every time the window's size changes.
在这之后,顶点positions被转换到窗口坐标.这是通过 viewport transform转换来进行的.这函数称为glViewport.每当窗口大小改变,就会调用这个函数.
Remember that the framework calls reshape
whenever the window's size changes. So the tutorial's implementation of reshape is this:
Example 1.4. Reshaping Window
void reshape (int w, int h) { glViewport(0, 0, (GLsizei) w, (GLsizei) h); }
This tells OpenGL what area of the available area we are rendering to. In this case, we change it to match the full available area. Without this function call, resizing the window would have no effect on the rendering. Also, make note of the fact that we make no effort to keep the aspect ratio constant; shrinking or stretching the window in a direction will cause the triangle to shrink and stretch to match.
没有这个函数调用,窗口大小的改变对于rendering不会有任何影响.
Recall that window coordinates are in a lower-left coordinate system. So the point (0, 0) is the bottom left of the window. This function takes the bottom left position as the first two coordinates, and the width and height of the viewport rectangle as the other two coordinates.
点(0,0)是窗口的左下角.这个函数将左下当做前两个参数,长和宽当做后两个参数.
Once in window coordinates, OpenGL can now take these 3 vertices and scan-convert it into a series of fragments. In order to do this however, OpenGL must decide what the list of vertices represents.
OpenGL can interpret a list of vertices in a variety of different ways. The way OpenGL interprets vertex lists is given by the draw command:
glDrawArrays(GL_TRIANGLES, 0, 3);
The enum GL_TRIANGLES
tells OpenGL that every 3 vertices of the list should be used to build a triangle. Since we passed only 3 vertices, we get 1 triangle.
If we rendered 6 vertices, then we would get 2 triangles.
A fragment shader is used to compute the output color(s) of a fragment. The inputs of a fragment shader include the window-space XYZ position of the fragment. It can also include user-defined data, but we will get to that in later tutorials.
fragment shader是用来计算fragment的输出颜色值的.fragment shader的输入包含了fragment在窗口空间的XYZ位置.也可以包含用户定义的数据,接下来会讲到.
Our fragment shader looks like this:
Example 1.5. Fragment Shader
#version 330 out vec4 outputColor; void main() { outputColor = vec4(1.0f, 1.0f, 1.0f, 1.0f); }
As with the vertex shader, the first line states that the shader uses GLSL version 3.30.
The next line specifies an output for the fragment shader. The output variable is of type vec4.
The main function simply sets the output color to a 4-dimensional vector, with all of the components as 1.0f. This sets the Red, Green, and Blue components of the color to full intensity, which is 1.0; this creates the white color of the triangle. The fourth component is something we will see in later tutorials.
Though all fragment shaders are provided the window-space position of the fragment, this one does not need it. So it simply does not use it.
After the fragment shader executes, the fragment output color is written to the output image.
虽然所有的fragment shaders都被提供了fragment在窗口空间的位置,但这个不需要它.So it simply does not use it.在所有的fragment shader执行之后,fragment 输出颜色会被写入到output图像中.
Note
In the section on vertex shaders, we had to use the layout(location = #)
syntax in order to provide a connection between a vertex shader input and a vertex attribute index. This was required in order for the user to connect a vertex array to a vertex shader input. So you may be wondering where the connection between the fragment shader output and the screen comes in.
OpenGL recognizes that, in a lot of rendering, there is only one logical place for a fragment shader output to go: the current image being rendered to (in our case, the screen). Because of that, if you define only one output from a fragment shader, then this output value will automatically be written to the current destination image. It is possible to have multiple fragment shader outputs that go to multiple different destination images; this adds some complexity, similar to attribute indices. But that is for another time.
Note
在前面顶点shader部分,我们使用layout语法是为了在顶点shader的输入和顶点attribute index之间建立联系.这需要有序的在顶点数组和顶点shader输入之间建立联系.也许你会奇怪fragment shader和屏幕输入之间的联系在哪里.
OpenGL认为,在许多的rendering之间,只有一个为fragment shader 输出准备的逻辑空间:当前正在被rendered的图像(在我们的例子中是屏幕).因为这样如果在一个fragment shader中定义一个output,接下来这个output值将会自动被写入到当前目的图像.许多复杂的output输出到不同的图像这种情况也是存在的;这增加了一些复杂性,similar to attribute indices.以后会讲到.