绘制直线的光栅化算法

绘制直线的光栅化算法原文链接直线在这里实际上是指线段 知道了线段的两个端点位置 要把这个线段显示在光栅化显示器上 就是直线光栅化的目标 由于图形学所有的渲染都是依靠无数线段的渲染来完成的 所以直线的光栅化算法的效率显得尤为重要 从这里开始在数学的观点来看线段是笔直的 没有宽度的 但是在显示器上由于像素呈现四边形 理论上无法完全模拟线段的本来面目 所以只能用近似的方法来让它 看起来 是一条线段 这就是直

假设线段的两个端点为 (x_{0},y_{0})(x_{1},y_{1}) 假设x_{0}\leq x_{1}

于是直线方程可以用斜截式y=kx+b(其中k b已知)表示,从而对x进行遍历,根据方程得到实数点(x,y) 再四舍五入得到在光栅显示器的显示坐标 (round(x),round(y))

这样的算法是可以实现直线的光栅化的,而问题在于效率并不高。在遍历x计算y的时候使用的方程有一个乘法的存在(k*x),而这会大大影响渲染效率。于是一场消灭乘法的智慧盛宴开始了。

考虑直线 y=kx+b 假设我们遍历横坐标x来在屏幕上画这条直线,也就是说我们每次向右前进一个像素,考虑在这个x坐标下的y是什么。

假设我们考虑完了点(x_{i},y_{i}) 这时我们看到(x_{i+1},y_{i+1})中有:

用二维数组模拟屏幕像素用DDA算法显示的一条线段

#include 
    #include 
    using namespace std; bool pic[50][50]; void DrawLine(int x0,int y0,int x1,int y1) { double k = (y1-y0)*1.0/(x1-x0); double y = y0; for(auto x = x0 ; x <= x1 ; ++ x) { pic[(int)round(y)][x] = true; y += k; } } void ShowPic() { for(auto i = 49 ; i >= 0 ; -- i) { for(auto j:pic[i]) { if(j) cout << 'x'; else cout << 'o'; } cout << endl; } } int main() { DrawLine(2,2,48,30); ShowPic(); return 0; }

但是显然我们需要更加完善的算法,现在有两个问题:

至于第2点的效率问题,虽然我们已经把乘法变成了加法,但是这是一个浮点数的加法。虽然现代计算机都有协处理器来计算浮点数,但和整数加法效率想想也知道是没法比的。幸运的是人类的智慧没有在此止步,采用整数加法的直线光栅化算法很快便由 Jack E. Bresenham 发明了。

怎么看一点(x_{0},y_{0})在一个一般式Ax+By+C=0的上方还是下方(或是在线上)呢?结论是将点坐标代入式B(Ax_{0}+By_{0}+C)中,和0比较。若式子等于0,则点在直线上,若式子大于0则在直线上方,小于0则在直线下方。 这其实是一个很简单的结论,但是问过的人包括一些老师绝大多数都把这点搞错了。

考虑斜率属于(0,1)的情况。假设现在我们画完了点(x_{i},y_{i}),那么下一个点必然画在(x_{i}+1,y_{i})或者(x_{i}+1,y_{i}+1)。那么我们不妨画在那个最接近原始直线的点。我们考虑两个点的中点(x_{i}+1,y_{i}+0.5)在直线上方还是下方,从而确定应该画哪个点。如果中点在直线上方,那么显然应该画点(x_{i}+1,y_{i}),反之画点(x_{i}+1,y_{i}+1)。

伪代码

y = y0 foreach x in (x0,x1): draw(x,y) if(B*(A*(x+1)+B*(y+0.5)+C) < 0) ++y;//中点与下面的点同侧,所以画上面的点

下面考虑:

  • 如果下一个点画在了(x_{i}+1,y_{i}+1),紧接着考虑的应该是中点M_{1}(x_{i}+2,y_{i}+1.5),代入直线一般式方程得到d_{1} = F(x_{i}+2,y_{i}+1.5)=A(x_{i}+2)+B(y_{i}+1.5)+C = A(x_{i}+1)+B(y_{i}+0.5)+C+A+B=d_{0}+A+B
  • 如果下一个点画在了(x_{i}+1,y{i})考虑的应该是中点M_{2}(x_{i}+2,y_{i}+0.5),代入直线一般式方程得到d_{2}=F(x_{i}+2,y_{i}+0.5)=A(x_{i}+2)+B(y_{i}+0.5)+C = A(x_{i}+1)+B(y_{i}+0.5)+C+A=d_{0}+A

于是我们得到了关于d的递推公式

d_{new}=d_{old}+A+B(B*d<0时)

d_{new}=d_{old}+A(B*d>=0时)

这时,我们的中点画线算法至少达到了和DDA算法一样的效率(浮点数加法)。但是这里的A和B都是整数,所以我们考虑把d全部扩大一倍(因为和0比较不影响),这样2d_{0}=2A+B,成功避开了浮点数加法。

使用中点画线法模拟生成的直线:

#include 
    #include 
    using namespace std; bool pic[50][50]; void DrawALine(int x0,int y0,int x1,int y1) { int d = 2*(y0-y1)+(x1-x0);//这里的d为实际上d的两倍 int y = y0; for(auto x = x0 ; x <= x1 ; ++ x) { pic[y][x] = true; if(d < 0) { d += 2*(y0-y1)+2*(x1-x0); ++ y; } else d += 2*(y0-y1); } } void ShowPic() { for(auto i = 49 ; i >= 0 ; -- i) { for(auto j:pic[i]) { if(j) cout << 'o'; else cout << 'x'; } cout << endl; } } int main() { DrawALine(2,2,45,20); ShowPic(); return 0; }

至此,中点画线算法已经把直线光栅化的效率推至极限(整数加法)。Bresenham算法在此基础上,扩展了中点画线算法的适用范围。

d = 0 y = y0 //在此根据不同的方程处理好k的值 foreach x in (x0,x1) draw(x,y) d += k if(d>=1) d -= 1; if(d > 0.5) ++y;

这样算法的效率是浮点数加法,下面改进成整数加法。

  • 首先可以将d与0.5比较(浮点数比较)进行优化
    令e = d-0.5,这样e的初始值变成0.5,并且当e>=0.5时将e-=1。这样只需要看e的符号就可以知道画在哪个像素上了。

  • 从上一点继续考虑
    既然我们只用到了e的符号,那么也可以用2e\Delta x做到这一点。所以现在e的初始值为-\Delta x,又因为k=\Delta y/\Delta x 所以每次加k就是加2\Delta y。如果大于\Delta x那么就减去2\Delta x。

#include 
    #include 
    using namespace std; bool pic[50][50]; void DrawALine(int x0,int y0,int x1,int y1) { int dx = fabs(x0-x1); int dy = fabs(y0-y1); int y = y0; int e = -2*dx; for(auto x = x0 ; x <= x1 ; ++ x) { pic[y][x] = true; e += 2*dy; if(e > 0) ++y; if(e >= dx) e -= 2*dx; } } void ShowPic() { for(auto i = 49 ; i >= 0 ; -- i) { for(auto j:pic[i]) { if(j) cout << 'x'; else cout << 'o'; } cout << endl; } } int main() { DrawALine(2,2,40,10); ShowPic(); return 0; }

从代码可以看到效率是整数加法的。Bresenham算法总结了DDA算法和中点画线算法的优点,应用更加广泛。

至此,几种最基本的直线光栅化算法就介绍完了。直线光栅化是构造所有图形的基础,效率也极大程度影响渲染效率。

原文链接

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/178695.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月26日 下午4:42
下一篇 2026年3月26日 下午4:42


相关推荐

  • HeadSetup出现安全漏洞 恐使用户机密资讯外泄[通俗易懂]

    HeadSetup出现安全漏洞 恐使用户机密资讯外泄[通俗易懂]HeadSetup出现安全漏洞 恐使用户机密资讯外泄

    2022年4月21日
    59
  • vue.js 三种方式安装(vue-cli)

    vue.js 三种方式安装(vue-cli)Vue.js(读音/vjuː/,类似于view)是一个构建数据驱动的web界面的渐进式框架。Vue.js的目标是通过尽可能简单的API实现响应的数据绑定和组合的视图组件。它不仅易于上手,还便于与第三方库或既有项目整合。下面介绍三种Vue.js的安装方法:1.独立版本我们可以在Vue.js的官网上直接下载vue…

    2022年6月14日
    36
  • MySQL–基于Xtrabackup+Shell+Crond实现的数据库(全量+增量)热备份方案

    MySQL–基于Xtrabackup+Shell+Crond实现的数据库(全量+增量)热备份方案学习背景不管你是正在上学 还是已经工作了 想必多多少少都听说过删库跑路 rm rf 的情况 主要指的就是被有意或无意的删除掉了数据库的数据 而数据是整个业务最重要的价值体现 如果你的公司或者项目组没有一个成熟的数据库数据备份方案 一旦发生意外 我只能说年轻人 大意了吧 没有备份数据库 一 Xtrabackup 介绍官方介绍文档简单总结下来就是 XtraBackup 是 Percona 旗下的一款产品 支持 MySQL 数据库的热备份 在线不停机 并且是免费 开源 适用所有 MySQL 版本 非阻塞 紧密

    2026年3月19日
    2
  • Oracle与MySQL的区别 以及优缺点

    Oracle与MySQL的区别 以及优缺点Oracle 与 MySQL 的区别以及优缺点 MySQL 的特点 1 性能卓越 服务稳定 很少出现异常宕机 2 开放源代码无版本制约 自主性及使用成本低 3 历史悠久 社区和用户非常活跃 遇到问题及时寻求帮助 4 软件体积小 安装使用简单且易于维护 维护成本低 品牌口碑效应 5 支持多种 OS 提供多种 API 接口 支持多种开发语言 对流行的 PHP Java 很好的支持 MySQL 的缺点 1 MySQL 最大的缺点是其安全系统 主要是复杂而非标准 另外只有到调用 mysqladmin 来重读用户权限才会发生改变 2

    2026年3月18日
    2
  • Navicat15在线激活码【在线注册码/序列号/破解码】

    Navicat15在线激活码【在线注册码/序列号/破解码】,https://javaforall.net/100143.html。详细ieda激活码不妨到全栈程序员必看教程网一起来了解一下吧!

    2022年3月18日
    62
  • Hotspot Shield Launch[通俗易懂]

    Hotspot Shield Launch[通俗易懂]你们懂的

    2022年7月3日
    32

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号