計算機圖形學常用算法實現11 掃描線z-buffer算法

圖形學作業要到deadline了,趕緊寫一個
這個算法比之前的算法的工作量都要大,但是隻要思路清晰,也不是很難。
1.創建各種需要的數據結構類

//點的類
class Point
{
public:
	float x;
	float y;
	float z;
	Point();
	~Point();
};
//y表
class YTable
{
public:
	int m_IndexOfPolygon;
	float m_Ymax;
	YTable * next;
	YTable();
	YTable(int IndexOfPolygon,float Ymax);
	~YTable();
};
//邊表
class ET
{
public:
	float m_Ymax;
	float m_Yminx;
	float m_Yminz;
	float m_DeltaX;
	ET *next;
	ET();
	~ET();
};
//活性邊對錶
class AET
{
public:
	float m_XL;
	float m_DeltaXL;
	float m_Lmax;
	float m_XR;
	float m_DeltaXR;
	float m_Rmax;
	float m_ZL;
	int m_PolygonIndex;
	float m_DeltaZA;
	float m_DettaZB;
	AET *next;
	AET();
	~AET();
};

2.定義我們的掃描線算法的主類

class ScanLine
{
public:
	Point pt[MAX_POINT];//存儲所有點的信息
	int polygon[MAX_POINT][3];//我們的圖形是由三角形組成的,保存三角形的頂點編號
	int currentPoint;//當前的點的數量
	int currentPolygon;//當前三角形的數量
	int m_Ymin;//最大的y值
	int m_Ymax;//最小的y值
	int m_Map[MAX_WIDTH][MAX_LENGTH];//幀緩存
	int m_ZB[MAX_WIDTH];//深度緩存
	Point color[MAX_POINT];
	ScanLine();
	~ScanLine();
	YTable *m_YTable[MAX_WIDTH];//多邊形y表
	YTable *m_APT;//活化多邊形表
	ET *m_ET[MAX_POINT][MAX_LENGTH];//邊表
	AET *m_AET;//活化邊對錶
	void Draw();
	void InitData();//初始化信息,在這裏讀取我們的點和多邊形
	void LoadObj();

private:
	void CreatYTable();//創建y表
	void FindYminAndYmax();//設置m_Ymin和m_Ymax的值
	void CalculateNormal(int cntOfPolygon,float normal[]);
};

3.接下來就是按順序執行我們的算法流程,首先讀取obj文件到點和多邊形裏面
這裏我的obj的讀法比較簡單,只考慮了點和麪,發現和紋理等我都沒有考慮。
值得注意的是,我們需要的座標範圍是1024*768
因此,當obj文件中的文件座標過於小或者存在負的時候,需要自行對座標進行方法以及平移處理
下面是放大100倍的情況,用於繪製後面的球,完整代碼是不放大的情況,用來繪製海豚

if (cnt == 0)
	pt[currentPoint].x = (atof(strTemp))*100+1024/2;
else if (cnt == 1)
	pt[currentPoint].y = (atof(strTemp))*100+768/2;
void ScanLine::LoadObj()
{
	FILE *fp;
	int index;
	printf("請輸入繪製的圖形序號1:茶壺 2:海豚 3:球 4:花環\n");
	scanf("%d", &index);
	if(index == 1)
		fp = fopen("teapot.obj","r");
	else if(index ==2)
		fp = fopen("dolphins.obj", "r");
	else if (index == 3)
		fp = fopen("sphere.obj", "r");
	else if (index == 4)
		fp = fopen("torus.obj", "r");
	char str[100];
	char strTemp[100];
	int cnt;
	int indexOfstrTemp;
	if (!fp)
		printf("ERROR");
	else
	{
		while (fgets(str, 999, fp) != NULL)
		{
			cnt = 0;
			indexOfstrTemp = 0;
			if (str[0] == 'v'&&str[1]!='n'&&str[1]!='t')
			{
				int i = 2;
				if (str[i] == ' ')
					i = 3;
				for (; ;i++)
					if (str[i] == ' '|| str[i]=='\0')
					{
						strTemp[indexOfstrTemp] = '\0';
						indexOfstrTemp = 0;
						if (cnt == 0)
							pt[currentPoint].x = (atof(strTemp))+1024/2;
						else if (cnt == 1)
							pt[currentPoint].y = (atof(strTemp))+768/2;
						else
						{
							pt[currentPoint].z = atof(strTemp);
							currentPoint++;
						}
						cnt = (cnt + 1) % 3;		
						if (str[i] == '\0')
							break;
					}
					else
					{
						strTemp[indexOfstrTemp++] = str[i];
					}
			}
			else if (str[0] == 'f')
			{
				for (int i = 0; i < strlen(str); i++)
					if (str[i] == ' ')
						cnt++;
				if (cnt == 3)
				{
					cnt = 0;
					for (int i = 2; ; i++)
						if (str[i] == ' ' || str[i] == '\0'||str[i]=='\\')
						{
							if(str[i]=='\\')
								for (; str[i] != '\0'&&str[i] != ' '; i++);
							strTemp[indexOfstrTemp] = '\0';
							indexOfstrTemp = 0;
							polygon[currentPolygon][cnt] = atof(strTemp)-1;
							if (cnt == 2)
								currentPolygon++;
							cnt = (cnt + 1) % 3;
							if (str[i] == '\0')
								break;
						}
						else
						{
							strTemp[indexOfstrTemp++] = str[i];
						}
				}
				else if (cnt == 4)
				{
					cnt = 0;
					for (int i = 2; ; i++)
						if (str[i] == ' ' || str[i] == '\0')
						{
							strTemp[indexOfstrTemp] = '\0';
							indexOfstrTemp = 0;
							if (cnt == 3)
							{
								polygon[currentPolygon][0] = polygon[currentPolygon-1][0];
								polygon[currentPolygon][1] = polygon[currentPolygon-1][2];
								polygon[currentPolygon][2] = atof(strTemp)-1;
								currentPolygon++;
							}
							polygon[currentPolygon][cnt] = atof(strTemp)-1;
							if (cnt == 2)
								currentPolygon++;
							cnt++;
							if (str[i] == '\0')
								break;
						}
						else
						{
							strTemp[indexOfstrTemp++] = str[i];
						}
				}
			}
		}
	}
}

4.給我們的三角形隨機顏色

void ScanLine::InitData()
{
	srand((int)time(NULL));
	for (int i = 0; i < currentPolygon; i++)
	{
		color[i].x = rand() % 1024 / 1024.0;
		color[i].y = rand() % 1024 / 1024.0;
		color[i].z = rand() % 1024 / 1024.0;
	}
}

5.開始繪圖函數
這裏要初始化y表,然後找到最大和最小的y值

void ScanLine::CreatYTable()
{
	float ymin = 99999;
	float ymax = 0;
	for (int i = 0; i < currentPolygon; i++)
	{
		//找到最低的頂點座標
		ymin = 99999;
		ymax = 0; 
		for (int j = 0; j < 3; j++)
		{
			if (ymin > pt[polygon[i][j]].y)
				ymin = pt[polygon[i][j]].y;
			if (ymax < pt[polygon[i][j]].y)
				ymax = pt[polygon[i][j]].y;
		}
		//插入Y表
		int y = ceil(ymin);
		//如果在首位的話,直接插入
		if (m_YTable[y] == nullptr)
		{
			m_YTable[y] = new YTable();
			m_YTable[y]->m_IndexOfPolygon = i;
			m_YTable[y]->m_Ymax = ymax;
			m_YTable[y]->next = nullptr;
		}
		//不在首位,則需要找到最後一位,然後插入到那個位置
		else
		{
			YTable *YTableTemp = m_YTable[y];
			while (YTableTemp->next != nullptr)
				YTableTemp = YTableTemp->next;
			YTable *YTableTemp1 = new YTable();
			YTableTemp1->m_IndexOfPolygon = i;
			YTableTemp1->m_Ymax = ymax;
			YTableTemp1->next = nullptr;
			YTableTemp->next = YTableTemp1;
		}
	}
}
void ScanLine::FindYminAndYmax()
{
	m_Ymax = 0;
	m_Ymin = 999999;
	for (int i = 0; i < currentPoint; i++)
	{
		if (m_Ymax < pt[i].y)
			m_Ymax = ceil(pt[i].y);
		if (m_Ymin > pt[i].y)
			m_Ymin = ceil(pt[i].y);
	}
}

6.下面就是完整的繪圖函數
基本上就是按照以下步驟來的
創建y表
找到y的最大值最小值
初始化幀緩存
循環每一條掃描線
初始化深度緩存
新建APT,ET,AET
遍歷AET繪圖
刪除到達頂點的APT,AET
更新AET

void ScanLine::Draw()
{
	//建立Y表
	CreatYTable();
	//找到最大的y和最小的y值,減小掃描線的數量
	FindYminAndYmax();
	//初始化背景顏色
	memset(m_Map, 0, sizeof(m_Map));
	//遍歷所有的掃描線
	for (int i = m_Ymin; i <= m_Ymax; i++)
	{
		//初始化深度緩存
		fill(m_ZB, m_ZB + MAX_WIDTH, -99999);
		//如果y表中有掃描線i對應的掃描線,則將其加入到APT中
		if (m_YTable[i] != nullptr)
		{
			YTable *headOfYtable = m_YTable[i];
			YTable *headOfAPT = m_APT;
			if(headOfAPT!=nullptr)
				while (headOfAPT->next != nullptr)
					headOfAPT = headOfAPT->next;
			//將y表中多邊形依次插入到APT中,併爲其建立邊表
			while (headOfYtable != nullptr)
			{
				YTable *YTableTemp = new YTable(headOfYtable->m_IndexOfPolygon, headOfYtable->m_Ymax);
				YTableTemp->next = nullptr;
				if (m_APT == nullptr)
				{
					m_APT = YTableTemp;
					headOfAPT = m_APT;
				}
				else
				{
					headOfAPT->next = YTableTemp;
					headOfAPT = headOfAPT->next;
				}
				//對於剛加入的多邊形,生成其邊表
				for (int j = 0; j < 3; j++)
				{
					//三角形,三條邊進行遍歷
					if (ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y) < ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j+1)%3]].y))
					{
						ET *newET = new ET();
						newET->next = nullptr;
						newET->m_Ymax = pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y;
						newET->m_Yminx = pt[polygon[headOfYtable->m_IndexOfPolygon][j]].x;
						newET->m_Yminz = pt[polygon[headOfYtable->m_IndexOfPolygon][j]].z;
						newET->m_DeltaX = (pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].x - pt[polygon[headOfYtable->m_IndexOfPolygon][j]].x) / ceil((pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y) - ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y));
						if(m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y)]==nullptr)
							m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y)] = newET;
						else
							m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y)]->next = newET;					
					}
					else if(ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y) > ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y))
					{
						ET *headOfET = m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y)];
						ET *newET = new ET();
						newET->next = nullptr;
						newET->m_Ymax = pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y;
						newET->m_Yminx = pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].x;
						newET->m_Yminz = pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].z;
						newET->m_DeltaX = (pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].x - pt[polygon[headOfYtable->m_IndexOfPolygon][j]].x) / ceil((pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y) - ceil(pt[polygon[headOfYtable->m_IndexOfPolygon][j]].y));
						if (m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y)] == nullptr)
							m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y)] = newET;
						else
							m_ET[headOfYtable->m_IndexOfPolygon][(int)ceil(pt[polygon[(headOfYtable->m_IndexOfPolygon)][(j + 1) % 3]].y)]->next = newET;
					}
				}
				headOfYtable = headOfYtable->next;
			}
			//遍歷APT中的多邊形,將其邊表中對應的邊對加入活化邊對錶AET
			headOfAPT = m_APT;
			while (headOfAPT != nullptr)
			{
				if (m_ET[headOfAPT->m_IndexOfPolygon][i] != nullptr)
				{
					//因爲我們只考慮三角形,多邊形沒考慮,這裏只需要考慮一個邊和兩個邊的時候
					if (m_ET[headOfAPT->m_IndexOfPolygon][i]->next != nullptr)
					{
						ET edge1;
						ET edge2;
						AET *newAET = new AET();
						//確定左邊的邊是哪一個
						edge1 = *m_ET[headOfAPT->m_IndexOfPolygon][i];
						edge2 = *m_ET[headOfAPT->m_IndexOfPolygon][i]->next;
						if (edge1.m_Yminx > edge2.m_Yminx)
						{
							ET temp;
							temp = edge1;
							edge1 = edge2;
							edge2 = temp;
						}
						else if(edge1.m_DeltaX > edge2.m_DeltaX)
						{
							ET temp;
							temp = edge1;
							edge1 = edge2;
							edge2 = temp;
						}
						newAET->m_DeltaXL = edge1.m_DeltaX;
						newAET->m_DeltaXR = edge2.m_DeltaX;
						newAET->m_XL = edge1.m_Yminx;
						newAET->m_XR = edge2.m_Yminx;
						newAET->m_ZL = edge1.m_Yminz;
						newAET->m_Lmax = edge1.m_Ymax;
						newAET->m_Rmax = edge2.m_Ymax;
						newAET->m_PolygonIndex = headOfAPT->m_IndexOfPolygon;
						newAET->next = nullptr;
						//計算出多邊形的法向量
						float normal[3];
						CalculateNormal(newAET->m_PolygonIndex, normal);
						newAET->m_DeltaZA = -normal[0] / normal[2];
						newAET->m_DettaZB = -normal[1] / normal[2];
						//插入aet
						if (m_AET == nullptr)
							m_AET = newAET;
						else
						{
							AET *headOfAET = m_AET;
							while (headOfAET->next != nullptr)
								headOfAET = headOfAET->next;
							headOfAET->next = newAET;
						}
					}
				}
				headOfAPT = headOfAPT->next;
			}
		}
		//遍歷AET畫圖
		AET *headOfAET = m_AET;
		while (headOfAET != nullptr)
		{
			if(headOfAET->m_XL>=0&&headOfAET->m_XL<=1024&& headOfAET->m_XR>=0&& headOfAET->m_XR<=1024)
				for (int j = ceil(headOfAET->m_XL); j <= ceil(headOfAET->m_XR) && j<=1024; j++)
				{
					if (headOfAET->m_ZL + headOfAET->m_DeltaZA * (j - ceil(headOfAET->m_XL)) > m_ZB[j])
					{
						m_ZB[j] = headOfAET->m_ZL + headOfAET->m_DeltaZA * (j - ceil(headOfAET->m_XL));
						m_Map[j][i] = headOfAET->m_PolygonIndex+1;
					}
				}
			headOfAET = headOfAET->next;
		}
		//刪除APT中最大頂點y座標爲i的多邊形
		YTable *headOfAPT = m_APT;
		//先刪除是首元素的情況
		while (headOfAPT != nullptr &&headOfAPT->m_Ymax <= i)
		{
			m_APT = headOfAPT->next;
			free(headOfAPT);
			headOfAPT = m_APT;
		}
		while (headOfAPT!=nullptr&&headOfAPT->next != nullptr)
		{
			//如果到達頂點,則刪除這個多邊形
			if (headOfAPT->next->m_Ymax <= i)
			{
				YTable *next = headOfAPT->next;
				headOfAPT->next = next->next;
				free(next);
			}
			else
				headOfAPT = headOfAPT->next;
		}
		//更新AET中的每一條邊對
		//刪除邊對中到達ymax的邊
		headOfAET = m_AET;
		//先刪除是首元素的情況
		while (headOfAET != nullptr && headOfAET->m_Lmax <= i &&headOfAET->m_Rmax <= i)
		{
			m_AET = headOfAET->next;
			free(headOfAET);
			headOfAET = m_AET;
		}
		//刪除後面到達頂點的
		while (headOfAET!=nullptr&&headOfAET->next != nullptr)
		{
			if (headOfAET->next->m_Lmax <= i && headOfAET->next->m_Rmax <= i)
			{
				AET *next = headOfAET->next;
				headOfAET->next = next->next;
				free(next);
			}
			else
				headOfAET = headOfAET->next;
		}
		headOfAET = m_AET;
		//更新後面的值
		while (headOfAET != nullptr)
		{
			//只刪除一條邊,則需要補一條邊
			if (headOfAET->m_Lmax <= i)
			{
				if (m_ET[headOfAET->m_PolygonIndex][i] != nullptr)
				{
					headOfAET->m_Lmax = m_ET[headOfAET->m_PolygonIndex][i]->m_Ymax;
					headOfAET->m_DeltaXL = m_ET[headOfAET->m_PolygonIndex][i]->m_DeltaX;
					headOfAET->m_ZL = m_ET[headOfAET->m_PolygonIndex][i]->m_Yminz;
					headOfAET->m_XL = m_ET[headOfAET->m_PolygonIndex][i]->m_Yminx;
				}
			}
			else if (headOfAET->m_Rmax <= i)
			{
				if (m_ET[headOfAET->m_PolygonIndex][i] != nullptr)
				{
					headOfAET->m_Rmax = m_ET[headOfAET->m_PolygonIndex][i]->m_Ymax;
					headOfAET->m_DeltaXR = m_ET[headOfAET->m_PolygonIndex][i]->m_DeltaX;
					headOfAET->m_XR = m_ET[headOfAET->m_PolygonIndex][i]->m_Yminx;
				}
			}
			//更新
			headOfAET->m_XL += headOfAET->m_DeltaXL;
			headOfAET->m_XR += headOfAET->m_DeltaXR;
			headOfAET->m_ZL += headOfAET->m_DeltaXL*headOfAET->m_DeltaZA + headOfAET->m_DettaZB;
			headOfAET = headOfAET->next;
		}	
	}
}

7.主函數

#include <GLTools.h>
#include <GLMatrixStack.h>  
#include <GLFrame.h>  
#include <GLFrustum.h>  
#include <GLBatch.h>  
#include <GLGeometryTransform.h>
#define FREEGLUT_STATIC
#include <glut.h>
#pragma comment(lib,"gltools.lib")
#include "ScanLine.h"
ScanLine scanline;
void DisPlayTest()
{
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);//設置混合模式
	glEnable(GL_BLEND);//開啓混合
	glEnable(GL_POINT_SMOOTH);//點的抗鋸齒
	glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);//最好模式運行平滑 可選GL_FASTEST

	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glBegin(GL_POINTS);
	for (int i = 0; i<1024; i++)
		for (int j = 0; j < 768; j++)
		{
			if (scanline.m_Map[i][j] != 0)
			{
				glColor3f(scanline.color[scanline.m_Map[i][j]-1].x,scanline.color[scanline.m_Map[i][j] - 1].y,scanline.color[scanline.m_Map[i][j] - 1].z);
				glVertex2f(i/ 1024.0*2 -1,j/ 768.0*2 -1);
			}
		}
	glEnd();
	glFlush();
}

int main(int argc, char* argv[])
{
	scanline.LoadObj();
	scanline.InitData();
	scanline.Draw();

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);
	glutInitWindowSize(1024, 768);
	glutCreateWindow("GL_POINTS");
	glutDisplayFunc(DisPlayTest);
	glutMainLoop();
	return 0;
}

效果圖如下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

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