圖形學初步--裁剪算法之Liang-Barsky算法

一、概念

裁剪是CG中許多重要問題的基礎,裁剪最典型的用途是確定場景中或畫面中位於給定區域之內的部分。由於在一個典型的場景之中,需要對大量的點、線段進行裁剪,因此裁剪算法的效率十分重要。

關於裁剪有一些很常見的算法,比如說Cohen-Sutherland線段細分裁剪算法、中點分割算法、Cyrus-Beck算法、Liang-Barsky算法。這篇文章重點講Liang-Barsky算法。剩下的那些算法有空再補。

二、Liang-Barsky算法

參考博客:

https://www.cnblogs.com/keguniang/p/9688126.html

https://blog.csdn.net/pleasecallmewhy/article/details/8393445

首先知道直線和裁剪框的位置關係,如下圖:

可以看到直線可能完全在裁剪框的外面,也可能完全在裁剪框的裏面,或者和裁剪框相交。

所以是裁剪的規則是這樣的:
 

如果直線的兩個端點都在窗口邊界之內,如ab,則直線保留;

如果直線的一個端點在窗口邊界之內,另一個端點在窗口邊界之外,如ef,則應從直線與邊界的交點處裁剪掉邊界之外的線段;

如果直線的兩個端點都在窗口邊界之外,有兩種情況:
    一種情況如ij,直線全部在窗口之外,應該全部裁減掉;
    另一種情況如gh,直線橫穿窗口邊界,應該保留相交的片段,裁減掉剩餘的片段;

那麼怎麼去判斷呢? 在講述判斷步驟之前我們要了解一個基礎理論,那就是編碼:

圖中陰影部分是裁剪框,我們把這個區域分成了9份,採用了四位數標識線段的端點位於九個區域的哪個區域位置,從右往左數:

第一個位置爲1-------如果線段端點位於窗口左側

第二個位置爲1------如果線段端點位於窗口右側

第三個位置爲1------如果線段端點爲窗口下面

第四個位置爲1------如果線段位於窗口上面

------------------------------------------------------------------接下來是算法推導-----------------------------------------------------------------------------

如下圖所示

1.我們用方程表示直線P1P2,其中t就是直線的斜率,t∈[0,1]:

 

裁剪區域內部可以表達爲兩個不等式:

把直線方程代入得到不等式:

 

 2.把直線看成是一條有方向的線段,把窗口的四條邊及其延長線分成兩類:入邊和出邊

入邊:左邊界和下邊界------從裁剪框外向裁剪框內

出邊:右邊界和上邊界------從裁剪框內向裁剪框外

3.分情況討論

①d=0,q<0,  說明直線與裁剪框平行,並且位於裁剪框的外面,直線爲不可見,可拋棄,直接結束

             q>=0,說明直線在它所平行的窗口邊界的內部,還需進一步計算確定直線是否在窗口內、外、或者相交

②d<0,說明直線是從裁剪邊界的外部延伸到內部

③d>0,   說明直線是從裁剪邊界的內部延伸到外部

對於d≠0,可以利用式子計算直線與邊界k的交點的參數u。對於每條直線,可以計算直線位於裁剪窗口內線段的參數d1和d2

      d1的值是由那些使得直線是從外部延伸到內部的窗口邊界決定。對於這些邊計算ri = qi/di.

      d1 = max(ri,0)

     d2的值是由那些使得直線是從內部延伸到窗口邊界決定

     d2 = min(ri,1)

      如果d1>d2,這條直線完全在窗口的外面,不可見,可拋棄,否則,根據參數u的兩個值,計算出裁剪後線段的端點

如果沒有看懂,可以看該博主的https://blog.csdn.net/pleasecallmewhy/article/details/8393445

所以僞代碼如下:

#Liang-Barsky two-dimensional clipping algorithm
#P1 and P2 are the line end points with components x1,y1,x2,y2
#tL and tU are the lower and upper parametric limits
#xL,xR,yB,yT are the left,right,bottom and top window edges
	function clipt(d,q,tL,tU):
		clipt performs trivial rejection tests and finds
		the max of the lower set of parameter values and 
		the min of the upper set of parameter values
		
		visible = true
		if d=0 and q<0 then #line is outside and parallel to edge
			visible = false
		else if d < 0 then #looking for upper limit
			t = q/d
			if t>tU then #check for trivial invisible
				visible = false
			else if t>tL then #find the min of the max
				tL = t
			end if
		else if d>0 then #look for the lower limit
			t = q/d
			if t<tL then #check for trivial invisible
				visible = false
			else if t<tU then #find the max of the min
				tU = t
			end if
			
		end if
		return visible
	end function
	
	start the main funciton:
		tL=0,tU=1
		delatx = x2-x1
		if clipt(-delatx,x1-xL,tL,tU) = true then #left edge
			if clipt(delatx,xR-x1,tL,tU) = true then #right edge
				deltay = y2-y1
				if clipt(-deltay,y1-yB,tL,tU) = true then #bottom edge
					if clipt(deltay,yT-y1,tL,tU) = true then #top edge
						if tU<1 then 
							x2 = x1+tU*delatx
							y2 = y1+tU*deltay
						end if
						if tL>0 then
							x1 = x1+tL*deltax
							y1 = y1+tL*deltay
						end if
						Draw P1,P2
					end if
				end if
			end if
		end if
	finish

最終實現的代碼如下:



#include <windows.h>
#include <GL/glut.h>
#include <math.h>
#include<stdio.h>
float xmin,xmax,ymin,ymax;


void myinit(void)
{
    glShadeModel (GL_FLAT);
    glClearColor (1.0, 1.0, 1.0, 0.0);
}


void myReshape(int w, int h)
{
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    if (w <= h) 
	gluOrtho2D (0.0, 1.0, 0.0, 1.0*(GLfloat)h/(GLfloat)w);
    else 
	gluOrtho2D (0.0, 1.0*(GLfloat)w/(GLfloat)h, 0.0, 1.0);
    glMatrixMode(GL_MODELVIEW);
}

int Clip(float p,float q,float *tL,float *tU) 
{  
	int flag=1;/*flag爲標誌變量0表示捨棄1表示可見*/ 
	float r; 

	if (p<0.0) 
	{ 
		r=q/p; 
		if (r>*tU)
			flag=0; 
		else if (r>*tL) {
			*tL = r;/*m取進入點最大參數值*/ 
		} 
	} 
	else if (p>0.0) { 
		r=q/p; 
		if (r<*tL)
			flag=0; 
		else if (r<*tU) {
			*tU = r;/*n取離開點的最小值*/ 
		} 
	} 
 
	else if (q<0 && p==0) //平行於邊界而且在界外的線 
		flag=0; 
	return flag; 
}
void myclip()
// line clipping algorithm 
{
	float dx, dy, x1,tL,tU, x2, y1, y2;
	tL = 0, tU = 1.0;
	printf("請輸入線段的兩個頂點座標x1,y1,x2,y2:\n");
	scanf("%f%f%f%f",&x1,&y1,&x2,&y2);

	glBegin(GL_LINES); 
	glColor4f (0.0, 0.0, 0.0, 0.0); 
	glVertex2f(x1, y1); // line startpoint 
	glVertex2f(x2, y2); // line endpoint 
	glEnd(); 

	dx=x2-x1; 
  
	if (Clip(-dx, x1 - xmin, &tL, &tU))
		if (Clip(dx, xmax - x1, &tL, &tU)){
			dy=y2-y1; 
			if (Clip(-dy, y1 - ymin, &tL, &tU))
				if (Clip(dy, ymax - y1, &tL, &tU))
				{ 
					if (tU<1.0) 
					{ 
						x2 = x1 + tU*dx;//通過n求得裁剪後的p2端點 
						y2 = y1 + tU*dy;
					} 
					if (tL>0.0)
					{ 
						x1 = x1 + tL*dx;//通過m求得裁剪後的p1端點 
						y1 = y1 + tL*dy;
					} 
					glBegin(GL_LINES); 
					glColor4f (1.0, 0.0, 0.0, 1.0); 
					glVertex2f( x1, y1); // clipped line startpoint 
					glVertex2f( x2, y2); // clipped line endpoint 
					glEnd();
				} 
		} 

}

void display(void)
 {
    glClear(GL_COLOR_BUFFER_BIT);

	printf("請分別輸入矩形的左右下上邊界:\n");
	scanf("%f%f%f%f",&xmin,&xmax,&ymin,&ymax);
    glColor4f (1.0, 1.0, 0.0, 0.75);
	glBegin(GL_POLYGON);
	glVertex2f( xmin, ymin); // Bottom Left
	glVertex2f( xmax, ymin); // Bottom Left
	glVertex2f( xmax, ymax); // Bottom Right
	glVertex2f( xmin, ymax); // Bottom Right
	glEnd();

   myclip();
    glFlush();
}


/*  Main Loop
 *  Open window with initial window size, title bar, 
 *  RGBA display mode, and handle input events.
 */
int main(int argc, char** argv)
{
   glutInit(&argc, argv);
   glutInitDisplayMode (GLUT_SINGLE | GLUT_RGBA);
   //define size and the relative positon of the applicaiton window on the display
   glutInitWindowSize (500, 500); 
   glutInitWindowPosition (100, 100);
 	//init the defined window with "argv[1]" as topic showed on the top the window
   glutCreateWindow (argv[0]);
	// opengl setup
   myinit ();

	//define callbacks
   glutDisplayFunc(display); 
   glutReshapeFunc(myReshape);
   //enter the loop for display
   glutMainLoop();

	return 1;
}

在運行該代碼之前需要配置opengl環境,若沒有配置,請參考該博客:https://blog.csdn.net/keneyr/article/details/83626935

代碼運行結果如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章