You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于重心坐标算法的三角形边缘光栅化异常问题求助

重心坐标算法渲染三角形边缘偏差问题排查

问题描述

正在给自定义3D渲染引擎添加Z-buffer,采用重心坐标算法对三角形内部点的深度进行插值,但该算法在三角形边缘计算上存在偏差。左侧绿色为旧算法渲染结果,右侧红色为重心坐标算法结果,叠加后边缘偏差明显。

测试三角形坐标(渲染分辨率320x240,右侧三角形仅偏移50像素):

{50, 20}
{70, 60}
{30, 50}

叠加对比图

旋转三角形示例(正确边缘为红色):
旋转三角形对比

算法实现

重心坐标计算与渲染代码

void barycentric(vec3f a, vec3f b, vec3f c, vec3f p, float *u, float *v, float *w) {
    vec3f v0 = vec3f_sub(b,a), v1 = vec3f_sub(c,a), v2 = vec3f_sub(p,a);
    float d00 = vec3f_dot_product(v0, v0);
    float d01 = vec3f_dot_product(v0, v1);
    float d11 = vec3f_dot_product(v1, v1);
    float d20 = vec3f_dot_product(v2, v0);
    float d21 = vec3f_dot_product(v2, v1);
    float denom = d00 * d11 - d01 * d01;
    *v = (d11 * d20 - d01 * d21) / denom;
    *w = (d00 * d21 - d01 * d20) / denom;
    *u = 1.0f - *v - *w;
}

void drawBarycentricTriangle(uint32_t pixels[], vec3f a, vec3f b, vec3f c, uint32_t color) {
    int min_x = (int)round(fminf(a.x, fminf(b.x, c.x)));
    int max_x = (int)round(fmaxf(a.x, fmaxf(b.x, c.x)));
    int min_y = (int)round(fminf(a.y, fminf(b.y, c.y)));
    int max_y = (int)round(fmaxf(a.y, fmaxf(b.y, c.y)));

    // iterate over the bounding box of the triangle
    for (int y = min_y; y <= max_y; y++) {
        for (int x = min_x; x <= max_x; x++) {
            vec3f p = {x, y, 0};
            float u, v, w;
            barycentric(a, b, c, p, &u, &v, &w);
            // only draw pixels if the points are inside the triangle
            if (u >= 0 && v >= 0 && w >= 0) {
                drawPixel(pixels, x, y, color);
            }
        }
    }
}

旧三角形渲染算法代码

void drawTriangle(uint32_t pixels[], vec2i P0, vec2i P1, vec2i P2, uint32_t color) {
    // sort the points by y-coordinate
    if (P1.y < P0.y) { swap(&P1.y, &P0.y); swap(&P1.x, &P0.x); }
    if (P2.y < P0.y) { swap(&P2.y, &P0.y); swap(&P2.x, &P0.x); }
    if (P2.y < P1.y) { swap(&P2.y, &P1.y); swap(&P2.x, &P1.x); }
    
    // degenerate triangle if all points on the same horizontal line
    if (P0.y == P1.y && P1.y == P2.y) return; 
    if (P0.x<0 && P1.x<0 && P2.x<0) return;
    if (P0.y<0 && P1.y<0 && P2.y<0) return;

    float invslope1, invslope2, curx1, curx2;
    if (P0.y == P1.y) {
        // flat-top triangle
        if (P1.x < P0.x) { swap(&P1.x, &P0.x); }
        invslope1 = (float)(P2.x - P0.x) / (P2.y - P0.y);
        invslope2 = (float)(P2.x - P1.x) / (P2.y - P1.y);
        curx1 = P2.x;
        curx2 = P2.x;
        for (int y = P2.y; y >= P0.y; y--) {
            int start = (int)round(curx1);
            int end = (int)round(curx2);
            if (start > end) { swap(&start, &end); }
            for (int x = start; x <= end; x++) {
                drawPixel(pixels, x, y, color);
            }
            curx1 -= invslope1;
            curx2 -= invslope2;
        }
    } else if (P1.y == P2.y) {
        // flat-bottom triangle
        if (P2.x < P1.x) { swap(&P2.x, &P1.x); }
        invslope1 = (float)(P1.x - P0.x) / (P1.y - P0.y);
        invslope2 = (float)(P2.x - P0.x) / (P2.y - P0.y);
        curx1 = P0.x;
        curx2 = P0.x;
        for (int y = P0.y; y <= P1.y; y++) {
            int start = (int)round(curx1);
            int end = (int)round(curx2);
            if (start > end) { swap(&start, &end); }
            for (int x = start; x <= end; x++) {
                drawPixel(pixels, x, y, color);
            }
            curx1 += invslope1;
            curx2 += invslope2;
        }
    } else {
        // general case: split into a flat-top and flat-bottom triangle
        vec2i P3 = { P0.x + (int)((float)(P1.y - P0.y) / (P2.y - P0.y) * (P2.x - P0.x)), P1.y };
        invslope1 = (float)(P1.x - P0.x) / (P1.y - P0.y);
        invslope2 = (float)(P3.x - P0.x) / (P3.y - P0.y);
        curx1 = P0.x;
        curx2 = P0.x;
        for (int y = P0.y; y <= P1.y; y++) {
            int start = (int)round(curx1);
            int end = (int)round(curx2);
            if (start > end) { swap(&start, &end); }
            for (int x = start; x <= end; x++) {
                drawPixel(pixels, x, y, color);
            }
            curx1 += invslope1;
            curx2 += invslope2;
        }
        invslope1 = (float)(P2.x - P1.x) / (P2.y - P1.y);
        invslope2 = (float)(P2.x - P3.x) / (P2.y - P3.y);
        curx1 = P2.x;
        curx2 = P2.x;
        for (int y = P2.y; y >= P1.y; y--) {
            int start = (int)round(curx1);
            int end = (int)round(curx2);
            if (start > end) { swap(&start, &end); }
            for (int x = start; x <= end; x++) {
                drawPixel(pixels, x, y, color);
            }
            curx1 -= invslope1;
            curx2 -= invslope2;
        }
    }
}

渲染效果对比

  • 重心坐标算法渲染效果:
    重心坐标算法渲染

  • 旧算法渲染效果:
    旧算法渲染

核对过算法逻辑未发现问题,请求定位边缘偏差原因并提供修复方案。


内容的提问来源于stack exchange,提问作者Nitaki

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 12:35:57