where u ≥ 0, v ≥ 0 and u + v ≤ 1. Given an attribute (f0, f1, f2) on the vertices of the triangle, we can use u, v to interpolate it over the interior:f(u,v) = f0 + (f1 - f0)u + (f2 - f0)v所有数学运算都可以使用上述参数化来完成,实际上,由于计算速度更快,因此有时更可取.但是,它不太方便,并且存在数字问题(例如P(1,0)可能不等于P1).All math can be done using the above parametrization, and in fact is sometimes preferable due to faster calculations. However it is less convenient and has numerical issues (e.g. P(1,0) might not equal P1).通常使用重心坐标.三角形内的每个点都是顶点的加权和:Instead barycentric coordinates are usually used. Every point inside the triangle is a weighted sum of the vertices:P(b0,b1,b2) = P0*b0 + P1*b1 + P2*b2f(b0,b1,b2) = f0*b0 + f1*b1 + f2*b2其中b0 + b1 + b2 = 1,b0≥0,b1≥0,b2≥0是三角形中点的重心坐标.每个bi都可以认为是必须混入多少Pi".所以b =(1,0,0),(0,1,0)和(0,0,1)是三角形的顶点,(1/3,1/3,1/3)是重心,等等.where b0 + b1 + b2 = 1, b0 ≥ 0, b1 ≥ 0, b2 ≥ 0 are the barycentric coordinates of the point in the triangle. Each bi can be thought of as 'how much of Pi has to be mixed in'. So b = (1,0,0), (0,1,0) and (0,0,1) are the vertices of the triangle, (1/3, 1/3, 1/3) is the barycenter, and so on.因此,假设我们在屏幕上填充了一个投影的2D三角形.对于每个片段,我们都有其窗口坐标.首先,我们通过反转P(b0,b1,b2)函数(它是窗口坐标中的线性函数)来计算其重心坐标.这为我们提供了 2D三角形投影上片段的重心坐标.So let's say we fill a projected 2D triangle on the screen. For every fragment we have its window coordinates. First we calculate its barycentric coordinates by inverting the P(b0,b1,b2) function, which is a linear function in window coordinates. This gives us the barycentric coordinates of the fragment on the 2D triangle projection.属性的正确正确插值将在剪辑坐标中(以及世界坐标)线性变化.为此,我们需要获取片段在片段空间中的重心坐标.Perspective correct interpolation of an attribute would vary linearly in the clip coordinates (and by extension, world coordinates). For that we need to get the barycentric coordinates of the fragment in clip space.发生时(请参见 [1] 和 [2] ),片段在窗口坐标中不是线性的,但逆深度(1/gl_Position.w)是线性的.因此,属性和剪辑空间重心坐标在被深度反比加权时,在窗口坐标中呈线性变化.As it happens (see [1] and [2]), the depth of the fragment is not linear in window coordinates, but the depth inverse (1/gl_Position.w) is. Accordingly the attributes and the clip-space barycentric coordinates, when weighted by the depth inverse, vary linearly in window coordinates.因此,我们通过以下方式计算透视校正的重心:Therefore, we compute the perspective corrected barycentric by: ( b0 / gl_Position[0].w, b1 / gl_Position[1].w, b2 / gl_Position[2].w )B = ------------------------------------------------------------------------- b0 / gl_Position[0].w + b1 / gl_Position[1].w + b2 / gl_Position[2].w然后使用它从顶点内插属性.and then use it to interpolate the attributes from the vertices. 注意:: GL_NV_fragment_shader_barycentric 通过gl_BaryCoordNoPerspNV公开设备线性重心坐标,并通过gl_BaryCoordNV校正透视图.Note: GL_NV_fragment_shader_barycentric exposes the device-linear barycentric coordinates through gl_BaryCoordNoPerspNV and the perspective corrected through gl_BaryCoordNV.这里是C ++代码,以类似于OpenGL的方式光栅化和着色CPU上的三角形.我鼓励您将其与下面列出的着色器进行比较:Here is a C++ code that rasterizes and shades a triangle on the CPU, in a manner similar to OpenGL. I encourage you to compare it with the shaders listed below:struct Renderbuffer { int w, h, ys; void *data;};struct Vert { vec4f position; vec4f texcoord; vec4f color;};struct Varying { vec4f texcoord; vec4f color;};void vertex_shader(const Vert &in, vec4f &gl_Position, Varying &out){ out.texcoord = in.texcoord; out.color = in.color; gl_Position = { in.position[0], in.position[1], -2*in.position[2] - 2*in.position[3], -in.position[2] };}void fragment_shader(vec4f &gl_FragCoord, const Varying &in, vec4f &out){ out = in.color; vec2f wrapped = vec2f(in.texcoord - floor(in.texcoord)); bool brighter = (wrapped[0] < 0.5) != (wrapped[1] < 0.5); if(!brighter) (vec3f&)out = 0.5f*(vec3f&)out;}void store_color(Renderbuffer &buf, int x, int y, const vec4f &c){ // can do alpha composition here uint8_t *p = (uint8_t*)buf.data + buf.ys*(buf.h - y - 1) + 4*x; p[0] = linear_to_srgb8(c[0]); p[1] = linear_to_srgb8(c[1]); p[2] = linear_to_srgb8(c[2]); p[3] = lrint(c[3]*255);}void draw_triangle(Renderbuffer &color_attachment, const box2f &viewport, const Vert *verts){ Varying perVertex[3]; vec4f gl_Position[3]; box2f aabbf = { viewport.hi, viewport.lo }; for(int i = 0; i < 3; ++i) { // invoke the vertex shader vertex_shader(verts[i], gl_Position[i], perVertex[i]); // convert to device coordinates by perspective division gl_Position[i][3] = 1/gl_Position[i][3]; gl_Position[i][0] *= gl_Position[i][3]; gl_Position[i][1] *= gl_Position[i][3]; gl_Position[i][2] *= gl_Position[i][3]; // convert to window coordinates auto &pos2 = (vec2f&)gl_Position[i]; pos2 = mix(viewport.lo, viewport.hi, 0.5f*(pos2 + vec2f(1))); aabbf = join(aabbf, (const vec2f&)gl_Position[i]); } // precompute the affine transform from fragment coordinates to barycentric coordinates const float denom = 1/((gl_Position[0][0] - gl_Position[2][0])*(gl_Position[1][1] - gl_Position[0][1]) - (gl_Position[0][0] - gl_Position[1][0])*(gl_Position[2][1] - gl_Position[0][1])); const vec3f barycentric_d0 = denom*vec3f( gl_Position[1][1] - gl_Position[2][1], gl_Position[2][1] - gl_Position[0][1], gl_Position[0][1] - gl_Position[1][1] ); const vec3f barycentric_d1 = denom*vec3f( gl_Position[2][0] - gl_Position[1][0], gl_Position[0][0] - gl_Position[2][0], gl_Position[1][0] - gl_Position[0][0] ); const vec3f barycentric_0 = denom*vec3f( gl_Position[1][0]*gl_Position[2][1] - gl_Position[2][0]*gl_Position[1][1], gl_Position[2][0]*gl_Position[0][1] - gl_Position[0][0]*gl_Position[2][1], gl_Position[0][0]*gl_Position[1][1] - gl_Position[1][0]*gl_Position[0][1] ); // loop over all pixels in the rectangle bounding the triangle const box2i aabb = lrint(aabbf); for(int y = aabb.lo[1]; y < aabb.hi[1]; ++y) for(int x = aabb.lo[0]; x < aabb.hi[0]; ++x) { vec4f gl_FragCoord; gl_FragCoord[0] = x + 0.5; gl_FragCoord[1] = y + 0.5; // fragment barycentric coordinates in window coordinates const vec3f barycentric = gl_FragCoord[0]*barycentric_d0 + gl_FragCoord[1]*barycentric_d1 + barycentric_0; // discard fragment outside the triangle. this doesn't handle edges correctly. if(barycentric[0] < 0 || barycentric[1] < 0 || barycentric[2] < 0) continue; // interpolate inverse depth linearly gl_FragCoord[2] = dot(barycentric, vec3f(gl_Position[0][2], gl_Position[1][2], gl_Position[2][2])); gl_FragCoord[3] = dot(barycentric, vec3f(gl_Position[0][3], gl_Position[1][3], gl_Position[2][3])); // clip fragments to the near/far planes (as if by GL_ZERO_TO_ONE) if(gl_FragCoord[2] < 0 || gl_FragCoord[2] > 1) continue; // convert to perspective correct (clip-space) barycentric const vec3f perspective = 1/gl_FragCoord[3]*barycentric*vec3f(gl_Position[0][3], gl_Position[1][3], gl_Position[2][3]); // interpolate the attributes using the perspective correct barycentric Varying varying; for(int i = 0; i < sizeof(Varying)/sizeof(float); ++i) ((float*)&varying)[i] = dot(perspective, vec3f( ((const float*)&perVertex[0])[i], ((const float*)&perVertex[1])[i], ((const float*)&perVertex[2])[i] )); // invoke the fragment shader and store the result vec4f color; fragment_shader(gl_FragCoord, varying, color); store_color(color_attachment, x, y, color); }}int main(){ Renderbuffer buffer = { 512, 512, 512*4 }; buffer.data = calloc(buffer.ys, buffer.h); // interleaved attributes buffer Vert verts[] = { { { -1, -1, -2, 1 }, { 0, 0, 0, 1 }, { 0, 0, 1, 1 } }, { { 1, -1, -1, 1 }, { 10, 0, 0, 1 }, { 1, 0, 0, 1 } }, { { 0, 1, -1, 1 }, { 0, 10, 0, 1 }, { 0, 1, 0, 1 } }, }; box2f viewport = { 0, 0, buffer.w, buffer.h }; draw_triangle(buffer, viewport, verts); stbi_write_png("out.png", buffer.w, buffer.h, 4, buffer.data, buffer.ys);} OpenGL着色器这是用于生成参考图像的OpenGL着色器.OpenGL shadersHere are the OpenGL shaders used to generate the reference image. 顶点着色器:#version 450 corelayout(location = 0) in vec4 position;layout(location = 1) in vec4 texcoord;layout(location = 2) in vec4 color;out gl_PerVertex { vec4 gl_Position;};layout(location = 0) out PerVertex { vec4 texcoord; vec4 color;} OUT;void main() { OUT.texcoord = texcoord; OUT.color = color; gl_Position = vec4(position[0], position[1], -2*position[2] - 2*position[3], -position[2]);} 片段着色器:#version 450 corelayout(location = 0) in PerVertex { vec4 texcoord; vec4 color;} IN;layout(location = 0) out vec4 OUT;void main() { OUT = IN.color; vec2 wrapped = fract(IN.texcoord.xy); bool brighter = (wrapped[0] < 0.5) != (wrapped[1] < 0.5); if(!brighter) OUT.rgb *= 0.5;}结果以下是C ++(左)和OpenGL(右)代码生成的几乎相同的图像:ResultsHere are the almost identical images generated by the C++ (left) and OpenGL (right) code:差异是由不同的精度和舍入模式引起的.The differences are caused by different precision and rounding modes.为进行比较,以下是一种不正确的透视图(在上面的代码中,使用barycentric而不是perspective进行插值):For comparison, here is one that is not perspective correct (uses barycentric instead of perspective for the interpolation in the code above): 这篇关于OpenGL如何精确地透视校正线性插值?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持! 上岸,阿里云! 08-29 12:29