【OpenGL学习】 四种绘制直线的算法
我是用MFC框架进行测试的,由于本人也没有专门系统学习MFC框架,代码若有不足之处,请指出。
一,先来一个最简单的DDA算法
DDA算法全称为数值微分法,基于微分方程来绘制直线。
①推导微分方程如下:
②,dM时间步长的倒数的详解:
可以看到
当|k|<=1时
dx=1或者-1,此时的x为计长方向
当|k|>1时
dy=1或者-1,此时的y为计长方向
绘制时需要用dM来控制绘制的点数
③绘制像素的问题:
为了“方便”管理算法,我为不同的绘制函数新建了一个类了。。。(其实可以写到一个类里面。。。。)
④代码实现:
MyDDA.cpp
1 #include "stdafx.h" 2 #include "MyDDA.h" 3 4 5 MyDDA::MyDDA() 6 { 7 } 8 9 10 MyDDA::~MyDDA() 11 { 12 } 13 14 void MyDDA::SetDc(CDC * dc) 15 { 16 this->pdc = dc; 17 } 18 19 void MyDDA::Moveline(CPoint start, CPoint end) 20 { 21 int x1, x2; 22 int y1, y2; 23 x1 = start.x; 24 x2 = end.x; 25 y1 = start.y; 26 y2 = end.y; 27 float dm = 0; 28 if (abs(x2 - x1) >= abs(y2 - y1)) 29 dm = abs(x2 - x1); 30 else 31 dm = abs(y2 - y1); 32 float dx = (float)(x2 - x1) / dm; 33 float dy = (float)(y2 - y1) / dm; 34 float x = x1 + 0.5; 35 float y = y1 + 0.5; 36 int i = 0; 37 while (i<dm) { 38 this->pdc->SetPixel((int)x, (int)y, RGB(0, 0, 0)); 39 x += dx; 40 y += dy; 41 i += 1; 42 } 43 44 }
总结:
其实这个算法还算是挺简单的,就是要确定是X还算Y为计长方向,需要注意的是循环过程中计长方向自增1,另一个方向按照当直线在计长方向自增1时,直线在轴上的投影的大小自增就可以了。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
二,逐点比较法
逐点比较法是在画线的过程中,每画一点就与理想直线比较,以决定下一点的走向,以一步步逼近的方法点亮最接近直线的一组像素。
①绘制前先将直线平移
使y坐标较小的点位于坐标原点(绘制的时候再平移回去,跟几何变换一个道理)
如图:
②推导过程:
先来谈一下直线的偏移值怎么表示
可以看到:
如果d是正值说明当前绘制点在绘制直线的上方。
反之,在下方。
③由于每次计算都需要计算两次乘法,计算工作量大,所以可以利用递推公式来,借助当前点的偏移值,获取下一个点的偏移值。
递推公式推导过程如下图所示:
④终点判断:
⑤整理下我们在编程时需要的参数:
⑥实现代码:
注释部分的代码比较容易理解一点,但是太长了,注释下面的代码是简化的版本,两者的作用是一样的。
PtoP.cpp
1 void PtoP::Moveline(CPoint start, CPoint end) 2 { 3 int xA, yA; 4 if (start.y > end.y) { 5 xA = start.x - end.x; 6 yA = start.y - end.y; 7 } 8 else 9 { 10 xA = end.x - start.x; 11 yA = end.y - start.y; 12 } 13 14 int n = abs(xA) + abs(yA); 15 16 int x = 0, y = 0, F = 0; 17 /* 18 if (xA > 0)//1 19 { 20 for (int i = 0; i < n; i++) 21 { 22 if (F >= 0) 23 { 24 x += 1; 25 F -= yA; 26 if(start.y>end.y) 27 this->pdc->SetPixel(x+end.x,y+end.y, RGB(0, 0, 0)); 28 else 29 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0)); 30 } 31 else 32 { 33 y += 1; 34 F += xA; 35 if (start.y > end.y) 36 this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0)); 37 else 38 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0)); 39 } 40 } 41 42 } 43 else//2 44 { 45 for (int i = 0; i < n; i++) 46 { 47 if (F >= 0) 48 { 49 y += 1; 50 F += xA; 51 if (start.y > end.y) 52 this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0)); 53 else 54 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0)); 55 } 56 else 57 { 58 x-= 1; 59 F += yA; 60 if (start.y > end.y) 61 this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0)); 62 else 63 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0)); 64 } 65 66 } 67 68 } 69 */ 70 for (int i = 0; i < n; i++) { 71 if (xA > 0) { 72 if (F >= 0) 73 { 74 x++; 75 F -= yA; 76 } 77 else 78 { 79 y++; 80 F += xA; 81 } 82 } 83 else { 84 if (F >= 0) 85 { 86 y++; 87 F += xA; 88 } 89 else 90 { 91 x--; 92 F += yA; 93 } 94 } 95 if (start.y > end.y) 96 this->pdc->SetPixel(x + end.x, y + end.y, RGB(0, 0, 0)); 97 else 98 this->pdc->SetPixel(x + start.x, y + start.y, RGB(0, 0, 0)); 99 } 100 }
⑦总结:
逐点比较法绘制最重要的是递推公式的推导,以及避免原偏移值公式的无理运算。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
三,Bresenham画线法
①引入
Bresenham画线算法是应用最广泛的直线生成算法,它采用加减以及乘2运算(即移位运算)来实现。
首先先从直线斜率为0<=k<=1的八分之一象限来开始讨论。
如图:
②如何判别选取点S还是点T
③迭代公式
④起点的判别值
⑤其他情况的判别
⑥整理所需变量
⑦代码实现
Bresenham.cpp
1 #include "stdafx.h" 2 #include "Bresenham.h" 3 4 5 Bresenham::Bresenham() 6 { 7 } 8 9 10 Bresenham::~Bresenham() 11 { 12 } 13 14 void Bresenham::SetDc(CDC * dc) 15 { 16 this->pdc = dc; 17 } 18 19 void Bresenham::Moveline(CPoint start, CPoint end) 20 { 21 int x1=start.x, y1=start.y; 22 int x2=end.x, y2=end.y; 23 24 this->pdc->SetPixel(start.x,start.y,RGB(0,0,0)); 25 int dx, dy; 26 dx = abs(x2-x1); 27 dy = abs(y2-y1); 28 int flag=0; 29 if (dx == 0 && dy == 0) 30 return; 31 if (dy > dx) 32 { 33 flag = 1; 34 swap_value(x1,y1); 35 swap_value(x2,y2); 36 swap_value(dx,dy); 37 } 38 int tx = (x2 - x1) > 0 ? 1 : -1; 39 int ty = (y2 - y1) > 0 ? 1 : -1; 40 int curx = x1 + 1; 41 int cury = y1; 42 int dS = 2 * dy; 43 int dT = 2 * (dy-dx); 44 int d =dS-dx; 45 while (curx != x2) 46 { 47 if (d >= 0) { 48 d += dT; 49 cury += ty; 50 } 51 else 52 { 53 d += dS; 54 } 55 if (flag) 56 this->pdc->SetPixel(cury,curx,RGB(0,0,255)); 57 else 58 this->pdc->SetPixel(curx, cury, RGB(0, 0, 255)); 59 curx+=tx; 60 } 61 } 62 63 void Bresenham::swap_value(int & a, int & b) 64 { 65 /* 66 a ^= b; 67 b ^= a; 68 a ^= b; 69 */ 70 int temp = a; 71 a = b; 72 b = temp; 73 74 }
总结:Bresenham算法关键点还是在于迭代公式的推导,以及如何选择下一个点,判断x轴和y轴的步长是自增还是自减。
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
四,中点画线法
中点画线法的思路跟Bresenham算法的思路极为相似,在学习完之后,我就觉得两者的算法不同之处就是取点的判断标准。
①构造判别式
②递推公式推导
③其他斜率情形
④整理变量
⑤代码实现
1 #include "stdafx.h" 2 #include "MidpointLine.h" 3 4 5 MidpointLine::MidpointLine() 6 { 7 } 8 9 10 MidpointLine::~MidpointLine() 11 { 12 } 13 14 void MidpointLine::SetDc(CDC * dc) 15 { 16 this->pdc = dc; 17 } 18 19 void MidpointLine::Moveline(CPoint start, CPoint end) 20 { 21 int x0 = start.x, x1 = end.x, y0 = start.y, y1 = end.y; 22 int a, b, d2, x, y, flag = 0; 23 if (abs(x1 - x0) < abs(y1 - y0)) 24 { 25 swap_value(x0,y0); 26 swap_value(x1, y1); 27 flag = 1; 28 } 29 if (x0 > x1) {//保证x0<x1,方便判别斜率 30 swap_value(x0, x1); 31 swap_value(y0, y1); 32 } 33 a = y0 - y1; 34 b = x1 - x0; 35 d2 = 2*a + b;//摆脱小数点,提高效率 36 if (y0 < y1) {//k>0 37 x = x0; y = y0; 38 this->pdc->SetPixel(x,y,RGB(0,0,0)); 39 while (x < x1) 40 { 41 if (d2 < 0) 42 { 43 x++; 44 y++; 45 d2 =d2+ 2*a + 2*b; 46 } 47 else { 48 x++; 49 d2 =d2+ 2 * a; 50 } 51 52 if(flag)//|k|>1 53 this->pdc->SetPixel(y, x, RGB(0, 0, 0)); 54 else 55 this->pdc->SetPixel(x, y, RGB(0, 0, 0)); 56 } 57 } 58 else {//k<0 59 x = x1; 60 y = y1; 61 this->pdc->SetPixel(x, y, RGB(0, 0, 0)); 62 while (x >x0) 63 { 64 if (d2 < 0) 65 { 66 x--; 67 y++; 68 d2 = d2-2 * a + 2 * b; 69 } 70 else { 71 x--; 72 d2 =d2- 2 * a; 73 } 74 75 if (flag)//|k|>1 76 this->pdc->SetPixel(y, x, RGB(0, 0, 0)); 77 else 78 this->pdc->SetPixel(x, y, RGB(0, 0, 0)); 79 } 80 } 81 } 82 83 void MidpointLine::swap_value(int & a, int & b) 84 { 85 /* 86 a ^= b; 87 b ^= a; 88 a ^= b; 89 */ 90 int temp = a; 91 a = b; 92 b = temp; 93 }
总结:
判断的准则不同也导致函数的写法跟之前Bresenham函数有很大的差别,最明显的就是中点判断法需要保证绘制直线的走向,以方便判断直线的斜率,而Bresenham算法则不需要这一点,它只需要知道tx,ty就能知道直线的走向,对于起点,终点在哪里。
并没有太大的约束。
最后来一张四种算法同时绘制直线的合照吧。。。。
最左边是DDA,依次是逐点比较法,Bresenham,中点画线法。
该工程的github链接:https://github.com/Thousandyearsofwar/DrawLine
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)