Advanced Game Tech All

软光 – perspective correct interpolation and vertex attributes

Interpolating Vertex Attributes


All we need to get a basis rasterizer working is to know how to project triangles onto the screen, convert the projected coordinates to raster space, then rasterize triangles, and potentially use a depth-buffer to solve the visibility problem. That would already be enough to create images of 3D scenes, which would be both perspective correct, and in which the visibility problem would be solved (objects that are hidden by others, are indeed not appearing in front of objects that are supposed to occlude them). This is already a good result. The code we have presented to do this is functional, but could be optimized greatly; however, optimizing the rasterization algorithm is not something we will look into in this lesson.【到这一章之前,你都解决所有的创建图像的准备工作,代码已经可以work了,但是还有些优化工作可以做】


Perspective Correct Vertex Attribute Interpolation


So, what are we going to talk about in this chapter? In the chapter devoted to rasterization, we talked about using barycentric coordinates to interpolate vertex data or vertex attribute (which is the most common name). We can use barycentric coordinates to interpolate depth values of course, and this is one of their main function, but they can also be used to interpolate vertex attributes; vertex attributes play a very important role in rendering, especially when it comes to lighting and shading. We will provide more information on how vertex attributes are used in shading when we get to shading in this section. But you don’t need to know anything about shading to actually understand the concept of perspective correct interpolation and vertex attributes.【这一章节讲什么?首先讲使用重心坐标差值vertex数据和属性,属性对于渲染时的光照阴影处理很关键】


As you already know, we need to store the z-coordinates of the original vertices (camera space vertices) in the z-coordinate of our projected vertices (screen space vertices). This is required so that we can compute the depth of points lying across the surface of projected triangle. Depth, as explained in the last chapter, is required to solve the visibility problem and is computed by linearly interpolating the reciprocal of the triangle vertices z-coordinates using barycentric coordinates. Though the exact same technique can be used to interpolate any other variable we want as long as the value of this variable is defined at the triangle’s vertices, similarly to the way we’ve stored in the projected point z-coordinates, the vertices original z-coordinates. For example it is very common to store at the triangle vertices a color. The other two most common variables or attributes (which is the proper term in CG) stored at the triangle vertices are texture coordinates and normals. Texture coordinates are 2D coordinates used for texturing (a technique we will study in this section). Normals are used in shading and define the orientation of the surface (check the lessons on shading to learn more about normals and smooth shading in particular). In this lesson we will more specifically use color and texture coordinates to illustrate the problem of perspective correct interpolation.【然后我们还会讲颜色和纹理坐标的差值】


As mentioned in the chapter on the rasterization stage, we can specify colors or anything else we want for the triangle vertices. These attributes can be interpolated using barycentric coordinates to find what the value of these attribute should be for any point inside the triangle. In other words, vertex attributes must be interpolated across the surface of a triangle when it is rasterized. The process is as follows:【如果使用barycentric coordinates来差值顶点属性,步骤如下】


  • You can assign as many vertex attributes to the triangle’s vertices as you want. They are defined on the original 3D triangle (in camera space). In our example, we will assign two vertex attributes, one for color and one for texture coordinates.【收集camera space的三角形顶点的属性】
  • The triangle is projected onto the screen (the triangle’s vertices are converted from camera space to raster space).【三角形投影到屏幕】
  • While in screen space, the triangle is “rasterized”. If a pixel overlaps the triangle, the barycentric coordinates of that pixel are computed.【三角形在屏幕空间光栅化,计算barycentric coordinates
  • Colors (or texture coordinates) defined at the triangle corners or vertices are interpolated using the previously computed barycentric coordinates using the following formula:【使用计算好的barycentric coordinates对属性差值】

    Where λ0λ0, λ1λ1 and λ2λ2 are the pixel’s barycentric coordinates, and C0C0, C1C1 and C2C2 are the colors for the triangle vertices. The result CPCP is assigned to the current pixel in the frame-buffer. The same can be done to compute the texture coordinates of the point on the triangle that the pixel overlaps.【纹理也是使用barycentric coordinates方式差值】

    These coordinates are used for texturing (check the lesson on Texture Mapping in this section if you wish to learn about texture coordinates and texturing).



This technique though just doesn’t work. 【这个方法不可行】


To understand why let’s see what happens to a point located in the middle of a 3D quad. As you can see in the top view of figure 2, we clearly have a quad and point P, is clearly in the middle of that quad (P is located at the intersection of the quad’s diagonals). Though, when we look at this good from a random viewpoint it is easy to see that depending on the quad’s orientation with respect to the camera, P doesn’t appear to be in the centre of the quad anymore. This is due to perspective projection which as mentioned before, preserves lines but doesn’t preserve distances. Though remember that barycentric coordinates are computed in screen space. 【例如我们有一个3D立方体,P是立方体的中心点,但是因为perspective projection我们看到的P不在屏幕上立方体中心位置,尽管barycentric coordinates下P是在中心位置】


Imagine that the quad is made of two triangles. In 3D space, P is located at equal distance between V1-V2 thus somehow it’s barycentric coordinates in 3D space are (0,0.5,0.5). Though, in screen space, since P is closer to V1 than it is to V2, then λ1λ1 is greater than λ2λ2 (and \lambda_0 is equal to 0). The problem though is that these are the coordinates that are used to interpolate the triangle’s vertex attributes. If V1 is white and V2 is black then the color at P should be 0.5. But if λ1λ1 is greater than λ2λ2 then we will get a value greater than 0.5. Thus clearly the technique we are using to interpolate vertex attributes doesn’t work. Let’s assume like in figure 1 that λ1λ1 and λ2λ2 are equal to 0.666 and 0.334 respectively. If we interpolate the triangle’s vertex colors, we get:【数字化前面的例子,有下面的差值公式】



We get 0.666 for the color of P and not 0.5 as we should. There is a problem and this problem relates to some extent to what we learned in the previous chapter regarding the interpolation of the vertices z-coordinates.【得到的结果是不对的】




Hopefully finding the right solution is not hard. Let’s imagine that we have a triangle with two z-coordinates Z0Z0 and Z1Z1 on each side of the triangle as shown in figure 3. If we connect these two points we can interpolate the z-coordinate of a point on this line using linear interpolation. We can do the same thing with two values of a vertex attributes C0C0 and C1C1 defined at the same positions on the triangle than Z0Z0 and Z1Z1 respectively. Technically, because both ZZ and CC are computed using linear interpolation, we can write the following equality (equation 1):【找到正确答案不难,可以在投影线上面找到01两个点,会差值得到C,Z


We also know from the last chapter that (equation 2):【从上一章我们已知,Z的等式】

The first thing we are going to do is substitute the equation for Z (equation 2) in the left-hand side of equation 1. The trick to simplify the resulting equation is to multiply the numerator and denominator of equation 2 by Z0Z1Z0Z1 to get rid of the 1/Z01/Z0 and 1/Z11/Z1 terms (this is of course not explained anywhere but here on Scratchapixel):【合并上面两式子,推导如下,可以解出C】


If we now multiply the numerator and the denominator by 1/Z0Z11/Z0Z1, we can then extract a factor of ZZ from the right-hand side of the equation:【再化简】

It took a while to get to that result, but this a very fundamental equation in rasterization (which by the way is almost never explained anywhere. It is sometimes explained but the steps to get to that result are almost never provided) because it is used to interpolate vertex attributes which is a very important and common feature in rendering. 【得到上式表示的C值,这个用来处理顶点特征差值的等式是光栅化里面的基础公式之一。】


What the equation says is that to interpolate a vertex attribute correctly, we first need to divide by the vertex attribute value by the z-coordinate of the vertex it is defined to, then linearly interpolate them using q (which in our case, will be the barycentric coordinates of the pixel on the 2D triangle), and then finally multiply the result by ZZ, which is the depth of the point on the triangle, that the pixel overlaps (the depth of the point in camera space where the vertex attribute is being interpolated). Here is another version of the code we used in chapter three, that shows an example of perspective correct vertex attribute interpolation:【这个等式的意思是,我们首先要将顶点属性除以Z值,然后根据q做线性差值,最后在乘回这个点的z值就是最终的差值结果,下面是代码实现】



Computing the sample death requires to use the reciprocal of the vertices z-coordinates. For this reason, we can pre-compute these values before we loop over all pixels (line 52). If we decide to use perspective correct interpolation, then the vertex attribute values are divided by the z-coordinate of the vertex they are associated to (lines 48-50). 【我们会在pixel着一处理之前,计算Z值倒数】


The following image shows on the left, an image computed without perspective correct interpolation, an image with (middle) and the content of the z-buffer (as a greyscale image. The closer the object is to the screen, the brighter). The difference is subtle though you can see in the left image how each color seems to roughly fill up the same area. This is due to the fact that colors in this case are interpolated within the “space” of the 2D triangle (as if the triangle was a flat surface parallel to the plane of the screen). However if you inspect the triangle vertices (and the depth buffer), you will notice that the triangle is not at all parallel to the screen (but oriented with a certain angle). In fact, because the vertex “painted” in green is closer to the camera than the other two, this part of the triangle fills up a larger part of the screen which is visible in the middle image (the green area is larger than the blue or red area). The image in the middle shows the correct interpolation and what you will get if you render this triangle with a graphics API such as OpenGL or Direct3D.【结果的差别如下图的左边两幅对比,左图是没有采用正确的差值,中间是采用了正确的差值,右边是Z-buffer。左图中可以看出每种颜色填充区域差不多大,那是因为采用了2D差值。中间则明显绿色区域比较多,因为采用了3D差值】



The difference between correct and incorrect perspective interpolation is even more visible when applied to texturing. In the next example, we assigned texture coordinates to the triangle vertices as vertex attributes, and use these coordinates to create a checkerboard patter to the triangle. Rendering the triangle with or without perspective correct interpolation is left as an exercise. In the image below you can see the result with. As you can see, it also matches an image of the same triangle with the same pattern rendered in Maya. Hopefully, our code so far seems to do the right thing. As with color, all you need to do (and this is true of all vertex attributes) is to divide the texture coordinates (which are generally denoted ST coordinates) by the z-coordinate of the vertex they are associated to, and then later in the code, multiply the texture coordinate interpolated value by Z. Here are the changes we made to the code:【正确不正确的效果在纹理贴图上面效果更明显】