(注:本小节不是对划线算法事无巨细的证明,如果你需要更加系统的学习,请跳转至文末的参考部分)

如果你是一名曾经学习过图形学基础的学生,那么你一定对画线算法稔熟于心,中点划线算法,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

[TinyRenderer] Chapter1 p3 Line的更多相关文章

  1. [poj] 2074 Line of Sight || 直线相交求交点

    原题 给出一个房子(线段)的端点坐标,和一条路的两端坐标,给出一些障碍物(线段)的两端坐标.问在路上能看到完整房子的最大连续长度是多长. 将障碍物按左端点坐标排序,然后用房子的右端与障碍物的左端连线, ...

  2. Android中锁定文件的方法

    androidSDK中并没有锁定文件相关的api. 但是android是基于linux操作系统的,linux比较底层,灵活性也更大,为了实现锁定文件的效果,大概有以下几种办法: 用chmod命令修改文 ...

  3. 计算几何模板 (bzoj 1336,poj 2451 ,poj3968)

    poj 3968 (bzoj 2642) 二分+半平面交,每次不用排序,这是几个算几版综合. #include<iostream> #include<cstdio> #incl ...

  4. Revit api 创建楼梯图元

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  5. Revit api 创建族并加载到当前项目

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  6. 曲线平滑-B样条曲线 【转】

    版权声明:本文为博主原创文章,未经博主允许不得转载. 3D空间曲线三次B样条平滑示例: struct D_DOT3D //D_DOT3D示例,未完全实现 { double x,y,z; } doubl ...

  7. 计算几何基础算法几何C++实现

    This file is implementation of Common Common Computational Geometry Algorithms.Please please pay att ...

  8. Blog2:nchu-software-oop-2022-4+5+期中

    Blog2:nchu-software-oop-2022-4+5+期中 一.前言 两次大作业是关于四边形和五边形的相关操作,类似于之前的三角形,但由于图形边数的变化,难度大大增加.对数学知识的运用考察 ...

  9. Blog1:nchu-software-oop-2022-1~3

    (1) 前言 针对在完成三次作业过程中产生的问题进行总结与分析. 三次作业一共十五(9+3+3)道题目,其中前两次难度较为简单,第三次复杂程度飞跃式上涨. 其中,涉及到java语言中基本的输入输出语句 ...

  10. [CareerCup] 7.5 A Line Cut Two Squares in Half 平均分割两个正方形的直线

    7.5 Given two squares on a two-dimensional plane, find a line that would cut these two squares in ha ...

随机推荐

  1. Serverless 工程实践 | 快速搭建 Kubeless 平台

    ​简介: Kubeless 是基于 Kubernetes 的原生无服务器框架.其允许用户部署少量的代码(函数),而无须担心底层架构. 快速搭建 Kubeless 平台 Kubeless 简介 Kube ...

  2. [Trading] 关于短线交易 Day Trading 的知识

    短线交易员操纵市场,试图利用股票.期货和其他金融产品价值的短期波动. 以下是你需要知道的关于日交易的知识,包括免费的交易图表,交易策略,以及日交易软件和平台. https://www.thebalan ...

  3. [ML] Tensorflow2 保存完整模型以及使用 HDF5

    将模型保存为完整的 HDF5 文件,后面可以直接加载使用: # cnblogs.com/farwish import tenforflow as tf model = tf.keras.models. ...

  4. 自动化测试数据生成:Asp.Net Core单元测试利器AutoFixture详解

    引言 在我们之前的文章中介绍过使用Bogus生成模拟测试数据,今天来讲解一下功能更加强大自动生成测试数据的工具的库"AutoFixture". 什么是AutoFixture? Au ...

  5. Swift实现判断目录下是否存在指定文件功能

    本文主要讲解以下这段名为 isDataJsonFilePathExists 的私有函数的 Swift 代码实现细节,该函数的作用是检查指定文件或文件夹是否存在,其返回值类型为 Bool 类型,如果存在 ...

  6. 鸿蒙HarmonyOS实战-ArkUI事件(手势方法)

    一.手势方法 应用程序的手势操作是指在移动设备上使用手指或手势进行与应用程序交互的方式.手势操作可以包括点击.滑动.双击.捏合等动作,用于实现不同的功能和操作. HarmonyOS中常见的手势操作及其 ...

  7. 开发环境需要同时安装2个nodejs版本

    由于同时有vue2和vue3的项目开发情况,vue2项目的nodejs版本是12,vue3项目在node12版本下运行不了,要求最低14版本,因此要用nvm同时安装和控制2个版本. 安装步骤: 1.卸 ...

  8. 详解GaussDB(DWS)中的行执行引擎

    本文分享自华为云社区<GaussDB(DWS)行执行引擎详解>,作者:yd_227398895. 1.前言 GaussDB(DWS)包含三大引擎,一是SQL执行引擎,用来解析用户输入的SQ ...

  9. echarts(数据可视化图表)

    echarts饼图详细 echarts下载 https://echarts.apache.org/zh/index.html  echarts官网 http://www.isqqw.com/#/hom ...

  10. 记一次DRF问题排障

    1 最近在搞django,在写一个接口的时候用到了APIview,之后再调用接口的时候一直显示405,不允许使用post方法 视图