ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

图形学核心:重心坐标原理、透视校正插值与三角形光栅化实践

图形学核心:重心坐标原理、透视校正插值与三角形光栅化实践 1. 项目概述为什么我们需要重心坐标在图形学里我们经常要处理三角形。无论是渲染一个3D模型还是计算一个像素的颜色三角形都是最基本的图元。但问题来了给你一个三角形和三角形内部的一个点P你怎么精确地描述P的位置用屏幕的x, y坐标吗这显然不行因为三角形可能旋转、缩放、变形屏幕坐标是“外部”的无法描述点与三角形三个顶点的相对关系。这就是重心坐标要解决的核心问题。它提供了一种在三角形内部定义位置的“内部坐标系”。简单来说它用三个数字α, β, γ来表示点P这三个数字分别代表了三个顶点A, B, C对点P位置的“贡献”或“权重”。并且这三个权重之和永远为1α β γ 1。我第一次接触这个概念是在写一个软件光栅化器的时候。我需要判断一个像素中心是否在三角形内如果在还需要根据三个顶点的颜色或纹理坐标、法线等属性来插值出这个像素的颜色。如果不用重心坐标你几乎无法优雅且高效地完成这件事。你会陷入复杂的面积比计算或者解线性方程组的麻烦中。重心坐标就像一把瑞士军刀它把“点是否在三角形内”、“点的属性如何从顶点插值”这两个图形学中的高频问题统一到了一个简洁的数学框架下。所以无论你是刚入门图形编程的新手还是正在优化渲染管线的老手透彻理解重心坐标都是绕不开的一步。它不仅是基础更是连接几何、着色与光栅化各个环节的关键桥梁。接下来我们就从最根本的原理开始拆解这把“瑞士军刀”的每一个零件。1.1 核心需求解析从几何直觉到数学定义我们从一个最简单的几何问题开始已知三角形ABC以及其内部一点P。如何用A, B, C三个点来表示P最直观的想法是面积。连接P点和三个顶点会把大三角形ABC分割成三个小三角形△PBC, △PCA, △PAB。点P越靠近顶点A那么顶点A对面的小三角形△PBC的面积就越大。如果点P就在顶点A上那么△PBC的面积就等于整个大三角形ABC的面积而△PCA和△PAB的面积则为0。这个观察直接引出了重心坐标最经典的面积定义法。我们定义α 面积(△PBC) / 面积(△ABC)β 面积(△PCA) / 面积(△ABC)γ 面积(△PAB) / 面积(△ABC)显然α β γ (面积(△PBC)面积(△PCA)面积(△PAB)) / 面积(△ABC) 1。并且当P在三角形内部时α, β, γ 都是非负的因为面积非负。如果有一个坐标为负比如α 0那就意味着点P在BC边的“外侧”与顶点A位于BC边的异侧。因此重心坐标(α, β, γ)完美地描述了点P相对于三角形ABC的位置三个坐标都在[0, 1]区间内点P在三角形内部包括边和顶点。有一个坐标为负点P在三角形外部。坐标(1, 0, 0)对应顶点A(0, 1, 0)对应B(0, 0, 1)对应C。坐标(1/3, 1/3, 1/3)对应三角形的几何重心三条中线的交点。注意这里有一个非常重要的细节。我们通常说的“重心”在物理学和几何学里是同一个概念质心对于密度均匀的三角形它就是三条中线的交点其重心坐标正是(1/3, 1/3, 1/3)。这也是“重心坐标”这个名字的由来。但在图形学中我们更多地是利用它的“权重”和“插值”特性。从面积定义出发我们可以推导出更便于计算的解析形式。假设我们在二维平面上点A, B, C, P的坐标分别为(x_A, y_A), (x_B, y_B), (x_C, y_C), (x, y)。三角形面积可以用二维向量的叉积模长来计算叉积的模长等于两向量所围平行四边形面积的一半。以α为例α Area(PBC) / Area(ABC)。向量PB (x_B - x, y_B - y)向量PC (x_C - x, y_C - y)。在二维中叉积 PB × PC 的结果是一个标量实际上是z分量其值为 (x_B - x)(y_C - y) - (y_B - y)(x_C - x)。这个值的绝对值就是平行四边形PBPC的面积一半就是三角形PBC的面积。为了避免绝对值带来的符号判断麻烦我们可以使用有向面积。定义整个三角形ABC的有向面积为Area_ABC ( (x_B - x_A)*(y_C - y_A) - (y_B - y_A)*(x_C - x_A) ) / 2同理三角形PBC的有向面积为Area_PBC ( (x_B - x)*(y_C - y) - (y_B - y)*(x_C - x) ) / 2那么α Area_PBC / Area_ABC。神奇的是这样计算出来的α表达式恰好可以整理成关于x, y的一次线性函数。β和γ同理。最终我们可以得到重心坐标的计算公式α ((y_B - y_C)*(x - x_C) (x_C - x_B)*(y - y_C)) / ((y_B - y_C)*(x_A - x_C) (x_C - x_B)*(y_A - y_C)) β ((y_C - y_A)*(x - x_C) (x_A - x_C)*(y - y_C)) / ((y_B - y_C)*(x_A - x_C) (x_C - x_B)*(y_A - y_C)) γ 1 - α - β分母是三角形ABC有向面积的两倍是一个常数。分子分别是三角形PBC, PCA, PAB有向面积的两倍。这个公式是许多图形API和教科书中的标准形式它直接给出了通过点坐标计算重心坐标的方法无需先显式计算面积。2. 核心原理与数学推导理解了面积定义的直观意义后我们需要深入到数学层面看看重心坐标更一般化的定义和性质。这对于理解它在三维空间、甚至更高维空间以及在透视投影下的行为至关重要。2.1 从面积比到线性组合让我们换一个角度。既然点P可以用三个顶点来表示我们不妨假设存在一组权重(α, β, γ)使得P α * A β * B γ * C并且满足α β γ 1。这就是重心坐标的向量形式定义。它把点P表示为顶点A, B, C的仿射组合带权重和为1的线性组合。如果我们把A, B, C, P都看作位置向量这个方程在二维和三维空间都成立。为什么权重和必须为1我们可以从几何和代数两个角度理解。几何角度考虑一个一维情况线段AB上的点P可以表示为 P (1-t)A tB这里(1-t) t 1。推广到二维的三角形这个“权重和为1”的条件保证了点P位于由A, B, C张成的平面或直线上而不是在这个平面之外。如果权重和不为1得到的点会沿着从原点出发的方向进行缩放脱离了ABC所在的仿射空间。代数角度我们将P αA βB γC 改写为 P - A β(B - A) γ(C - A)。这表示向量AP可以用平面上的两个基向量AB和AC的线性组合来表示。这里α消失了因为它被约束条件 α 1 - β - γ 代换了。这个形式清晰地表明点P位于以A为原点以AB和AC为基向量的平面上。现在我们把向量形式定义P αA βB γC和约束α β γ 1写在一起。这是一个包含α, β, γ三个未知数的方程组在二维是2个坐标方程1个约束共3个方程在三维是3个坐标方程1个约束共4个方程但其中只有3个是独立的。求解这个方程组就能得到α, β, γ的解析表达式而这个表达式经过整理恰好就是我们之前通过面积比推导出来的公式这证明了面积定义和向量定义是等价的。2.2 重心坐标的几何与代数性质重心坐标拥有一系列优美而实用的性质这些性质是它在图形学中得以广泛应用的基础。仿射不变性Affine Invariance这是最重要的性质之一。如果对空间进行一个仿射变换线性变换平移例如旋转、缩放、剪切或平移点P的新坐标可以通过对顶点A, B, C施加同样的变换后再用相同的(α, β, γ)权重组合得到。也就是说重心坐标在仿射变换下保持不变。公式表达设T是一个仿射变换T(P) M * P t其中M是线性变换矩阵t是平移向量。那么有T(P) T(αA βB γC) α * T(A) β * T(B) γ * T(C)因为仿射变换保持线性组合和权重和为1的性质。应用意义这意味着我们可以在模型空间局部坐标计算好重心坐标然后对顶点进行变换到世界空间、观察空间最后用同样的重心坐标去插值变换后的顶点属性结果依然是正确的。这为顶点着色器中的属性插值提供了理论依据。插值属性Attribute Interpolation这是重心坐标在渲染中的核心用途。假设三角形的三个顶点拥有某些属性比如颜色C_A,C_B,C_C或者纹理坐标U_A,U_B,U_C或者法线向量N_A,N_B,N_C。那么三角形内部任意一点P的该属性值可以通过重心坐标进行线性插值得到Attr_P α * Attr_A β * Attr_B γ * Attr_C这个插值对于颜色、纹理坐标等标量或向量属性是直接可用的。对于法线等需要在插值后重新归一化的向量也通常先插值再归一化。范围与符号Range and Sign如果点P在三角形内部包括边则 α, β, γ ≥ 0。如果点P恰好位于一条边上例如边BC上则 α 0。如果点P在三角形外部则至少有一个坐标为负。坐标的符号可以用来快速进行点-三角形包含性测试。归一化Normalizationα β γ 1 是一个天然的归一化条件。这使得重心坐标构成一个“重心坐标系”三个坐标并非独立知道其中两个就能确定第三个。2.3 三维空间与透视校正插值在三维图形渲染中我们面临一个更复杂的情况透视投影。相机模型模拟了人眼“近大远小”的效果。当一个三角形在三维空间中远离相机时它在屏幕上的投影会变小。如果我们简单地将屏幕空间经过投影和视口变换后的2D坐标计算出的重心坐标直接用于插值三维空间中的属性如纹理坐标、颜色就会发生错误。最典型的例子就是纹理贴图在透视投影下出现扭曲而不是正确的透视效果。问题的根源在于屏幕空间Screen Space的线性插值并不对应着三维世界空间World Space或相机空间View Space的线性插值。假设我们在三维空间中有一条线段其两个端点的深度值Z值不同。将这条线段投影到屏幕上屏幕上的像素位置是均匀变化的但线段上对应点的三维深度值并不是均匀变化的。由于投影变换是非线性的涉及除以Z在屏幕上均匀移动对应在三维空间中是在一条双曲线上移动。因此对于需要在三维空间中保持正确关系的属性最典型的是纹理坐标我们必须进行透视校正插值Perspective-Correct Interpolation。透视校正插值的推导涉及齐次坐标和投影变换。这里给出结论和实际操作步骤核心思想在投影空间中需要被线性插值的不是属性Attr本身而是Attr / w和1 / w其中w是顶点变换到裁剪空间后的齐次坐标的w分量在标准透视投影中w分量就是观察空间中的深度值-Z_view。操作步骤 a. 在顶点着色器中将顶点属性Attr和变换后的裁剪空间坐标的w分量通常就是gl_Position.w传递给光栅化阶段。 b. 在光栅化阶段对每个像素先用屏幕空间坐标计算重心坐标 (α, β, γ)。 c. 使用这个重心坐标对Attr / w和1 / w进行插值interpolated_Attr_div_w α*(Attr_A/w_A) β*(Attr_B/w_B) γ*(Attr_C/w_C)interpolated_one_div_w α*(1/w_A) β*(1/w_B) γ*(1/w_C)d. 最后在片段着色器中恢复正确的属性值corrected_Attr interpolated_Attr_div_w / interpolated_one_div_w为什么有效数学上可以证明经过Attr/w和1/w的线性插值后再相除等价于在三维空间中对Attr进行正确的线性插值。现代GPU的光栅化硬件自动完成了这个过程。作为开发者我们只需要确保在顶点着色器中输出正确的w分量通常由投影矩阵保证并在片段着色器中直接使用插值后的属性即可硬件会帮我们做好透视校正。实操心得在编写自己的软件光栅化器时透视校正插值是必须实现的功能否则渲染的纹理会严重失真。一个常见的错误是只对颜色做了透视校正却忘了对纹理坐标做。记住所有在三维空间中定义的、需要从顶点插值到片段的属性都必须进行透视校正。这包括纹理坐标、颜色如果是在世界空间计算的光照、法线虽然法线插值后需要归一化但插值过程本身也需要透视校正等。3. 核心应用场景与实现细节重心坐标的理论很优美但它的价值最终体现在解决实际图形学问题上。下面我们深入几个最核心的应用场景看看如何将理论转化为代码。3.1 场景一三角形光栅化与像素包含性测试光栅化的第一步就是确定屏幕上的哪些像素被一个三角形覆盖。对于每个候选像素通常取其中心点我们需要判断它是否在三角形内。重心坐标提供了最优雅高效的解决方案。实现步骤预处理给定屏幕空间中的三角形顶点v0,v1,v2已经是2D坐标计算整个三角形的有向面积的两倍作为分母float area edgeFunction(v0, v1, v2); // 实际是2*面积这里edgeFunction(p, a, b)通常实现为二维叉积(b.x - a.x)*(p.y - a.y) - (b.y - a.y)*(p.x - a.x)。这个函数的值正比于点p到边ab的有向距离。遍历像素确定三角形的屏幕包围盒AABB遍历包围盒内的每个像素P。计算重心坐标对于每个像素中心P计算三个“子三角形”的有向面积float w0 edgeFunction(P, v1, v2); // 对应顶点v0的权重即α的分母部分 float w1 edgeFunction(P, v2, v0); // 对应顶点v1的权重β float w2 edgeFunction(P, v0, v1); // 对应顶点v2的权重γ注意这里的w0, w1, w2并不是最终的归一化重心坐标它们与面积成正比且w0 w1 w2 area整个三角形的有向面积。包含性测试与坐标归一化测试如果area为正假设我们约定顶点顺序为逆时针正面朝向那么当w0 0 w1 0 w2 0时像素P在三角形内或边上。这个测试同时完成了背面剔除如果area为负和点包含性判断。归一化如果像素在三角形内则计算归一化的重心坐标float alpha w0 / area;float beta w1 / area;float gamma w2 / area; // 或者 1.0 - alpha - beta属性插值使用计算出的alpha, beta, gamma对顶点属性如颜色、纹理坐标、深度进行插值。PixelColor alpha * v0.color beta * v1.color gamma * v2.color;优化技巧增量计算在遍历像素时w0, w1, w2可以增量更新。当像素P沿x轴移动一个像素Δx 1时edgeFunction(P, v1, v2)的值变化是常数(v1.y - v2.y)。同理沿y轴移动时变化是常数(v2.x - v1.x)。这可以避免每个像素都重新计算三个叉积显著提升性能。这是许多高效光栅化算法的基础。提前深度测试Early-Z在插值计算颜色之前先插值得到像素的深度值Z-Buffer并与深度缓冲区中的值比较。如果被遮挡则跳过后续昂贵的着色计算。3.2 场景二纹理映射与属性插值纹理映射是重心坐标最直观的应用。三角形的每个顶点关联一个纹理坐标 (u, v)。我们需要为三角形内的每个像素找到对应的纹理颜色。基本流程 a. 在顶点着色器阶段将纹理坐标作为顶点属性输出。 b. 在光栅化阶段如3.1所述计算每个像素的重心坐标(alpha, beta, gamma)。 c. 使用重心坐标插值纹理坐标float u alpha * u0 beta * u1 gamma * u2;float v alpha * v0 beta * v1 gamma * v2;d. 在片段着色器中使用插值得到的(u, v)对纹理进行采样。透视校正的重要性这里插值的u, v必须是经过透视校正的。如2.3节所述现代GPU管线自动完成了这一步。在软件渲染中你需要手动实现插值u/w,v/w和1/w然后在片段中相除得到正确的u和v。纹理过滤与Mipmapping直接使用浮点纹理坐标采样会导致锯齿。实际应用中还需要根据像素在纹理空间中的覆盖范围通过计算纹理坐标在屏幕空间中的导数dFdx,dFdy来选择合适的Mipmap层级并进行双线性/三线性过滤。这些操作都建立在正确插值得到的纹理坐标基础上。3.3 场景三射线与三角形求交Ray-Triangle Intersection在光线追踪Ray Tracing中我们需要判断一条射线是否与一个三角形相交并求出交点坐标。重心坐标再次提供了完美的工具。最常用的算法是Möller–Trumbore算法。它将射线方程和三角形的重心坐标表示相结合。射线方程R(t) O t * D其中O是原点D是单位方向向量t是距离。三角形平面方程用重心坐标表示P (1 - u - v) * V0 u * V1 v * V2其中(u, v)是重心坐标这里α 1-u-v,β u,γ v。联立方程O t * D (1 - u - v) * V0 u * V1 v * V2整理后可以得到一个关于t, u, v的线性方程组。Möller-Trumbore算法通过克莱姆法则Cramer‘s rule直接给出解vec3 e1 V1 - V0; vec3 e2 V2 - V0; vec3 pvec cross(D, e2); float det dot(e1, pvec); // 如果行列式接近0射线与三角形平面平行 if (abs(det) EPSILON) return false; float inv_det 1.0 / det; vec3 tvec O - V0; float u dot(tvec, pvec) * inv_det; if (u 0.0 || u 1.0) return false; // 交点重心坐标u超出范围 vec3 qvec cross(tvec, e1); float v dot(D, qvec) * inv_det; if (v 0.0 || u v 1.0) return false; // 交点重心坐标v超出范围或α1-u-v为负 float t dot(e2, qvec) * inv_det; if (t T_MIN || t T_MAX) return false; // 交点在射线有效区间外 // 相交成功交点坐标为 O t * D重心坐标为 (1-u-v, u, v)算法优势直接计算出交点参数t和重心坐标(u, v)无需先求平面交点再判断是否在三角形内。计算量相对较小只涉及向量加减、点积和叉积。得到的重心坐标(u, v)可以直接用于插值顶点的法线、纹理坐标等属性为后续的光照计算提供便利。注意事项实现时要注意数值精度问题。EPSILON的选择要合适过大可能漏交过小可能因浮点误差误判。通常选择一个与场景尺度相关的较小值如1e-6或1e-7。另外det 0和det 0可以用于区分射线是从三角形正面还是背面相交这在需要区分材质双面时有用。3.4 场景四几何处理与变形重心坐标在几何处理中也大有用武之地。例如在网格变形、蒙皮或形状插值中我们经常需要将一点从一个三角形“绑定”到另一个三角形。应用示例网格变形传递假设我们有一个源三角形SrcTri(A_s, B_s, C_s)和一个目标三角形DstTri(A_d, B_d, C_d)。已知源三角形内部一点P_s的重心坐标为(α, β, γ)。现在想要找到目标三角形上“对应”的点P_d。 利用重心坐标的仿射不变性我们可以直接使用相同的重心坐标在目标三角形上进行重建P_d α * A_d β * B_d γ * C_d这样得到的P_d在目标三角形内的相对位置与P_s在源三角形内的相对位置是一致的。这种方法常用于将细节从高模传递到低模或者在不同姿态的模型间传递纹理或颜色信息。4. 常见问题、优化与深度探讨即使理解了原理和基础应用在实际编码和优化中仍然会遇到不少坑。下面分享一些实践中积累的经验和需要深入思考的问题。4.1 精度问题与稳健的实现重心坐标计算涉及浮点数运算尤其是叉积容易受到数值误差影响。在光栅化或光线求交中这些误差可能导致像素闪烁Z-fighting或错误地判断点在边上。问题1点在边上的处理当像素中心恰好落在三角形边上时计算出的w0, w1, w2可能有一个为0或者由于浮点误差一个本应为正的小数变成了一个负的极小值如-1e-7。这会导致本应被两个三角形共享的像素可能被两个三角形都渲染也可能都不渲染造成缝隙或重叠。解决方案使用“上左”规则Top-Left Rule或约定严格的边界条件。上左规则定义三角形的边如果它是水平边则只属于其位于下方的三角形如果是非水平边则只属于其位于左侧的三角形。在计算edgeFunction时将“点在边上”结果0的情况视为“正”inside。更简单的实现方式是在判断w0 0时使用一个很小的正数epsilon作为容差w0 -epsilon。这能确保共享边的像素被确定性地分配给其中一个三角形。问题2透视除法后的深度缓冲精度在透视投影中经过投影变换和透视除法后深度值Z-Buffer的分布是非线性的靠近近裁剪面的精度高远离近裁剪面的精度低。这会导致远处的物体更容易出现Z-fighting。解决方案尽量让近裁剪面Near Plane设置得远一些当然不能裁剪掉需要的内容可以改善深度值分布的均匀性。使用反转的Z-BufferReversed-Z即用1.0 - depth或far / (far - near) * (1 - near/z)等形式存储深度能更好地利用深度缓冲的浮点数精度范围特别是在使用浮点深度纹理时。对于必须重叠的物体如贴花Decal可以使用轻微的深度偏移Depth Bias或修改渲染顺序来规避。4.2 重心坐标在非三角形图元中的应用重心坐标的概念可以推广到其他多边形但最自然和最常用的还是三角形。对于四边形可以将其分割为两个三角形然后分别处理。对于更复杂的凸多边形可以使用重心坐标的广义形式称为“重心坐标”或“重心插值”但计算更复杂且不具有三角形重心坐标的所有优美性质如非负性仅在凸多边形内保证。在曲面细分Tessellation中重心坐标用于在参数化域如一个三角形面片内生成新的顶点位置。例如对于三角形域新的顶点位置可以用其重心坐标(u, v, w)来表示然后通过插值控制点来计算出实际的三维坐标。4.3 软件实现中的优化技巧如果你正在实现一个软件光栅化器以下优化技巧能显著提升性能固定点运算Fixed-Point Arithmetic屏幕坐标和重心坐标的增量计算可以转换为整数或固定点数运算避免昂贵的浮点乘除。将坐标缩放一个大的常数如2^16用整数存储和运算最后再除以常数得到结果。扫描线算法结合重心坐标的增量性质可以实现经典的扫描线光栅化。对于三角形的每一条扫描线计算其与三角形左右边的交点然后在这条水平线段上从左到右遍历像素并增量更新重心坐标和深度值。层次化遍历不要总是逐个像素测试。可以先使用粗糙的网格如8x8的瓦片测试三角形与瓦片的包围盒是否相交只对相交的瓦片进行精细的像素级遍历。SIMD优化现代CPU支持SIMD指令如SSE, AVX。可以同时对多个像素例如4个或8个计算edgeFunction进行并行包含性测试和属性插值。4.4 重心坐标的局限性尽管强大重心坐标也有其局限性仅适用于三角形这是其定义决定的。对于其他多边形需要先三角化。线性插值的局限重心坐标提供的是线性插值。如果顶点属性在三角形面上的真实变化是非线性的例如由高光引起的颜色剧烈变化线性插值会产生不准确的结果。这就是为什么复杂的光照计算如Phong着色需要在每个像素上进行Phong Shading而不是在每个顶点上计算再插值颜色Gouraud Shading。投影变形如之前强调的屏幕空间的线性插值不等于三维空间的线性插值必须进行透视校正。这是新手最容易忽略的错误之一。5. 从理论到实践一个简单的软件光栅化器示例为了将以上所有知识点串联起来我们勾勒一个极简的软件光栅化器核心流程重点关注重心坐标的应用。假设我们渲染一个彩色三角形到帧缓冲区。// 数据结构 struct Vertex { Vec2 position; // 屏幕空间坐标 Vec3 color; // RGB颜色 float w; // 裁剪空间w分量用于透视校正 }; struct Triangle { Vertex v0, v1, v2; }; // 边缘函数计算有向面积的2倍 float edgeFunction(const Vec2 p, const Vec2 a, const Vec2 b) { return (b.x - a.x) * (p.y - a.y) - (b.y - a.y) * (p.x - a.x); } void rasterizeTriangle(const Triangle tri, FrameBuffer fb, DepthBuffer db) { // 1. 计算三角形包围盒 int minX floor(min(tri.v0.position.x, tri.v1.position.x, tri.v2.position.x)); int maxX ceil(max(tri.v0.position.x, tri.v1.position.x, tri.v2.position.x)); int minY floor(min(tri.v0.position.y, tri.v1.position.y, tri.v2.position.y)); int maxY ceil(max(tri.v0.position.y, tri.v1.position.y, tri.v2.position.y)); // 2. 计算整个三角形面积2倍 float area edgeFunction(tri.v0.position, tri.v1.position, tri.v2.position); if (area 0) return; // 背面剔除或退化三角形 // 3. 预计算顶点属性的 1/w Vec3 color0_div_w tri.v0.color / tri.v0.w; Vec3 color1_div_w tri.v1.color / tri.v1.w; Vec3 color2_div_w tri.v2.color / tri.v2.w; float inv_w0 1.0f / tri.v0.w; float inv_w1 1.0f / tri.v1.w; float inv_w2 1.0f / tri.v2.w; // 4. 遍历包围盒内像素 for (int y minY; y maxY; y) { for (int x minX; x maxX; x) { Vec2 pixelCenter(x 0.5f, y 0.5f); // 5. 计算重心坐标权重未归一化 float w0 edgeFunction(pixelCenter, tri.v1.position, tri.v2.position); float w1 edgeFunction(pixelCenter, tri.v2.position, tri.v0.position); float w2 edgeFunction(pixelCenter, tri.v0.position, tri.v1.position); // 6. 包含性测试使用小容差epsilon处理边上情况 const float epsilon 1e-6f; if (w0 -epsilon w1 -epsilon w2 -epsilon) { // 7. 归一化重心坐标 float alpha w0 / area; float beta w1 / area; float gamma 1.0f - alpha - beta; // 或 w2 / area // 8. 透视校正插值插值 1/w 和 color/w float interpolated_inv_w alpha * inv_w0 beta * inv_w1 gamma * inv_w2; float z 1.0f / interpolated_inv_w; // 恢复深度值 (view space z) // 深度测试 if (z db.get(x, y)) { db.set(x, y, z); Vec3 interpolated_color_div_w alpha * color0_div_w beta * color1_div_w gamma * color2_div_w; Vec3 final_color interpolated_color_div_w / interpolated_inv_w; // 9. 写入帧缓冲区简单clamp fb.setPixel(x, y, clamp(final_color, 0.0f, 1.0f)); } } } } }这个示例省略了纹理、法线、光照等复杂因素但清晰地展示了重心坐标在光栅化中的核心作用包含性测试、属性插值并与透视校正深度测试紧密结合。最后的体会重心坐标远不止是一个数学概念。它是图形学管线中数据流动的“粘合剂”将顶点的信息平滑、正确地传递到每一个像素。理解它不仅能帮你写出正确的渲染代码更能让你洞悉GPU光栅化阶段内部的工作原理。下次当你调试一个奇怪的纹理扭曲或插值错误时不妨先检查一下你的重心坐标计算对了吗透视校正做了吗
返回列表