首页 > 其他分享 >[TinyRenderer] Chapter1 p3 Line

[TinyRenderer] Chapter1 p3 Line

时间:2024-06-13 21:21:17浏览次数:19  
标签:p3 begin int TinyRenderer y1 y0 Line x1 x0

(注:本小节不是对划线算法事无巨细的证明,如果你需要更加系统的学习,请跳转至文末的参考部分)
如果你是一名曾经学习过图形学基础的学生,那么你一定对画线算法稔熟于心,中点划线算法,Bresenham算法。其中,现代光栅化器中使用最多的就是Bresenham算法,它以去除了除法和浮点运算而著称。

但如果现在让你看下面的这段代码,你能否把它和Bresenham算法联系起来呢?

void Segment::draw(const Point2i begin, const Point2i end, const RGBPixel& color, BMPImage& image)
{
    int x0 = begin.x;
    int y0 = begin.y;
    int x1 = end.x;
    int y1 = end.y;

    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    int sx = (x0 < x1) ? 1 : -1;
    int sy = (y0 < y1) ? 1 : -1;
    int error = dx - dy;

    while (true)
    {
        image.set(x0, y0, color);

        if (x0 == x1 && y0 == y1)
            break;
        int e2 = 2 * error;
        if (e2 > -dy)
        {
            error -= dy;
            x0 += sx;
        }
        if (e2 < dx)
        {
            error += dx;
            y0 += sy;
        }
    }
}

如果你可以顺利地说出每行代码的含义,那么恭喜你,你已经完全掌握了Bresenham算法,你可以跳过本小节,进行下一小节的学习。
但如果你还有所异或,那么相信我,看完本小节,你必定有所收获。

本节目标

在光栅化渲染器中加入画线功能

分析

首先,让我们考虑一种再简单不过的场景,你从一个初始点begin出发,从左向右,沿着斜率在0~1的直线到达end

这是一个Bresenham算法的基础场景,在这个场景中,我们可以通过中点划线算法知道存在一个判断依据,用于确定在每次遍历时y的值是否需要改变。

这个值的变化由直线方程给出,在这里我们仅使用beginend给出。(具体论证请翻阅参考)
还记得我们的假设场景么?在这个场景下:

assert: (x0 < x1) and ((y1 – y0) < (x1 – x0))
δx = x1 – x0;
δy = y1 – y0;
incrE = 2 * δy;
incrNE = 2 * (δy - δx);
d = 2 * δy – δx;

保持x递增的同时,判断y是否改变。
d<0 -> d += incrE
else -> d += incrNE and ++y

现在我们得到了在斜率为0~1的时候的Bresenham算法,且begin.x<end.x
那么对于其他场景呢?

  1. begin.x > end.x
  2. slope < 0 or slope > 1
  3. 平行于坐标轴的方向

解决方案:

  1. 只需要将begin和end两个点做一下交换即可
  2. slope < 0 or slope > 1
    1. slope < 0只需要对begin和end加上符号,最后set的时候再变回来即可
    2. slope > 1这个更加简单,只需要交换xy即可
  3. 平行于轴体的方向只需要特判一下进行处理,而且这样效率更高
实现

实际上TinyRenderer的实现思路也是如此:

void line(int x0, int y0, int x1, int y1, TGAImage &image, 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; 
    int error2 = 0; 
    int y = y0; 
    for (int x=x0; x<=x1; x++) { 
        if (steep) { 
            image.set(y, x, color); 
        } else { 
            image.set(x, y, color); 
        } 
        error2 += derror2; 
        if (error2 > dx) { 
            y += (y1>y0?1:-1); 
            error2 -= dx*2; 
        } 
    } 
} 

而这种实现还可以转化成下面的这种写法,就是上面我们提到的方法,更加优雅,不需要特判条件,使用统一变量。

实现

void Segment::draw(const Point2i begin, const Point2i end, const RGBPixel& color, BMPImage& image)
{
    int x0 = begin.x;
    int y0 = begin.y;
    int x1 = end.x;
    int y1 = end.y;

    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    // 决定决策会落在坐标系四个方位中的哪一个
    int sx = (x0 < x1) ? 1 : -1;
    int sy = (y0 < y1) ? 1 : -1;
    // 维护error,用于决策下一个点的位置
    int error = dx - dy;

    while (true)
    {
        image.set(x0, y0, color);

        if (x0 == x1 && y0 == y1)
            break;
        int e2 = 2 * error;
        // 每次迭代会进行两次决策,共同决定下一个点是在一个角落中的哪一个
        // 如果在x轴上的误差较大
        if (e2 > -dy)
        {
            error -= dy;
            x0 += sx;
        }
        // 如果在y轴上的误差较大
        if (e2 < dx)
        {
            error += dx;
            y0 += sy;
        }
    }
}

误差判别的依据,B,C点

最后记得,反转一下y轴,因为bmp图像中的y轴方向是向下的

结果

Reference

  1. # Lesson 1: Bresenham’s Line Drawing Algorithm
  2. Bresenham.pdf
  3. # wiki Bresenham's line algorithm

标签:p3,begin,int,TinyRenderer,y1,y0,Line,x1,x0
From: https://www.cnblogs.com/qiyuewuyi/p/18246788

相关文章

  • 【esp32 学习笔记】 入门使用u8g2库(以OLED驱动芯片SSD1306为例)
    一、常用APIU8g2库提供了丰富的API,用于控制各种显示器并在屏幕上绘制文本、图形等元素。以下是U8g2库中一些常用的API:1.初始化-------U8G2U8G2(display,rotation,[,reset[,clock,data,cs,dc,reset,cs1,cs2,cs3]]) 初始化U8g2对象,其中display表示所使用的显示器......
  • DockerCompose+Jenkins+Pipeline流水线打包Vue项目(解压安装配置Node)入门
    场景DockerCompose+Jenkins+Pipeline流水线打包SpringBoot项目(解压安装配置JDK、Maven等)入门:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/139631755以上使用流水线配置和打包springboot后台项目,如果要使用流水线和配置node打包vue项目,可按如下步骤。注:博......
  • AP3462 4-30V 输入、1.2A 固定输出同步降压驱动器
    产品描述 AP3462是一款支持宽电压输入的同步降压电源管理芯片,输入电压4-30V范围内可实现1.2A的连续电流输出。AP3462具有优秀的恒压特性。AP3462采用电流模式的环路控制原理,实现了快速的动态响应。AP3462工作开关频率为220kHz,具有良好的EMI特性。AP3462内置有......
  • AP3464 4-30V 输入、2.4A 输出同步降压驱动器
    产品描述 AP3464是一款支持宽电压输入的同步降压电源管理芯片,输入电压4-30V范围内可实现2.4A的连续电流输出。通过调节FB端口的分压电阻,设定输出1.8V到28V的稳定电压。AP3464具有优秀的恒压/恒流(CC/CV)特性。AP3464采用电流模式的环路控制原理,实现了快速的动态......
  • AP3465 4-30V 输入、3A 输出同步降压驱动器
    产品描述 AP3465是一款支持宽电压输入的同步降压电源管理芯片,输入电压4-30V范围内可实现3A的连续电流输出。通过调节FB端口的分压电阻,设定输出1.8V到28V的稳定电压。AP3465具有优秀的恒压/恒流(CC/CV)特性。AP3465采用电流模式的环路控制原理,实现了快速的动态响......
  • HTTP1.x HTTP2 HTTP3 的简单对比
    协议简要描述比喻HTTP1.0短连接,一次数据通信,结束后就断开一次性道路,简单暴力通过。HTTP1.1长连接,连接可以被复用,但需要按照资源顺序复用。单向单车道,婚礼车队,不能逆序。HTTP2连接复用,增加了http头部压缩和帧传输,连接可以被异步服用,服务器端可以主动推送资源......
  • Mamba: Linear-Time Sequence Modeling with Selective State Spaces
    目录概Mamba代码GuA.andDaoT.Mamba:Linear-timesequencemodelingwithselectivestatespaces.2023.概Mamba.MambaS4和S4D虽然解决了SSM计算速度的问题,但是有一个前提,就是\(A,B,C,D\)是与时间\(t\)无关的.这导致这些方法只能采取一种固定的模......
  • etcd错误:Failed to defragment etcd member[127.0.0.1:2379] (context deadline excee
    etcd版本#etcdctlversionetcdctlversion:3.5.1APIversion:3.5问题在执行etcdctl--endpoints=http://127.0.0.1:2379defrag命令时,可能遇到错误:{"level":"warn","ts":"2024-06-12T18:20:17.444+0800","logger":"et......
  • 用ESP32(ESP32-CAM)(Micropython)、水位传感器、继电器、水泵 做根据水位自动加水的设
    基本流程水位传感器放在水缸内上方位置,水位到达水位传感器所在的位置时,水位传感器触发,并输出信号给到ESP32(ESP32CAM)。然后ESP32(ESP32CAM)控制继电器闭合,水泵启动并工作60S。之后还是根据传感器信号,决定继电器是闭合还是断开,一直循环下去。main.py点击查看main.pyfrom......
  • DockerCompose+Jenkins+Pipeline流水线打包SpringBoot项目(解压安装配置JDK、Maven等)
    场景DockerCompose中部署Jenkins(DockerDesktop在windows上数据卷映射):https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/139491855Docker+Jenkins+Gitee+Maven项目配置jdk、maven、gitee等拉取代码并自动构建以及遇到的那些坑:https://blog.csdn.net/BADAO_LIUMANG_......