周一到周五,每天一篇,北京时间早上7点准时更新~,中英文对照,一边学编程一边弹吉他,做一个奇葩码农!
After the front end of the pipeline has run (which includes vertex shading, tessellation, and geometry shading)(当前面提到的那些渲染管线阶段执行完毕后), a fixed-function part of the pipeline performs a series of tasks that take the vertex representation of our scene and convert it into a series of pixels(渲染管线固定管线的部分会通过一系列手段将那些空间中的点变成像素), which in turn need to be colored and written to the screen(这些像素随后将被着色并刷新到屏幕上去). The first step in this process is primitive assembly(第一步是生成图元像素), which is the grouping of vertices into lines and triangles(也就是要把点们变成个线和三角形). Primitive assembly still occurs for points, but it is trivial in that case(生成图元像素的步骤也可以生成点的像素,但是生成点不是很重要)
Once primitives have been constructed from their individual vertices, they are clipped against the displayable region, which usually means the window or screen, but can also be a smaller area known as the viewport(当从一些独立的点去构建图元的时候,他们将会被两个东西裁剪,第一个是窗口第二个是视口). Finally, the parts of the primitive that are determined to be potentially visible are sent to a fixed-function subsystem called the rasterizer(最终那些被认为可见的像素被传给下一个阶段:光栅化). This block determines which pixels are covered by the primitive (point, line,or triangle) and sends the list of pixels on to the next stage—that is, fragment shading (这个模块会判断哪些像素区域被图元所覆盖然后把这些像素的列表发送给下一个阶段:fragment shader)
Clipping(剪裁)
As vertices exit the front end of the pipeline, their position is said to be in clip space.(当点被前面所有阶段处理完毕后,他们处于剪裁空间中) This is one of the many coordinate systems that can be used to represent positions(这就是一个普普通通的可以表达位置的坐标系). You may have noticed that the gl_Position variable that we have written to in our vertex, tessellation, and geometry shaders has a vec4 type(你可能注意到了我们在vertex tessellation和geometry shader中用到的gl_Position是一个vec4的类型的), and that the positions we have produced by writing to it are all four-component vectors(并且我们为它赋值的时候也全部都是四维向量). This is what is known as a homogeneous coordinate(这就是人们所知道的齐次坐标). The homogeneous coordinate system is used in projective geometry because much of the math ends up being simpler in homogeneous coordinate space than it does in regular Cartesian space(奇次坐标系统被用于投影几何,因为很多在笛卡尔坐标系里的数学在齐次坐标系里更简单). Homogeneous coordinates have one more component than their equivalent Cartesian coordinate(齐次坐标比他们对应的笛卡尔坐标多一个元素), which is why our threedimensional position vector is represented as a four-component variable(这就是为什么我们的三维坐标被表达为四维向量)
Although the output of the front end is a four-component homogeneous coordinate, clipping occurs in Cartesian space(尽管我们前面的这些阶段输出的全部是齐次坐标系的坐标,但是剪裁发生在笛卡尔坐标系下). Thus, to convert from homogeneous coordinates to Cartesian coordinates, OpenGL performs a perspective division, which involves dividing all four components of the position by the last, w component(也就是说,为了将齐次坐标转化成笛卡尔坐标,OpenGL的固定处理部分会做这个转化). This has the effect of projecting the vertex from the homogeneous space to the Cartesian space, leaving w as 1.0(这样就把所有的齐次坐标转化成了笛卡尔坐标,最终w部分变成了1). In all of the examples so far, we have set the w component of gl_Position as 1.0, so this division has not had any effect(在所有的例子中,我们都给w部分写的是1.0,所以实际上到目前为止,这个转化其实对数据没什么影响). When we explore projective geometry in a short while, we will discuss the effect of setting w to values other than 1.0(稍后我们来讨论w部分不是1.0的情况)
After the projective division, the resulting position is in normalized device space(在齐次坐标转化为笛卡尔坐标后,我们得到了在NDC坐标系下的位置). In OpenGL, the visible region of normalized device space is the volume that extends from −1.0 to 1.0 in the x and y dimensions and from 0.0 to 1.0 in the z dimension(在OpenGL中,NDC坐标系下的可见空间为x、y从-1.0~1.0),z从0~1.0. Any geometry that is contained in this region may become visible to the user and anything outside of it should be discarded(任何在这个空间里的坐标都会被看见,不在这个空间的坐标则不会被看见). The six sides of this volume are formed by planes in three-dimensional space(这个剪裁用的小盒子在三维空间中被6个面包围). As a plane divides a coordinate space in two, the volumes on each side of the plane are called half-spaces(一个平面可以把空间切成两半,这个剪裁的小盒子的平面的每一边都被称为半空间)
Before passing primitives on to the next stage(在将图元传递给下一个阶段之前), OpenGL performs clipping by determining which side of each of these planes the vertices of each primitive lie on(OpenGL通过点在裁剪小盒子的面的哪一侧来判断什么点将可以被传送给下一阶段). Each plane effectively has an “outside” and an “inside.” If a primitive’s vertices all lie on the “outside” of any one plane, then the whole thing is thrown away(如果图元整个都在剪裁小盒子的外面,那么整个图元将被剪裁掉). If all of primitive’s vertices are on the “inside” of all the planes (and therefore inside the view volume), then it is passed through unaltered(如果整个图元都在剪裁小盒子的里面,整个图元将原封不动的传送给下一个阶段). Primitives that are partially visible (which means that they cross one of the planes) must be handled specially(如果图元是部分在小盒子里,部分在小盒子外,那么图元将被特殊处理后,发送给下一阶段). More details about how this works is given in the “Clipping” section in Chapter 7(更多关于剪裁的知识,请看第七章)
Viewport Transformation(视口变换)
After clipping, all of the vertices of the geometry have coordinates that lie between −1.0 and 1.0 in the x and y dimensions(在剪裁完成之后,所有的几何点的坐标都被转换到了-1到1之间). Along with a z coordinate that lies between 0.0 and 1.0, these are known as normalized device coordinates(而z坐标的范围则是0~1,这个坐标被称为NDC坐标). However, the window that you’re drawing to has coordinates that usually start from (0, 0) at the bottom left and range to (w − 1,h − 1)(然而,你所知道的窗口坐标通常是从(0,0)到(w-1,h-1)的坐标范围的,其中w,h是指窗口的像素的宽和高), where w and h are the width and height of the window in pixels, respectively. To place your geometry into the window, OpenGL applies the viewport transform, which applies a scale and offset to the vertices’ normalized device coordinates to move them into window coordinates(为了让你的几何点放到窗口中去,OpenGL使用视口变换,将通过缩放和偏移将NDC坐标变成窗口坐标). The scale and bias to apply are determined by the viewport bounds, which you can set by calling glViewport() and glDepthRange()(如何缩放和偏移则可以通过视口的范围来控制,你可以通过glViewport和glDepthRange去控制,他们的函数申明如下:). Their prototypes are
void glViewport(GLint x, GLint y, GLsizei width, GLsizei height);
and(和)
void glDepthRange(GLdouble nearVal, GLdouble farVal);
This transform takes the following form:(这个变换使用下面的数学公式)
Here, xw, yw, and zw are the resulting coordinates of the vertex in window space(这里xw,yw,zw是输出的窗口坐标系下的坐标), and xd, yd, and zd are the incoming coordinates of the vertex in normalized device space(xd,yd,zd是输入的NDC坐标). px and py are the width and height of the viewport in pixels, and n and f are the near and far plane distances in the z coordinate, respectively(px,py是视口坐标,单位是像素,n,f是近剪裁平面和远剪裁平面的z坐标). Finally, ox, oy, and oz are the origins of the viewport(最后,ox,oy,oz是视口的原点)
Culling(剔除)
Before a triangle is processed further(在进行更多的处理之前), it may be optionally passed through a stage called culling(它还要进过一道叫做剔除工作的处理), which determines whether the triangle faces toward or away from the viewer and can decide whether to actually go ahead and draw it based on the result of this computation(这个工序主要是通过当前正在被绘制的图元是否面向摄像机而决定要不要画它). If the triangle faces toward the viewer, then it is considered to be frontfacing(如果这个三角形面向摄像机,那么它会被认为是前面); otherwise, it is said to be back-facing(否则这个面被认为是背面). It is very common to discard triangles that are back-facing because when an object is closed, any back-facing triangle will be hidden by another front-facing triangle(当某个面被认为是背面的时候,通常我们会不去绘制这个面,因为它会被它的前面所挡住)
To determine whether a triangle is front- or back-facing, OpenGL will determine its signed area in window space(为了判断是前面还是背面,OpenGL判断它在窗口空间中面积的符号来确定). One way to determine the area of a triangle is to take the cross product of two of its edges(一个方法是将三角形的两条边叉乘). The equation for this is(公式如下)
Here, and are the coordinates of the ith vertex of the triangle in window space and i ⊕ 1 is (i +1) mod 3(这里是对变量的一些说明,但是由于网页无法展示这些复杂的公式,请参考原书). If the area is positive, then the triangle is considered to be front-facing(如果这个面积是正的,那么就是正面); if it is negative, then it is considered to be back-facing(如果是负的,那么这会被认为是负面). The sense of this computation can be reversed by calling glFrontFace() with dir set to either GL_CW or GL_CCW (这个计算公式可以通过glFrontFace来进行设置,设置不一样运算公式中的叉乘顺序可能需要反着来) (where CW and CCW stand for clockwise and counterclockwise,respectively). This is known as the winding order of the triangle, and the clockwise or counterclockwise terms refer to the order in which the vertices appear in window space(这个技能被称为三角形的组织顺序,顺时针或者是逆时针决定了三角形的点的连接顺序). By default, this state is set to GL_CCW, indicating that triangles whose vertices are in counterclockwise order are considered to be front-facing and those whose vertices are in clockwise order are considered to be back-facing(默认情况下,OpenGL中是逆时针顺序,也就是说,那些逆时针组织的顶点组成的面是三角形的正面). If the state is GL_CW,then a is simply negated before being used in the culling process(如果是顺时针的情况,剔除的公式反过来就好了,如图3.3所示). Figure 3.3 shows this pictorially for the purpose of illustration
Once the direction that the triangle is facing has been determined, OpenGL is capable of discarding either front-facing, back-facing, or even both types of triangles(当三角形的正面和反面被确定下来后,OpenGL就可以有条件做剔除了). By default, OpenGL will render all triangles, regardless of which way they face(默认情况下,OpenGL会渲染所有三角形,无论是前面还是后面). To turn on culling, call glEnable() with cap set to GL_CULL_FACE(为了开启剔除,你需要调用glEnable,传入GL_CULL_FACE). When you enable culling, OpenGL will cull back-facing triangles by default(当你开启了剔除的时候,OpenGL默认会剔除背面). To change which types of triangles are culled, call glCullFace() with face set to GL_FRONT, GL_BACK, or GL_FRONT_AND_BACK.(你也可以通过glCullFace去设置将被剔除的面) As points and lines don’t have any geometric area,2 this facing calculation doesn’t apply to them and they can’t be culled at this stage(由于点和线无法计算面积,所以剔除这个过程不会被用于点和线上).
Rasterization(光栅化)
Rasterization is the process of determining which fragments might be covered by a primitive such as a line or a triangle(光栅化是确定哪个像素会被什么图元覆盖的问题). There are myriad algorithms for doing this, but most OpenGL systems will settle on a half-space–based method for triangles(有很多算法可以做这件事,但是大部分OpenGL会使用half-space–based), as it lends itself well to parallel implementation(因为这个算法可以很好的并行处理图元). Essentially, OpenGL will determine a bounding box for the triangle in window coordinates and test every fragment inside it to determine whether it is inside or outside the triangle(基本上,OpenGL会使用一个包围盒来计算窗口坐标系里的三角形是否在这个包围盒里面). To do this, it treats each of the triangle’s three edges as a half-space that divides the window in two(为了实现这一点,它会将三角形的三条边都视为将窗口切成两个的半空间)
Fragments that lie on the interior of all three edges are considered to be inside the triangle and fragments that lie on the exterior of any of the three edges are considered to be outside the triangle(那些落在内部的像素被认为在三角形里面,在外部的像素则在三角形外面). Because the algorithm to determine which side of a line a point lies on is relatively simple and is independent of anything besides the position of the line’s endpoints and of the point being tested, many tests can be performed concurrently, providing the opportunity for massive parallelism(判断线和点的情况相对简单,很多种手段都可以并发运行)
本日的翻译就到这里,明天见,拜拜~~
第一时间获取最新桥段,请关注东汉书院以及图形之心公众号
东汉书院,等你来玩哦