自制C++三角形光栅器存在边缘像素缺失问题求助
自制C++三角形光栅器边缘空白间隙问题
自制的C++三角形光栅器渲染的三角形整体显示正常,但部分三角形边缘之间存在像素缺失的空白间隙。推测问题出在边缘方程或左上规则(top left rule)的实现上,但无法定位具体错误。相关代码如下:
struct EdgeEqn { float a, b, c; bool tl; }; GLPbo::EdgeEqn construct(const glm::vec3 a, const glm::vec3& b) { GLPbo::EdgeEqn x = { a.y - b.y,b.x - a.x,(b.y - a.y) * a.x - (b.x - a.x) * a.y }; if (x.a > 0) { x.tl = true; } else if (x.a < 0) { x.tl = false; } else if (x.b < 0) { x.tl = true; } else { x.tl = false; } return x; } bool edgeFunction(GLPbo::EdgeEqn x, const glm::vec2& c, float& line_eval) { float eval = x.a * c.x + x.b * c.y + x.c; line_eval = eval; return (eval > 0.f || (eval == 0.f && x.tl)) ? true : false; } bool Topleft_increase(GLPbo::EdgeEqn x, float eval) { return (eval > 0.f || (eval == 0.f && x.tl)) ? true : false; } bool GLPbo::render_triangle(glm::vec3 const& p0, glm::vec3 const& p1, glm::vec3 const& p2, glm::vec3 clr) { if (((p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y)) < 0) { culling++; return false; } GLPbo::Color random = { static_cast<GLubyte>(clr.r), static_cast<GLubyte>(clr.g) , static_cast<GLubyte>(clr.b), 255 }; int min_x = static_cast<int>(std::min({ p0.x, p1.x, p2.x })); int min_y = static_cast<int>(std::min({ p0.y, p1.y, p2.y })); int max_x = static_cast<int>(std::max({ p0.x, p1.x, p2.x })); int max_y = static_cast<int>(std::max({ p0.y, p1.y, p2.y })); GLPbo::EdgeEqn l0 = construct(p1, p2); GLPbo::EdgeEqn l1 = construct(p2, p0); GLPbo::EdgeEqn l2 = construct(p0, p1); float ev0 = 0, ev1 = 0, ev2 = 0; bool e0 = edgeFunction(l0, glm::vec2(min_x + 0.5, min_y + 0.5), ev0); bool e1 = edgeFunction(l1, glm::vec2(min_x + 0.5, min_y + 0.5), ev1); bool e2 = edgeFunction(l2, glm::vec2(min_x + 0.5, min_y + 0.5), ev2); for (int y = min_y; y <= max_y; ++y) { bool hE0 = e0; bool hE1 = e1; bool hE2 = e2; float hEv0 = ev0; float hEv1 = ev1; float hEv2 = ev2; for (int x = min_x; x <= max_x; ++x) { if (hE0 == true && hE1 == true && hE2 == true) { set_pixel(x, y, random); } hEv0 += l0.a; hEv1 += l1.a; hEv2 += l2.a; hE0 = Topleft_increase(l0, hEv0); hE1 = Topleft_increase(l1, hEv1); hE2 = Topleft_increase(l2, hEv2); } ev0 += l0.b; ev1 += l1.b; ev2 += l2.b; e0 = Topleft_increase(l0, ev0); e1 = Topleft_increase(l1, ev1); e2 = Topleft_increase(l2, ev2); } return true; }
问题定位与修复
1. 左上规则标记逻辑完全倒置
原construct函数中,判断tl标记的逻辑和左上规则的要求相反。左上规则的核心是相邻三角形的共享边缘必须被且仅被一个三角形渲染,正确的判定逻辑应该是:
- 当边缘从下到上(终点y坐标大于起点y)时,当前三角形保留这条边缘的像素
- 当边缘水平且从右到左(起点和终点y相同,终点x小于起点x)时,当前三角形保留这条边缘的像素
- 其他情况(从上到下、水平从左到右),当前三角形放弃这条边缘的像素
修复后的construct函数:
GLPbo::EdgeEqn construct(const glm::vec3 a, const glm::vec3& b) { GLPbo::EdgeEqn x = { a.y - b.y, b.x - a.x, (b.y - a.y) * a.x - (b.x - a.x) * a.y }; // 左上规则:仅向上或水平向左的边缘保留像素 if (b.y > a.y) { x.tl = true; } else if (b.y == a.y && b.x < a.x) { x.tl = true; } else { x.tl = false; } return x; }
2. 边缘方程符号与三角形内部判定一致性验证
原代码的背面剔除逻辑是叉乘小于0时剔除,说明只渲染顺时针环绕的三角形。边缘方程eval = a*x + b*y + c对于顺时针三角形的内部点,计算结果应为正,这部分原代码是正确的,无需修改。
3. 冗余函数合并(可选优化)
edgeFunction和Topleft_increase逻辑完全重复,可以合并为一个函数,减少代码冗余:
bool testEdge(const GLPbo::EdgeEqn& edge, float eval) { return eval > 0.f || (eval == 0.f && edge.tl); }
验证方法
- 绘制两个共享一条边缘的三角形,检查边缘像素是否被其中一个三角形正确填充,无间隙也无重叠
- 测试水平边缘、垂直边缘、倾斜边缘的场景,确保所有相邻三角形的衔接处都没有空白间隙
内容的提问来源于stack exchange,提问作者krater
相关产品推荐
相关产品推荐

