开一个新坑,记录从零开始学习图形学的过程,现在还是个正在学习的萌新,写的不好请见谅。

首先从最基础的直线生成算法开始,当我们要在屏幕上画一条直线时,由于屏幕由一个个像素组成,所以实际上计算机显示的直线是由一些像素点近似组成的,直线生成算法解决的是如何选择最佳的一组像素来显示直线的问题。

对这个问题,首先想到的最暴力的方法当然是从直线起点开始令x或y每次增加1直到终点,每次根据直线方程计算对应的函数值再四舍五入取整,即可找到一个对应的像素,但这样做每一步都要进行浮点数乘法运算,效率极低,所以出现了DDA和Bresenham两种直线生成算法。

数值微分法(DDA算法)

DDA算法主要是利用了增量的思想,通过同时对x和y各增加一个小增量,计算下一步的x和y值。

根据上式可知$\bigtriangleup x$=1时,x每递增1,y就递增k,所以只需要对x和y不断递增就可以得到下一点的函数值,这样避免了对每一个像素都使用直线方程来计算,消除了浮点数乘法运算。

代码实现:

#include<Windows.h>
#include<iostream>
#include<cmath>
using namespace std; const int ScreenWidth = ;
const int ScreenHeight = ; LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case WM_CLOSE:
DestroyWindow(hWnd);
break;
case WM_DESTROY:
PostQuitMessage();
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return ;
} void DDALine(int x0,int y0,int x1,int y1,HDC hdc)
{
int i=;
float dx, dy, length, x, y;
if (fabs(x1 - x0) >= fabs(y1 - y0))
length = fabs(x1 - x0);
else
length = fabs(y1 - y0);
dx = (x1 - x0) / length;
dy = (y1 - y0) / length;
x = x0;
y = y0;
while (i<=length)
{
SetPixel(hdc,int(x + 0.5), ScreenHeight--int(y + 0.5), RGB(, , ));
x = x + dx;
y = y + dy;
i++;
}
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nShowCmd)
{
WNDCLASS wcs;
wcs.cbClsExtra = ; // 窗口类附加参数
wcs.cbWndExtra = ; // 窗口附加参数
wcs.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 窗口DC背景
wcs.hCursor = LoadCursor(hInstance, IDC_CROSS); // 鼠标样式
wcs.hIcon = LoadIcon(NULL, IDI_WINLOGO); // 窗口icon
wcs.hInstance = hInstance; // 应用程序实例
wcs.lpfnWndProc = (WNDPROC)WinProc;
wcs.lpszClassName = "CG";
wcs.lpszMenuName = NULL;
wcs.style = CS_VREDRAW | CS_HREDRAW;
RegisterClass(&wcs);
HWND hWnd;
hWnd = CreateWindow("CG","DrawLine", WS_OVERLAPPEDWINDOW, , , ScreenWidth, ScreenHeight, NULL, NULL, hInstance, NULL);
ShowWindow(hWnd, nShowCmd);
UpdateWindow(hWnd);
MSG msg; // hdc init
HDC hdc = GetDC(hWnd); // 绘图,画一条从点(0,0)到(100,350)的直线
DDALine(, , , , hdc);// 消息循环
while (GetMessage(&msg, , NULL, NULL)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
} // release
ReleaseDC(hWnd, hdc);
return ;
}

以上是DDA算法的实现,WinMain和WinProc函数是Windows API编程特有的,我们只需要关注DDALine这个绘图函数,该函数传入两个点的坐标画出一条直线。

首先判断起点和终点间x轴和y轴哪个轴向的跨度更大(斜率范围),为了防止丢失像素,应让跨度更大的轴向每次自增1,这样能获得更精确的结果。

接下来就没什么好说的,依次让x和y加上增量然后四舍五入就行了,浮点数四舍五入可以直接用int(x+0.5)计算,setPixel函数用于设置像素的颜色值(需要传入窗口的hdc句柄),由于Windows窗口坐标的原点在左上角,所以拿窗口高度减去y值就可以转换成平常习惯的左下角坐标系了。

运行结果:

Bresenham算法

DDA算法尽管消除了浮点数乘法运算,但仍存在浮点数加法和取整操作,效率仍有待提高,1965年Bresenham提出了更好的直线生成算法,成为了时至今日图形学领域使用最广泛的直线生成算法,该算法采用增量计算,借助一个误差量的符号确定下一个像素点的位置,该算法中不存在浮点数,只有整数运算,大大提高了运行效率。

我们先只考虑斜率在0-1之间的情况,从线段左端点开始处理,并逐步处理每个后续列,每确定当前列的像素坐标$(x_{i},y_{i})$后,那么下一步需要在列$x_{i+1}$上确定y的值,此时y值要么不变,要么增加1,这是因为斜率在0-1之间,x增长比y快,所以x每增加1,y的增量是小于1的。

对于左端点默认为其像素坐标,下一列要么是右方的像素,要么是右上方的像素,设右上方像素到直线的距离为d2,右方像素到直线的距离为d1,显然只需要判断直线离哪个像素点更近也就是d1-d2的符号即可找到最佳像素。

所以可以推出以下式子:

其中$\bigtriangleup x$起点到终点x轴上距离,$\bigtriangleup y$为y轴上距离,k=$\bigtriangleup y$/$\bigtriangleup x$,c是常量,与像素位置无关。

令$e_{i}$=$\bigtriangleup x$(d1-d2),则$e_{i}$的计算仅包括整数运算,符号与d1-d2一致,称为误差量参数,当它小于0时,直线更接近右方像素,大于0时直线更接近右上方像素。

可利用递增整数运算得到后继误差量参数,计算如下:

所以选择右上方像素时($y_{i+1}$-$y_{i}$=1):

选择右方像素时($y_{i+1}$-$y_{i}$=0):

初始时,将k=$\bigtriangleup y$/$\bigtriangleup x$代入$\bigtriangleup x$(d1-d2)中可得到起始像素的第一个参数:

斜率在0-1之间的Bresenham算法代码实现(替换上面程序中DDALine即可):

void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc)
{
int dx, dy, e, x=x0, y=y0;
dx = x1 - x0; dy = y1 - y0;
e = * dy - dx;
while (x<=x1)
{
SetPixel(hdc, x, ScreenHeight--y, RGB(, , ));
if (e >= )//选右上方像素
{
e = e + * dy - * dx;
y++;
}
else//选右方像素
{
e = e + * dy;
}
x++;
}
}

运行结果:

要实现任意方向的Bresenham算法也很容易,斜率在0-1之间意味着直线位于坐标系八象限中的第一象限,如果要绘制第二象限的直线,只需要利用这两个象限关于直线x=y对称的性质即可,可以先将x和y值互换先在第一象限进行计算,然后调用SetPixel时再将x和y值反过来,在第二象限中绘制,其他象限也是类似的思路。

绘制任意方向直线的Bresenham算法代码实现:

void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc)
{
int flag = ;
int dx = abs(x1 - x0);
int dy = abs(y1 - y0);
if (dx == && dy == ) return;
if (abs(x1 - x0) < abs(y1 - y0))
{
flag = ;
swap(x0, y0);
swap(x1, y1);
swap(dx, dy);
}
int tx = (x1 - x0) > ? : -;
int ty = (y1 - y0) > ? : -;
int x = x0;
int y = y0;
int dS = * dy; int dT = * (dy - dx);
int e = dS - dx;
SetPixel(hdc, x0, y0, RGB(,,));
while (x != x1)
{
if (e < )
e += dS;
else
{
y += ty; e += dT;
}
x += tx;
if (flag)
SetPixel(hdc, y, ScreenHeight - - x, RGB(, , ));
else
SetPixel(hdc, x, ScreenHeight - - y, RGB(, , ));
}
}

直线生成算法就到这里啦,接下来也要加油学习图形学~

 

图形学入门(1)——直线生成算法(DDA和Bresenham)的更多相关文章

  1. 【OpenGL学习】 四种绘制直线的算法

    我是用MFC框架进行测试的,由于本人也没有专门系统学习MFC框架,代码若有不足之处,请指出. 一,先来一个最简单的DDA算法 DDA算法全称为数值微分法,基于微分方程来绘制直线. ①推导微分方程如下: ...

  2. 计算机图形学之扫描转换直线-DDA,Bresenham,中点画线算法

    1.DDA算法 DDA(Digital Differential Analyer):数字微分法 DDA算法思想:增量思想 公式推导: 效率:采用了浮点加法和浮点显示是需要取整 代码: void lin ...

  3. Python使用DDA算法和中点Bresenham算法画直线

    title: "Python使用DDA算法和中点Bresenham算法画直线" date: 2018-06-11T19:28:02+08:00 tags: ["图形学&q ...

  4. [计算机图形学]光栅化算法:DDA和Bresenham算法

    目录 一.DDA 二.Bresenham 三.绘制图形 1. 绘制直线 2. 绘制圆 3. 绘制椭圆 一.DDA DDA算法是最简单的直线绘制算法.主要思想是利用直线的斜截式:\(y=kx+b\) 对 ...

  5. [计算机图形学] 基于C#窗口的Bresenham直线扫描算法、种子填充法、扫描线填充法模拟软件设计(二)

    上一节链接:http://www.cnblogs.com/zjutlitao/p/4116783.html 前言: 在上一节中我们已经大致介绍了该软件的是什么.可以干什么以及界面的大致样子.此外还详细 ...

  6. OpenGL编程(五)绘直线以及分析绘直线的算法

    这次主要实现在窗口上绘制点.线以及修改其属性,另外还会分析画直线的原理和相关算法. 1.在窗口指定位置画点 glBegin(GL_POINTS); glEnd(); 使用glBegin()和glEnd ...

  7. opengl实现直线扫描算法和区域填充算法

    总体介绍 1.   使用线性扫描算法画一条线,线性离散点 2.   利用区域填充算法画多边形区域,区域离散的点 开发环境VS2012+OpenGL 开发平台 Intel core i5,Intel H ...

  8. 美团技术分享:深度解密美团的分布式ID生成算法

    本文来自美团技术团队“照东”的分享,原题<Leaf——美团点评分布式ID生成系统>,收录时有勘误.修订并重新排版,感谢原作者的分享. 1.引言 鉴于IM系统中聊天消息ID生成算法和生成策略 ...

  9. Rxjs入门实践-各种排序算法排序过程的可视化展示

    Rxjs入门实践-各种排序算法排序过程的可视化展示 这几天学习下<算法>的排序章节,具体见对排序的总结,想着做点东西,能将各种排序算法的排序过程使用Rxjs通过可视化的方式展示出来,正好练 ...

随机推荐

  1. easyui--权限管理

    1.权限目的: 是为了让不同的用户可以操作系统中不同资源   直接点说就是不同的用户可以操作不同的菜单     核心:实现菜单权限的核心思想就是控制用户登录后台所传递的menuId(与树形菜单分类列段 ...

  2. 异常:ORA-01013: 用户请求取消当前的操作

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/bnmnba/article/detail ...

  3. 078_使用 egrep 过滤 MAC 地址

    #!/bin/bash#MAC 地址由 16 进制组成,如 AA:BB:CC:DD:EE:FF#[0-9a-fA-F]{2}表示一段十六进制数值,{5}表示连续出现 5 组前置:的十六进制egrep ...

  4. TCP四次握手断开连接

    建立连接非常重要,它是数据正确传输的前提:断开连接同样重要,它让计算机释放不再使用的资源.如果连接不能正常断开,不仅会造成数据传输错误,还会导致套接字不能关闭,持续占用资源,如果并发量高,服务器压力堪 ...

  5. 用Python实现自己下载音乐的统计

    今天看Python实例,学习了如何对文件进行操作,突然想把自己网易云音乐下载到本地的歌曲名单写到一个txt中,看看具体情况.当然,我现在肯定无法做到直接去网易云音乐上爬取,就做个最简单的吧,嘻嘻^-^ ...

  6. Java 8的Time包常用API

    Date.Canlender.SimpleDateFormat类在新的Time包面前几乎没有优势 日期LocalDate,时间LocalTime,日期时间LocalDateTime. 时区ZoneId ...

  7. Fidller抓包分析post请求

    目的:抓包是为了最近做接口测试做准备,以前没有用过这个工具,最近来学下,但是网上很多文章了,所以不一一记录,有一部分参考即可 1.如何抓取想要的web端或者手机端包,已经有很多文章谢了,推荐的参考文章 ...

  8. 使用Spring Ehcache二级缓存优化查询性能

    最近在对系统进行优化的时候,发现有些查询查询效率比较慢,耗时比较长, 通过压测发现,主要耗费的性能 消耗在 查询数据库,查询redis 数据库:连接池有限,且单个查询不能消耗大量的连接池,占用大量IO ...

  9. 【原】Python基础-循环语句

    x = 1while x <= 10: print(x) x += 1 password = ""while password != "3213554": ...

  10. Poseidon 系统是一个日志搜索平台——认证看链接ppt,本质是索引的倒排列表和原始日志数据都存在HDFS,而文档和倒排的元数据都在NOSQL里,同时针对单个filed都使用了独立索引,使用MR来索引和搜索

    Poseidon 系统是一个日志搜索平台,可以在百万亿条.100PB 大小的日志数据中快速分析和检索.360 公司是一个安全公司,在追踪 APT(高级持续威胁)事件,经常需要在海量的历史日志数据中检索 ...