簡單渲染流水管線C++代碼實現(五)---線段裁剪

線段裁剪是圖形學中一個非常重要的東西。
線段裁剪簡單來說,就是截取能在屏幕中顯示出來的部分,這樣做可以減少計算機在渲染過程中的計算量,加快渲染速度。
下面開始實現線段裁剪函數bool ClipLine(int* x1, int* y1, int* x2, int* y2)

1.劃分區域,對區域編碼

劃分區域是爲了更好的區分線段所在位置,爲裁剪做準備:
線段端點在中心不必處理,若在其他區域,便需要裁剪至中心範圍內
在這裏插入圖片描述
編碼由來參照下面的代碼

#define _C 0x00 //0000  中心
#define _N 0x01 //0001  北
#define _S 0x02 //0010	南
#define _W 0x04 //0100	西
#define _E 0x08 //1000	東
#define _EN (_E | _N) //1001	東北
#define _ES (_E | _S) //1010	東南
#define _WS (_W | _S) //0110	西南
#define _WN (_W | _N) //0101	西北

2.下面就是判斷兩點的所屬區域

	//緩存,取得兩點值,裁剪過程中不能修改原值
	int port1_x = *x1;
	int port1_y = *y1;
	int port2_x = *x2;
	int port2_y = *y2;

	//這裏的判斷隱藏了一個技巧,取<=  >=是可以避免下面判斷0值問題
	char port1 = _C;
	//判斷點1的位置
	if (port1_x <= 0)
		port1 |= _W;
	else if (port1_x >= _CLIENT_W)
		port1 |= _E;
	if (port1_y <= 0)
		port1 |= _N;
	else if (port1_y >= _CLIENT_H)
		port1 |= _S;

	char port2 = _C;
	//判斷點2的位置
	if (port2_x <= 0)
		port2 |= _W;
	else if (port2_x >= _CLIENT_W)
		port2 |= _E;
	if (port2_y <= 0)
		port2 |= _N;
	else if (port2_y >= _CLIENT_H)
		port2 |= _S;

3.進行初判斷

得到點1、點2的位置後,是可以直接去初次判斷某些無法裁剪的情況
比如:
在這裏插入圖片描述
(1)只要線段兩端點都在中心(0000)處,不需要裁剪,可以直接繪製
(2)當線段兩端點有共同區域時,是不可能繪製出來顯示到屏幕中的

	//兩點在中心,無需裁剪
	if (port1 == _C && port2 == _C)
		return false;
	//同時包含相同方向,比如東南和東北,都包括東,無需裁剪
	if ((port1 & port2) != 0)
		return false;

4.進行裁剪

(1)正方向(正北、正南、正東、正西)上的裁剪如下
在這裏插入圖片描述

case _W:
	{
		port1_x = 0;
		port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
		break;
	}

PS:上面的公式中後面+0.5的意義主要是爲了double轉int時四捨五入,如果不加0.5,編譯器會在轉換時向下取整,即1.9轉爲int時爲1。
上面舉了一個正西的例子,其他三個方向類似推導即可

case _E:
	{
		port1_x = _CLIENT_W - 1;
		port1_y = (int)(*y1 - (double)((*x1 - (_CLIENT_W - 1)) * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		break;
	}
	case _N:
	{
		port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
		port1_y = 0;
		break;
	}
	case _S:
	{
		port1_x = (int)(*x2 - (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y2)) / (*y1 - *y2) + 0.5);
		port1_y = _CLIENT_H - 1;
		break;
	}

(2)非正方向(東北、西北、東南、西南)上的裁剪
在這裏插入圖片描述
代碼如下:

case _WN:
	{
		//按西方向裁剪
		port1_x = 0;
		port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
		if (port1_y < 0)
		{
			//按北方向裁剪
			port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
			port1_y = 0;
		}
		break;
	}

其餘的非正方向同理

5.點位置裁剪完畢後需要判斷

不在屏幕內的會被剔除,這樣的判斷也能確定點2是否能被裁剪到屏幕內

	//判斷點1裁剪後的位置
	if (port1_x < 0 || port1_x > _CLIENT_W - 1 || port1_y < 0 || port1_y > _CLIENT_H - 1)
	{
		return false;
	}

6.點2的裁剪略過(類似點1),最後再處理一下

	//裁剪成功
	*x1 = port1_x;
	*y1 = port1_y;
	*x2 = port2_x;
	*y2 = port2_y;
	return true;

7.完整代碼+測試如下

#include <iostream>

#define _CLIENT_W 640
#define _CLIENT_H 480

#define _C 0x00 //00000000
#define _N 0x01 //00000001
#define _S 0x02 //00000010
#define _W 0x04 //00000100
#define _E 0x08 //00001000
#define _EN (_E | _N) //00001001
#define _ES (_E | _S) //00001010
#define _WS (_W | _S) //00000110
#define _WN (_W | _N) //00000101
bool ClipLine(int* x1, int* y1, int* x2, int* y2)
{
	//緩存,取得兩點值
	int port1_x = *x1;
	int port1_y = *y1;
	int port2_x = *x2;
	int port2_y = *y2;

	//這裏的判斷隱藏了一個技巧,取<=  >=是可以避免下面判斷0值問題
	char port1 = _C;
	//判斷點1的位置
	if (port1_x <= 0)
		port1 |= _W;
	else if (port1_x >= _CLIENT_W)
		port1 |= _E;
	if (port1_y <= 0)
		port1 |= _N;
	else if (port1_y >= _CLIENT_H)
		port1 |= _S;

	char port2 = _C;
	//判斷點2的位置
	if (port2_x <= 0)
		port2 |= _W;
	else if (port2_x >= _CLIENT_W)
		port2 |= _E;
	if (port2_y <= 0)
		port2 |= _N;
	else if (port2_y >= _CLIENT_H)
		port2 |= _S;

	//兩點在中心,無需裁剪
	if (port1 == _C && port2 == _C)
		return false;
	//同時包含相同方向,比如東南和東北,都包括東,無需裁剪
	if ((port1 & port2) != 0)
		return false;

	//裁剪時需使用相似三角形原理
	//裁剪點1
	switch (port1)
	{
	case _W:
	{
		port1_x = 0;
		port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
		break;
	}
	case _E:
	{
		port1_x = _CLIENT_W - 1;
		port1_y = (int)(*y1 - (double)((*x1 - (_CLIENT_W - 1)) * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		break;
	}
	case _N:
	{
		port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
		port1_y = 0;
		break;
	}
	case _S:
	{
		port1_x = (int)(*x2 - (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y2)) / (*y1 - *y2) + 0.5);
		port1_y = _CLIENT_H - 1;
		break;
	}
	case _WN:
	{
		port1_x = 0;
		port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
		if (port1_y < 0)
		{
			port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
			port1_y = 0;
		}
		break;
	}
	case _WS:
	{
		port1_x = 0;
		port1_y = (int)(*y2 + (double)(*x2 * (*y1 - *y2)) / (*x2 - *x1) + 0.5);
		if (port1_y > _CLIENT_H - 1)
		{
			port1_x = (int)(*x2 - (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y2)) / (*y1 - *y2) + 0.5);
			port1_y = _CLIENT_H - 1;
		}
		break;
	}
	case _EN:
	{
		port1_x = _CLIENT_W - 1;
		port1_y = (int)(*y1 - (double)((*x1 - (_CLIENT_W - 1)) * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		if (port1_y < 0)
		{
			port1_x = (int)(*x1 - *y1 * (double)(*x2 - *x1) / (*y2 - *y1) + 0.5);
			port1_y = 0;
		}
		break;
	}
	case _ES:
	{
		port1_x = _CLIENT_W - 1;
		port1_y = (int)(*y1 - (double)((*x1 - (_CLIENT_W - 1)) * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		if (port1_y > _CLIENT_H - 1)
		{
			port1_x = (int)(*x2 - (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y2)) / (*y1 - *y2) + 0.5);
			port1_y = _CLIENT_H - 1;
		}
		break;
	}
	}
	//判斷點1裁剪後的位置
	if (port1_x < 0 || port1_x > _CLIENT_W - 1 || port1_y < 0 || port1_y > _CLIENT_H - 1)
	{
		return false;
	}
	//剩下的情況點2裁剪後一定在Certain
	//裁剪點2
	switch (port2)
	{
	case _W:
	{
		port2_x = 0;
		port2_y = (int)(*y2 - (double)(*x2 * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		break;
	}
	case _E:
	{
		port2_x = _CLIENT_W - 1;
		port2_y = (int)(*y1 + (double)(((_CLIENT_W - 1) - *x1) * (*y2 - *y1)) / (*x2 - *x1) + 0.5);
		break;
	}
	case _N:
	{
		port2_x = (int)(*x2 - *y2 * (double)(*x1 - *x2) / (*y1 - *y2) + 0.5);
		port2_y = 0;
		break;
	}
	case _S:
	{
		port2_x = (int)(*x1 + (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y1)) / (*y2 - *y1) + 0.5);
		port2_y = _CLIENT_H - 1;
		break;
	}
	case _WN:
	{
		port2_x = 0;
		port2_y = (int)(*y2 - (double)(*x2 * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		if (port2_y < 0)
		{
			port2_x = (int)(*x2 - *y2 * (double)(*x1 - *x2) / (*y1 - *y2) + 0.5);
			port2_y = 0;
		}
		break;
	}
	case _WS:
	{
		port2_x = 0;
		port2_y = (int)(*y2 - (double)(*x2 * (*y1 - *y2)) / (*x1 - *x2) + 0.5);
		if (port2_y > _CLIENT_H - 1)
		{
			port2_x = (int)(*x1 + (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y1)) / (*y2 - *y1) + 0.5);
			port2_y = _CLIENT_H - 1;
		}
		break;
	}
	case _EN:
	{
		port2_x = _CLIENT_W - 1;
		port2_y = (int)(*y1 + (double)(((_CLIENT_W - 1) - *x1) * (*y2 - *y1)) / (*x2 - *x1) + 0.5);
		if (port2_y < 0)
		{
			port2_x = (int)(*x2 - *y2 * (double)(*x1 - *x2) / (*y1 - *y2) + 0.5);
			port2_y = 0;
		}
		break;
	}
	case _ES:
	{
		port2_x = _CLIENT_W - 1;
		port2_y = (int)(*y1 + (double)(((_CLIENT_W - 1) - *x1) * (*y2 - *y1)) / (*x2 - *x1) + 0.5);
		if (port2_y > _CLIENT_H - 1)
		{
			port2_x = (int)(*x1 + (double)((*x2 - *x1) * (_CLIENT_H - 1 - *y1)) / (*y2 - *y1) + 0.5);
			port2_y = _CLIENT_H - 1;
		}
		break;
	}
	}
	//裁剪成功
	*x1 = port1_x;
	*y1 = port1_y;
	*x2 = port2_x;
	*y2 = port2_y;
	return true;
}

void main()
{
	int x1 = -100;
	int y1 = -100;
	int x2 = 1000;
	int y2 = 500;
	std::cout << "裁剪前:" << std::endl;
	std::cout << x1 << "---" << y1 << std::endl;
	std::cout << x2 << "---" << y2 << std::endl;
	bool result = ClipLine(&x1, &y1, &x2, &y2);
	if (result)
	{
		std::cout << "裁剪後:" << std::endl;
		std::cout << x1 << "---" << y1 << std::endl;
		std::cout << x2 << "---" << y2 << std::endl;
	}
	else
	{
		std::cout << "線段不在視口中" << std::endl;
	}
	system("pause");
}

結果:
在這裏插入圖片描述

發佈了19 篇原創文章 · 獲贊 16 · 訪問量 1202
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章