1. 程式人生 > 程式設計 >opengl實現直線掃描演算法和區域填充演算法

opengl實現直線掃描演算法和區域填充演算法

本文例項為大家分享了opengl實現直線掃描演算法和區域填充演算法,供大家參考,具體內容如下

總體介紹

1、採用直線掃描演算法繪製一條線段,直線由離散點組成

2、利用區域填充演算法繪製多邊形區域,區域由離散點組成

開發環境VS2012+OpenGL

開發平臺 Intel core i5,Intel HD Graphics Family

設計思路

一、直線掃描演算法

1、數值微分法(DDA)

已知過端點P0 (x0,y0),P1(x1,y1)的直線段L:y = kx + b,容易得知直線斜率為:k = (y1-y0)/(x1-x0),(假設x1≠x0)。

我們假設|k|≤1,這樣x每增加1,y將增加k,並且保證x每增加1,y的增量不能大於1;如果|k|> 1,則應該將x和y互換。由於k是浮點數,因此演算法中需要將y舍入為int型,並圓整到最接近的位置。

DDA演算法在每次迭代中的x,y值是上一步的值加上一個增量獲得的,因此它是一個增量演算法。但是這種方法直觀,但效率太低,因為每一步需要一次浮點乘法和一次舍入運算。

2、中點畫線法

在直線斜率在0~1直接的情況下,設當前畫素點為(x,y),那麼它的下一個畫素點就是p1(x+1,y)或者p2(x+1,y+1),若稱p1和p2的中點M(px+1,y+0.5),Q為理想直線與x+1垂線的交點,當Q在M的下方時,p1即為下一個畫素點,否則p2即為下一個畫素點。

3、Bresenham演算法

過各行各列象素中心構造一組虛擬網格線。按直線從起點到終點的順序計算直線與各垂直網格線的交點,然後確定該列象素中與此交點最近的象素。該演算法的巧妙之處在於採用增量計算,使得對於每一列,只要檢查一個誤差項的符號,就可以確定該列的所求象素。

opengl實現直線掃描演算法和區域填充演算法

如圖所示,設直線方程為yi+1=yi+k(xi+1-xi)+k。假設列座標象素已經確定為xi,其行座標為yi。那麼下一個象素的列座標為xi+1,而行座標要麼為yi,要麼遞增1為yi+1。是否增1取決於誤差項d的值。誤差項d的初值d0=0,x座標每增加1,d的值相應遞增直線的斜率值k,即d=d+k。一旦 d≥1,就把它減去1,這樣保證d在0、1之間。當d≥0.5時,直線與垂線x=xi+1交點最接近於當前象素(xi,yi)的右上方象素(xi+1,yi+1);而當d<0.5時,更接近於右方象素(xi+1,yi)。為方便計算,令e=d-0.5,e的初值為-0.5,增量為k。當e≥0時,取當前象素(xi,yi)的右上方象素(xi+1,yi+1);而當e<0時,取(xi,yi)右方象素(xi+1,yi)。

二、區域填充演算法

1、遞迴演算法

從指定的種子點開始,向各個方向上搜索,逐個畫素進行處理,直到遇到邊界,各種種子填充演算法只是在處理顏色和邊界的方式上有所不同。

2、掃描線演算法

掃描線種子填充演算法的基本過程如下:當給定種子點(x,y)時,首先分別向左和向右兩個方向填充種子點所在掃描線上的位於給定區域的一個區段,同時記下這個區段的範圍[xLeft,xRight],然後確定與這一區段相連通的上、下兩條掃描線上位於給定區域內的區段,並依次儲存下來。反覆這個過程,直到填充結束。

掃描線種子填充演算法可由下列四個步驟實現:

(1) 初始化一個空的棧用於存放種子點,將種子點(x,y)入棧;

(2) 判斷棧是否為空,如果棧為空則結束演算法,否則取出棧頂元素作為當前掃描線的種子點(x,y),y是當前的掃描線;

(3) 從種子點(x,y)出發,沿當前掃描線向左、右兩個方向填充,直到邊界。分別標記區段的左、右端點座標為xLeft和xRight;

(4) 分別檢查與當前掃描線相鄰的y - 1和y + 1兩條掃描線在區間[xLeft,xRight]中的畫素,從xLeft開始向xRight方向搜尋,若存在非邊界且未填充的畫素點,則找出這些相鄰的畫素點中最右邊的一個,並將其作為種子點壓入棧中,然後返回第(2)步;

三、演算法實現

opengl實現直線掃描演算法和區域填充演算法

opengl實現直線掃描演算法和區域填充演算法

opengl實現直線掃描演算法和區域填充演算法

總結及學習感悟

在學習直線掃描演算法時,一開始總是畫不出來,後來發現這句glBegin(GL_POINTS);少了個S,沒有S就只能畫一個點,細節很重要。

學習區域填充演算法時,基本的思路就是以一個點為起點,不斷探索周圍,如果在這個區域內,就填充顏色,如果遇到邊界就停止。掃描線演算法也是,先以某點畫一條直線,在區域內的線段部分就填充顏色。

我們就像被選中的一點一樣,周圍的一切對我們來說都是不可知的黑色,只有不斷探索,才知道哪裡是邊界,也可能或許沒有邊界,或許邊界的那邊又是一個更大的新世界······噗,我想多了。

原始碼

掃描線主要演算法

void k1() //0<k<1 
{ 
 glClear(GL_COLOR_BUFFER_BIT); 
 
 glColor3f(0.0,0.0,0.0); 
 glBegin(GL_POINTS); 
 GLint x1=0,y1=0,x2=400,y2=200; 
 GLint x=x1,y=y1; 
 GLint dx=x2-x1,dy=y2-y1,dT=2*(dy-dx),dS=2*dy; 
 GLint d=2*dy-dx; 
 glVertex2i(x,y); 
 while(x<x2) 
 { 
 x++; 
 if(d<0) 
 d=d+dS; 
 else 
 { 
 y++; 
 d=d+dT; 
 } 
 glVertex2i(x,y); 
 } 
 glEnd(); 
 glFlush(); 
 
}

區域填充
#include "gl/glut.h"
#include "windows.h"
const int POINTNUM=7; //多邊形點數.
 
/******定義結構體用於活性邊表AET和新邊表NET***********************************/
 typedef struct XET
 {
 float x;
 float dx,ymax;
 XET* next;
 }AET,NET;
 
/******定義點結構體point******************************************************/
 struct point
 {
 float x;
 float y;
 }
 polypoint[POINTNUM]={250,50,550,150,400,250,100,350,120,30};//多邊形頂點
 
 void PolyScan()
{
/******計算最高點的y座標(掃描到此結束)****************************************/
 int MaxY=0;
 int i;
 for(i=0;i<POINTNUM;i++)
 if(polypoint[i].y>MaxY)
 MaxY=polypoint[i].y;
 
/*******初始化AET表***********************************************************/
 AET *pAET=new AET;
 pAET->next=NULL;
 
/******初始化NET表************************************************************/
 NET *pNET[1024];
 for(i=0;i<=MaxY;i++)
 {
 pNET[i]=new NET;
 pNET[i]->next=NULL;
 }
 glClear(GL_COLOR_BUFFER_BIT); //賦值的視窗顯示. 
 glColor3f(0.0,0.0);  //設定直線的顏色紅色
 glBegin(GL_POINTS);
/******掃描並建立NET表*********************************************************/
 for(i=0;i<=MaxY;i++)
 {
 for(int j=0;j<POINTNUM;j++)
 if(polypoint[j].y==i)
 { //一個點跟前面的一個點形成一條線段,跟後面的點也形成線段 
 if(polypoint[(j-1+POINTNUM)%POINTNUM].y>polypoint[j].y)
 {
 NET *p=new NET;
 p->x=polypoint[j].x;
 p->ymax=polypoint[(j-1+POINTNUM)%POINTNUM].y;
 p->dx=(polypoint[(j-1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j-1+POINTNUM)%POINTNUM].y-polypoint[j].y);
 p->next=pNET[i]->next;
 pNET[i]->next=p;
 
 }
 if(polypoint[(j+1+POINTNUM)%POINTNUM].y>polypoint[j].y)
 {
 NET *p=new NET; 
 p->x=polypoint[j].x;
 p->ymax=polypoint[(j+1+POINTNUM)%POINTNUM].y;
 p->dx=(polypoint[(j+1+POINTNUM)%POINTNUM].x-polypoint[j].x)/(polypoint[(j+1+POINTNUM)%POINTNUM].y-polypoint[j].y);
 p->next=pNET[i]->next;
 pNET[i]->next=p;
 }
 }
 }
/******建立並更新活性邊表AET*****************************************************/
for(i=0;i<=MaxY;i++)
 {
 //計算新的交點x,更新AET
 NET *p=pAET->next;
 while(p)
 {
 p->x=p->x + p->dx;
 p=p->next;
 }
 //更新後新AET先排序*************************************************************/
 //斷表排序,不再開闢空間
 AET *tq=pAET;
 p=pAET->next;
 tq->next=NULL;
 while(p)
 {
 while(tq->next && p->x >= tq->next->x)
 tq=tq->next;
 NET *s=p->next;
 p->next=tq->next;
 tq->next=p;
 p=s;
 tq=pAET;
 }
 //(改進演算法)先從AET表中刪除ymax==i的結點****************************************/
 AET *q=pAET;
 p=q->next;
 while(p)
 {
 if(p->ymax==i)
 {
 q->next=p->next;
 delete p;
 p=q->next;
 }
 else
 {
 q=q->next;
 p=q->next;
 }
 }
 //將NET中的新點加入AET,並用插入法按X值遞增排序**********************************/
 p=pNET[i]->next;
 q=pAET;
 while(p)
 {
 while(q->next && p->x >= q->next->x)
 q=q->next;
 NET *s=p->next;
 p->next=q->next;
 q->next=p;
 p=s;
 q=pAET;
 }
/******配對填充顏色***************************************************************/
 
  p=pAET->next;
 while(p && p->next)
 {
 for(float j=p->x;j<=p->next->x;j++)
 glVertex2i(static_cast<int>(j),i);
 p=p->next->next;//考慮端點情況
 }
 
 
 }
 glEnd();
glFlush(); 
}
void init(void)
{glClearColor(1.0,1.0,0.0);
//視窗的背景顏色設定為白色
glMatrixMode(GL_PROJECTION);
gluOrtho2D(0.0,600.0,450.0);
}
 
void main(int argc,char* argv)
{
 glutInit(&argc,&argv);  //I初始化 GLUT.
 glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB); //設定顯示模式:單個快取和使用RGB模型
 glutInitWindowPosition(50,100); //設定視窗的頂部和左邊位置
 glutInitWindowSize(400,300); //設定視窗的高度和寬度
 glutCreateWindow("An Example OpenGL Program"); //建立顯示視窗
 
 init();    //呼叫初始化過程
 glutDisplayFunc(PolyScan); //圖形的定義傳遞給我window.
 glutMainLoop();   //顯示所有的圖形並等待
 }

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支援我們。