TinyRenderer从零实现(二):lesson 1 直线绘制算法

基础算法:

1、试想给出两个点,连一条线,考虑屏幕像素是一块一块的,简单的想法是起点朝着终点的方向一块一块的画点,足够多的点就连成了一条线,具体实现:

(这里需要注意static_cast < type-id > ( expression )是把expression强制转换成type-id类型。)

void line1(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color)
{
    for (float t = 0.0f; t < 1.0f; t += 0.01f)   //求出起点到终点之间尽量多的点的坐标
    {
        int x = static_cast<int>(x0 + (x1 - x0) * t);
        int y = static_cast<int>(y0 + (y1 - y0) * t);
        image.set(x, y, color);
    }
}

2、起点(x0,y0),终点(x1,y1),然后中间点(x,y),引入斜率相关概念。(x - x0)/(x1-x0) = (y-y0)/(y1-y) = t; 然后得出方程式:y=(1-t)*y0+t*y1,代码

void line2(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color)
{
    for (float x = x0; x <= x1; x+=0.1)
    {
        float t = (x - x0) / static_cast<float>(x1 - x0);
        int y = static_cast<int>(y0 * (1.0 - t) + y1 * t);
        image.set(x, y, color);
    }
}

上述本质上就是对x进行采样,根据x的步长来确定精准度,每次采样的x0到x1范围越大、递增值越小,线条精度越高。但有的时候x0到x1的跨越范围很小,y0到y1的范围跨越很大,就会造成失真,比如x0=0;x1=10,而y0=0,y1=100这种情况,优化逻辑就是做两次采样,并判断选择,代码如下:

void line2_transpose(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color)
{
    for (int y = y0; y <= y1; y++)
    {
        float t = (y - y0) / static_cast<float>(y1 - y0);
        int x = static_cast<int>(x0 * (1.0f - t) + x1 * t);
        image.set(x, y, color);
    }
}
void line3(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color)
{
    int dx = std::abs(x1 - x0);
    int dy = std::abs(y1 - y0);

    if (dx >= dy)
    {
        if (x0 < x1)
        {
            line2(x0, y0, x1, y1, image, color);
        }
        else
        {
            line2(x1, y1, x0, y0, image, color);
        }
    }
    else
    {
        if (y0 < y1)
        {
            line2_transpose(x0, y0, x1, y1, image, color);
        }
        else
        {
            line2_transpose(x1, y1, x0, y0, image, color);
        }
    }
}

进阶算法:

1、DDA直线算法

2、中点画线算法

3、Bresenham画线算法

对于真实的直线,我们的横坐标变化dx,那么纵坐标就跟着变化dy(对应上图中横坐标增加1,纵坐标增加slope)。所以,当x = x0 + 1时,真实的直线上点坐标应该是(x0 + 1, y0 + 1 * slope),那么我们下一个可能选择的点就是(x0+1,y0)或者(x0+1, y0+1),我们通过比较y0 + 1 * slope和这两个待选点的中点位置来确定选择的结果,即x0 + 1点的y坐标是int(y0 + 1 * slope + 0.5)。

此时:

真实的y值为y0 + 1 * slope,y0和y0 + 1的中点为(2 * y0 + 1) / 2,即y0 + 0.5,所以我们实际上只需要比较1 * slope和0.5就可以:

1) 如果1 * slope > 0.5,说明真实的y值更靠近y0 + 1,那么下一个点就应该选择(x0 + 1, y0 + 1);

2) 如果1 * slope < 0.5,说明真实的y值更靠近y0,那么下一个点选择(x0 + 1, y0);

所以,我们就可以用斜率的大小来估计下一个点选哪一个。

在上面的代码中,derror就是斜率值,即我们所讨论的slope。

我们目前一直讨论的都只是(x0,y0)右边的第一个点如何选择,那么更后边的点该怎么判断呢?

我们还是以y0为参考基准,那么后续第n个点的真实y坐标就是y0 + n * slope,而它需要跟谁进行比较呢?

所以在代码中增加了一个error变量来累计误差,每次error大于0.5的时候,需要给y值加1,同时给error减1来平衡y+1带来的影响。

Timings: fifth and final attem

void line(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color)
{
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;//如果dy大于dx,那么就翻转x、y坐标
    }
    if (x0 > x1) {
        std::swap(x0, x1);//如果逆序,那么就交换起点终点坐标
        std::swap(y0, y1);
    }
    int dx = x1 - x0;
    int dy = std::abs(y1 - y0);

    int d = 2 * dy - dx;   
//判断斜率, 如果2dy-dx<0推出dy/dx<1/2,斜率小于0.5,则代表选择y,反之大于0.5则代表选择y+1
    int incrE = 2 * dy;
    int incrNE = 2 * dy - 2 * dx;

    int x = x0, y = y0;

    if (steep) {               //第一个像素点直接着色
        image.set(y, x, color);
    }
    else {
        image.set(x, y, color);
    }

    for (int x = x0 + 1; x <= x1; x++)
    {
        if (d < 0)
        {
            d += incrE;
        }
        else
        {
            if (y1 > y0)
                y++;
            else
                y--;
            d += incrNE;
        }

        if (steep) {
            image.set(y, x, color);
        }
        else {
            image.set(x, y, color);
        }
    }
}
void line5(int x0, int y0, int x1, int y1, TGAImage& image, const TGAColor& color) 
{
    bool steep = false;
    if (std::abs(x0 - x1) < std::abs(y0 - y1)) {
        std::swap(x0, y0);
        std::swap(x1, y1);
        steep = true;
    }
    if (x0 > x1) {
        std::swap(x0, x1);
        std::swap(y0, y1);
    }
    int dx = x1 - x0;
    int dy = y1 - y0;
    int derror2 = std::abs(dy) * 2;    //2dy
    int error2 = 0;
    int y = y0;
    for (int x = x0; x <= x1; x++) {
        if (steep) {
            image.set(y, x, color);  //steep为true表示x和y交换过,因此要颠倒
        }
        else {
            image.set(x, y, color);
        }
        error2 += derror2;  
        if (error2 > dx) {
            y += (y1 > y0 ? 1 : -1);
            error2 -= dx * 2;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值