Bresenham’s line drawing(布雷森汉姆算法)

进行games101的光栅化作业时,对其渲染原理仍不甚了解,找到tinyrenderer软光栅项目。在此记录下试错的过程。

作者在最初为我们做好了framebuffer,读者入手的方向实际是从渲染的过程开始。对于如何渲染出像素显示在画面上,应该需要从其他博主那进行学习,或者从作者实现的文件中分析,这里就不做多余的解释。

友情提示:这里记录个人对tinyrenderer原理的理解,若需源代码请从原作者的github处下载。(作者的博客内蓝色高光处包含了不同阶段源代码的地址)

github地址在这里:https://ssloy.github.io/tinyrenderer

作者第一节从线的绘制开始,一个三角面有三个顶点,即绘制三条直线。

Bresenham’s line drawing(布雷森汉姆算法)

给出两个顶点a(ax,ay),b(bx,by),渲染出两点间的直线。

按照作者的说法,直接抛出算法会很难以理解,采取渐进的形式,逐渐演化算法的执行。

假设参数t \(\in\)[0,1],定义二维点(x(t),y(t))如下:


x(t) = ax + t * (bx - ax)

y(t) = ay + t * (by - by)
对于该公式的推导

可以想象直线上两点,在两点间再取一点(x(t),y(t)),(这里设为(x,y)或许更好一些,但是图画好了懒得改了)

很容易可以想到它们间存在的相似三角形,

(y(t) - ay) / (by - ay) = t

t为0时,y(t) = ay,t为1时,y(t) = by

之后,简单推导即可得到参数方程,x(t)同理可得。

代码实现

void line(int ax,int ay,int bx,int by, TGAImage &framebuffer,TGAColor color){
for(float t = 0; t < 1; t += 0.02){
int x = ax + std::round(t * (bx - ax));
int y = ay + std::round(t * (by - ay));
framebuffer.set(x,y,color);
}
}



可以注意到红线部分存在四个缺口,仔细观察一下可以发现

t的取值为0-1,每0.02取值进行运算,可取51次

cx - ax = 62 - 7 = 55 次

其gap = 55 - 1 = 4

t的取值不足导致了gap的出现:

一个直接的解决方法是:

可以将 t += .02改为 t += .01,这样确实可以解决当前的gap问题,但当cx - ax的差值更大时,t的取值仍然会不足;

作者给出的方法则是使用t的定义式,而不是直接赋值:

t = (x(t) - ax) / (bx - ax)
或者
t = (y(t) - ay) / (by - ay)
对于t定义式可行的个人见解 每一个(x(t),y(t))坐标表示一个像素点位置,两点间横坐标差值即缺少的横向像素点的数量,纵坐标同理,这样即可遍历每一个单位像素点的横坐标,得到对应的t值,从而求得相应的纵坐标。

函数实现

void line(int ax,int ay,int bx,int by, TGAImage &framebuffer,TGAColor color){
for(int x = ax; x <= bx; x ++){
//t需要float型,故须使用static_cast<float>来使计算返回浮点值
float t = (x - ax) / static_cast <float> (bx - ax);
int y = ay + std::round(t * (by - ay));
framebuffer.set(x,y,color);
}
}

好了,现在我们出现两个问题,黄线出现了大量gap,红线消失:

红线消失很好发现原因,在for循环中,若bx < ax,循环会被打破,也就不会有线产生。

在bx < ax时,我们交换两点的位置即可。


if(bx < ax){
std::swap(ax,bx);
std::swap(ay,by);
}

绿线的问题,观察下图的白框和黄点可以看出



由于取整的问题,一个单位横坐标跨越了多个单位纵坐标。也就是说一个纵坐标可以对应着不到一个单位的横坐标,取整后可以做到连续的纵坐标对应着同一个横坐标,这样就避免了gap的出现:


bool steep = std::abs(bx - ax) < std::abs(by - ay);
if(steep){
std::swap(ax,ay);
std::swap(bx,by);
} //在绘制直线时,须判断steep,换回x,y位置
if(steep)
framebuffer.set(y,x,color)
else
framebuffer.set(x,y,color);



绘制成功!

接下来是性能上的优化,逐渐的接近布雷森汉姆算法

既然要优化性能,需要先了解花费性能较高的部分,首先是framebuffer.set()的调用,可惜我们此次不涉及该函数的设计,不考虑。其次就是y值的计算:

可以将t(x)式带入y(t)可得


y(x) = ay + (by - ay) * (x(t) - ax) / (bx - ax)

这样我们在代码上可以进一步处理:

我们知道,在for循环中,x(t)的值从ax开始,每次增加1,

所以(x(t) - ax)的值则为0,1,2,……;



y(i + 1) - y(i) = (by - ay) / (bx - ax)

在该值为0时,上式子中y(ax) = ay;

在这种条件下,y(x)的代码可转化为:


//此处y由int改为float
//每次循环y增值(by - ay) /static_cast (bx - ax),该数为float,多次增加后其值对y的影响会越来越大,将y改为float,可以避免误差的发生
float y = ay;
for(int x = ax; x <= bx; x ++){
if(steep)
framebuffer.set(y,x,color);
else
framebuffer.set(x,y,color);
y += (by - ay) /static_cast <float> (bx - ax);
}

此次以整型转换为浮点型为代价,经过作者测试,由3.99s降至2.8s,有显著优化



避免浮点运算可以为我们提升很大的性能,布雷森汉姆算法即使如此。

我们将y由float改为int:

易知,增值(by - ay) /static_cast <float> (bx - ax)不会大于一,自动由float转为int时,会丢弃小数位,不进行四舍五入,这意味着y值将不会发生改变;

我们通过引入变量 float error = 0;每次循环error增值(by - ay) /static_cast (bx - ax);即error做了float y之前做的事;
超过+-0.5,便为y+-1,error +-1;

实现代码

//此处改为使用std::abs来使得每次error的增值为正,以省去判断正负的问题。
float error = 0;
int y = ay;
for(int x = ax; x < bx; x ++){
if(steep)
framebuffer.set(y,x,color);
else
framebuffer.set(x,y,color);
error += std::abs(by - ay) / static_cast <float> (bx - ax);
if(error > .5){
y += by > ay ? 1 : -1;
error -= 1;
}
}

不难猜出,这次引入float error会花费更多的性能,为进行接下来的消除浮点运算。



我在这里进行进一步的演算,以便可以更清晰的消除浮点运算(error由float转为int):


//这里的表现形式不太合规,但应该可以理解
[error += std::abs(by - ay)] / static_cast <float> (bx - ax) > 0.5
两边同乘static_cast <float> (bx - ax)
[error += std::abs(by - ay)] > 0.5 * static_cast <float> (bx - ax)
此时,不可变的浮点数仅有0.5,那么我们两边同乘2
[error += 2 * std::abs(by - ay)] > (bx - ax)

至此,我们成功消除了浮点运算,实现布雷森汉姆算法。

代码实现

int ierror = 0;
int y = ay;
for(int x = ax; x < bx; x ++){
if(steep)
framebuffer.set(y,x,color);
else
framebuffer.set(x,y,color);
ierror += 2 * std::abs(by - ay);
if(ierror > (bx - ax)){
y += by > ay ? 1 : -1;
ierror -= 2 * (bx - ax);
}
}

从作者的测试结果来看,布雷森汉姆算法的实际速度要慢于第一次的优化,原因则是现在的整数运算并不总是比浮点运算更高效。

至于无分支结构的优化,可以在作者的博客中进行了解,我实际进行的测试中,作者的无分支形式实际要花费更多的性能,可能是现在的CPU的对分支版本的优化更加高效,不充分的无分支形式无法在其中获得优势

记录:tinyrenderer的更多相关文章

  1. 记一次debug记录:Uncaught SyntaxError: Unexpected token ILLEGAL

    在使用FIS3搭建项目的时候,遇到了一些问题,这里记录下. 这里是发布搭建代码: // 代码发布时 fis.media('qa') .match('*.{js,css,png}', { useHash ...

  2. nginx配置反向代理或跳转出现400问题处理记录

    午休完上班后,同事说测试站点访问接口出现400 Bad Request  Request Header Or Cookie Too Large提示,心想还好是测试服务器出现问题,影响不大,不过也赶紧上 ...

  3. Kali对wifi的破解记录

    好记性不如烂笔头,记录一下. 我是在淘宝买的拓实N87,Kali可以识别,还行. 操作系统:Kali 开始吧. 查看一下网卡的接口.命令如下 airmon-ng 可以看出接口名称是wlan0mon. ...

  4. 2015 西雅图微软总部MVP峰会记录

    2015 西雅图微软总部MVP峰会记录 今年决定参加微软MVP全球峰会,在出发之前本人就已经写这篇博客,希望将本次会议原汁原味奉献给大家 因为这次是本人第一次写会议记录,写得不好的地方希望各位园友见谅 ...

  5. 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)

    分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...

  6. 我是如何在SQLServer中处理每天四亿三千万记录的

    首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务. ...

  7. 前端学HTTP之日志记录

    前面的话 几乎所有的服务器和代理都会记录下它们所处理的HTTP事务摘要.这么做出于一系列的原因:跟踪使用情况.安全性.计费.错误检测等等.本文将谥介绍日志记录 记录内容 大多数情况下,日志的记录出于两 ...

  8. ASP.NET Core应用中如何记录和查看日志

    日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.Logger ...

  9. python+uwsgi导致redis无法长链接引起性能下降问题记录

    今天在部署python代码到预生产环境时,web站老是出现redis链接未初始化,无法连接到服务的提示,比对了一下开发环境与测试环境代码,完全一致,然后就是查看各种日志,排查了半天也没有查明是什么原因 ...

  10. 记录我这一年的技术之路(nodejs纯干货)

    2015年12月28日23:19:54 更新koa应用.学习型网站和开发者工具等 coding伊始 开始认认真真的学习技术还是2015.10.21日开始的,记得很清楚,那天,是我在龙湖正式学习的第一天 ...

随机推荐

  1. Windows修改电脑DNS

    访问浏览器出现无法访问此页面,找不到DNS地址,则可以通过如下方式修改DNS 按下windows键+R键(两个键一起按) 出现下面窗口 输入control按回车键(Enter键)就会出现下面的窗口 D ...

  2. Qt开发经验小技巧211-215

    QMainWindow 在对停靠窗体进行排列的时候,有些不常用的设置容易遗忘,建议将 QMainWindow 的头文件函数过一遍一目了然. //设置停靠参数,不允许重叠,只允许拖动 this-> ...

  3. 模拟数据生成器mock.js入门

    1.在某一指定目录下,按下shift+鼠标右键,,点击"在此处打开Powershell窗口(S)",启动命令行窗口.如下图: 2.在窗口中输入以下命令以便创建项目:vue crea ...

  4. [转]When allowCredentials is true, allowedOrigins cannot contain the special value “*“

    前言 项目接口访问出现allowedOrigins cannot contain the special value "*" java.lang.IllegalArgumentEx ...

  5. IM开发技术学习:揭秘微信朋友圈这种信息推流背后的系统设计

    本文由徐宁发表于腾讯大讲堂,原题"程序员如何把你关注的内容推送到你眼前?揭秘信息流推荐背后的系统设计",有改动和修订. 1.引言 信息推流(以下简称"Feed流" ...

  6. Git Permission denied

    问题现象 家里电脑 git pull 项目时,提示:Permission denied,ssh -T 测试又是正常的,如下图 同样配置和密钥,在公司电脑就可以正常 pull .push . 问题原因 ...

  7. KMS for Office 2021

    I. 镜像下载 官方镜像下载地址: Office 2021 专业增强版: https://officecdn.microsoft.com/pr/492350f6-3a01-4f97-b9c0-c7c6 ...

  8. 按部就班--从零开始建设k8s监控(二)

    前言 书接上文,prometheus已经安装好了,并且能够对k8s的整体状态进行监控,但是我们还需要更多 环境准备 组件 版本 操作系统 Ubuntu 22.04.4 LTS docker 24.0. ...

  9. Solution Set -「AGC 001~003」C~F

    目录 「AGC 001C」Shorten Diameter 「AGC 001D」Arrays and Palindrome * 「AGC 001E」BBQ Hard * 「AGC 001F」Wild ...

  10. w3cschool-OpenResty 最佳实践

    https://www.w3cschool.cn/openresty1/ OpenResty 简介 OpenResty(也称为 ngx_openresty)是一个全功能的 Web 应用服务器.它打包了 ...