记录:tinyrenderer
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的更多相关文章
- 记一次debug记录:Uncaught SyntaxError: Unexpected token ILLEGAL
在使用FIS3搭建项目的时候,遇到了一些问题,这里记录下. 这里是发布搭建代码: // 代码发布时 fis.media('qa') .match('*.{js,css,png}', { useHash ...
- nginx配置反向代理或跳转出现400问题处理记录
午休完上班后,同事说测试站点访问接口出现400 Bad Request Request Header Or Cookie Too Large提示,心想还好是测试服务器出现问题,影响不大,不过也赶紧上 ...
- Kali对wifi的破解记录
好记性不如烂笔头,记录一下. 我是在淘宝买的拓实N87,Kali可以识别,还行. 操作系统:Kali 开始吧. 查看一下网卡的接口.命令如下 airmon-ng 可以看出接口名称是wlan0mon. ...
- 2015 西雅图微软总部MVP峰会记录
2015 西雅图微软总部MVP峰会记录 今年决定参加微软MVP全球峰会,在出发之前本人就已经写这篇博客,希望将本次会议原汁原味奉献给大家 因为这次是本人第一次写会议记录,写得不好的地方希望各位园友见谅 ...
- 分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间)
分享一个SQLSERVER脚本(计算数据库中各个表的数据量和每行记录所占用空间) 很多时候我们都需要计算数据库中各个表的数据量和每行记录所占用空间 这里共享一个脚本 CREATE TABLE #tab ...
- 我是如何在SQLServer中处理每天四亿三千万记录的
首先声明,我只是个程序员,不是专业的DBA,以下这篇文章是从一个问题的解决过程去写的,而不是一开始就给大家一个正确的结果,如果文中有不对的地方,请各位数据库大牛给予指正,以便我能够更好的处理此次业务. ...
- 前端学HTTP之日志记录
前面的话 几乎所有的服务器和代理都会记录下它们所处理的HTTP事务摘要.这么做出于一系列的原因:跟踪使用情况.安全性.计费.错误检测等等.本文将谥介绍日志记录 记录内容 大多数情况下,日志的记录出于两 ...
- ASP.NET Core应用中如何记录和查看日志
日志记录不仅对于我们开发的应用,还是对于ASP.NET Core框架功能都是一项非常重要的功能特性.我们知道ASP.NET Core使用的是一个极具扩展性的日志系统,该系统由Logger.Logger ...
- python+uwsgi导致redis无法长链接引起性能下降问题记录
今天在部署python代码到预生产环境时,web站老是出现redis链接未初始化,无法连接到服务的提示,比对了一下开发环境与测试环境代码,完全一致,然后就是查看各种日志,排查了半天也没有查明是什么原因 ...
- 记录我这一年的技术之路(nodejs纯干货)
2015年12月28日23:19:54 更新koa应用.学习型网站和开发者工具等 coding伊始 开始认认真真的学习技术还是2015.10.21日开始的,记得很清楚,那天,是我在龙湖正式学习的第一天 ...
随机推荐
- 用EwoMail开源版搭建邮箱服务器
介绍: EwoMail是基于Linux的开源邮件服务器,支持一键搭建,集成了众多优秀稳定的组件,是一个快速部署.简单高效.安全稳定的邮件解决方案,支持电脑和手机的客户端,适合个人或邮箱功能需求少的企业 ...
- Qt/C++自定义界面大全/20套精美皮肤/26套精美UI界面/一键换肤/自定义颜色/各种导航界面
一.前言 这个系列对应自定义控件大全,一个专注于控件的编写,一个专注于UI界面的编写,程序员有两大软肋,一个是忌讳别人说自己的程序很烂很多bug,一个就是不擅长UI,基本上配色就直接rgb,对于第一点 ...
- Qt编写安防视频监控系统28-摄像机点位
一.前言 摄像机点位的功能主要是在图片地图和在线离线地图上设置对应摄像机的位置,然后双击可以实时预览对应摄像机的视频,在图片地图上拖动摄像机图标到对应位置,系统会自动保存位置信息,在网页地图上的摄像机 ...
- 提供Chrome浏览器插件网站
提供Chrome浏览器插件网站: 1.收藏猫插件 2.极简插件
- LetsTalk_Android中引导用户加入白名单图-2
=================================================================== ================================ ...
- JVM实战—8.如何分析jstat统计来定位GC
大纲 1.使用jstat了解线上系统的JVM运行状况 2.使用jmap和jhat了解线上系统的对象分布 3.如何分析JVM运行状况并合理优化 4.使用jstat分析模拟的BI系统JVM运行情况 5.使 ...
- ffmpeg 去除音频中的静音
去除音频中的静音 //去除所有超过0.3秒的静音部分 ffmpeg -i input.mp3 -af silenceremove=stop_periods=-1:stop_duration=0.3:s ...
- WPF Play Image slider animation using Storyboard
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using Sys ...
- React、Angular、Vue.js三者比较指南
Vue-React-Angular三者区别 1. 基本概念Angular 是一个应用设计框架与开发平台,用于创建高效.复杂.精致的单页面应用. React 是一个用于构建用户界面的 JavaScr ...
- TCP的网络编程基础
服务器建立 ServerSocket 对象ServerSocket 对象负责等待客户端请求建立套接字连接,类似邮局某个窗口中的业务员.也就是说, 服务器必须事先建立一个等待客户请求建立套接字的 连接 ...