扫描线zbuffer消隐算法

本片blog阐述了图形学中扫描线缓冲器消隐算法的原理和C++实现。 

       题外话:上学期上过FJQ老师的课,首先为老师的认真态度点赞,不但课件做得好,课也上得好,讲课条理清晰,难易适中,是我上过的10来门课中讲得最好,留下印象最深的。

 一、为了阐述zbuffer的算法思路,首先会引用冯老师的课件。算法原理如下:

以上图片的顺序是从左到右,从上到下。

二、数据结构说明

首先准备需要的数据结构:

1.分类多边形的边表结点:

typedef struct nodeClassifiedPolygon{
	GLdouble a, b, c, d;//多边形系数
	GLint id;//多边形编号
	GLint dy;//跨越的扫描线数目
	Triple<GLubyte> color;//多边形颜色
	nodeClassifiedPolygon* next;//同一扫描线上的下一个多边形
}nodeClassifiedPolygon;

2.分类边表的边表结点:

typedef struct nodeClassifiedEdge{
	GLdouble x;//边上端点的x坐标
	GLdouble dx;//扫描线向下移动一次时x的增量
	GLint dy;//边跨越的扫描线数目
	GLint id;//边所在所变形的编号
	bool used;//该边是否已经处理过
	nodeClassifiedEdge* next;//具有相同x的下一条边
}nodeClassifiedEdge;

3.活化多边形链表结点:(结构与nodeClassifiedEdge相同):

typedef struct nodeActivePolygon{
	GLdouble a, b, c, d; 
	GLint id;
	GLint dy;
	Triple<GLubyte> color;
	nodeActivePolygon* next;
}nodeActivePolygon;

4.活化边对链表结点:

typedef struct nodeActiveEdgePair{
	GLdouble xl;//当前扫描线边对的左临界点
	GLdouble dxl;//相邻扫描线交点x坐标之差
	GLint dyl;//靠左的边跨越的扫描线数目
	GLdouble xr; //当前扫描线边对的右临界点
	GLdouble dxr;//扫描线向下移动一次时右边的xr的增量
	GLint dyr;//靠右的边跨越的扫描线数目
	GLdouble zl;//当前扫描线下左边的深度
	GLdouble dzx;//向右扫描一个像素时,深度的增量
	GLdouble dzy;//向下移动扫描线时深度的增量
	GLint id;//边对所在多边形的编号
	Triple<GLubyte> color;//颜色
	nodeActiveEdgePair* next;//下一边对
}nodeActiveEdgePair;
</pre><span style="font-family:Microsoft YaHei;font-size:18px;"></span><p><span style="white-space:pre">	</span><span style="font-family:Microsoft YaHei;font-size:18px;">5.分类边表的边表vector:</span></p><p><pre name="code" class="cpp">vector<nodeClassifiedPolygon*> tClassifiedPolygon;

6.分类多边形的边表vector

vector<nodeClassifiedEdge*> tClassifiedEdge;

7.活化多边形链表的头指针,该指针的next指向第一个节点:

nodeActivePolygon tActivePolygonHead;

8.活化边对链表的头指针,该指针的next指向第一个节点:

nodeActiveEdgePair tActiveEdgePairHead;

9.使用了如下额外的数据结构:

typedef struct{
	GLint x,y;
	GLdouble z;
} Point;

这个结构的作用是在读取obj模型时,模型的三维定点坐标都是浮点数,而帧缓存的像素坐标是整数,但是z坐标,即深度值仍然可以是浮点,因此在消隐的时候将会把一个xyz坐标都是浮点的顶点转化为Point结构,其中,转化的结果是在xOy面上取最近点。

三、编码实现

1.首先定义一个三元组模板类,这在顶点坐标和颜色值都会用到:

#pragma once
template <typename T>
class Triple
{
public:
	T x, y, z;
	Triple(T _x, T _y, T _z):x(_x), y(_y), z(_z){}
	Triple(T v[3]):x(v[0]), y(v[1]), z(v[2]){}
	Triple(){}
	~Triple(void){}

	void operator = (const Triple<T>& a){
		x = a.x;
		y = a.y;
		z = a.z;
	}

	Triple<T> operator + (const Triple<T>& a/*, Point b*/){
		return Triple<T>(x+a.x, y+a.y, z+a.z);
	}

	Triple<T> operator + (GLdouble a/*, Point b*/){
		return Triple<T>(x+a, y+a, z+a);
	}

	Triple<T> operator * (GLdouble a/*, Point b*/){
		return Triple<T>(x*a, y*a, z*a);
	}

	Triple<T> operator - (const Triple<T>& a/*, Point b*/){
		return Triple<T>(x-a.x, y-a.y, z-a.z);
	}
	bool operator == (const Triple<T>& a/*, Point b*/){
		return (x==a.x&&y==a.y&&z==a.z);
	}
	bool operator != (const Triple<T>& a/*, Point b*/){
		return !((*this)==a);
	}

};


2.定义帧缓存类:

#include "Triple.h"
class FrameBuffer{
public:

	FrameBuffer():m_width(0),m_height(0)/*,m_centerX(0),m_centerY(0),m_center(0)*/{}

	//----更新高度和宽度,并重新设置其大小
	void ResizeBuffer(int width,int height){
		if(m_width!=width || m_height!=height){
			m_width = width;
			m_height = height;
			m_buffer.clear();
			m_buffer.resize(m_width * m_height * 3);	// 帧缓存大小
			//m_centerX = m_width >> 1;
			//m_centerY = m_height >> 1;
			//m_center = m_width * m_centerY + m_centerX;
		}
	}

	//---初使化帧缓存----
	void Memset(int value){
		memset(&m_buffer[0],value,m_buffer.size());
	}

	//---返回要写入的帧缓存位置-----
	void SetPixel(int x,int y,Triple<GLubyte>& color){
		if(x<0 || x>=m_width || y <0 || y>=m_height) return;
		*(&m_buffer[0]+(m_width*y+x)*3) = color.x;
		*(&m_buffer[0]+(m_width*y+x)*3+1) = color.y;
		*(&m_buffer[0]+(m_width*y+x)*3+2) = color.z;
		//std::cout<<"x:"<<x<<" y:"<<y<<std::endl;
		
			//memcpy(&m_buffer[m_center]+(m_width*y+x)*3, &color[0], 3);//debugger
		//}
	}

	Triple<GLubyte>* getPixelAddr(int x,int y){
		if(y>=0 && y<m_height && x>=0 && x<m_width)
			return ((Triple<GLubyte>*)(&m_buffer[0]+(m_width*y+x)*3));
		std::cout<<"getPixelAddr error"<<std::endl;
		return NULL;
	}

	int getBufferWidth(){
		return m_width;
	}
	int getBufferHeight(){
		return m_height;
	}
public:
	std::vector<GLubyte> m_buffer;	// 帧缓存
	int m_width,m_height;	// 宽度/高度
	//int m_centerX, m_centerY, m_center;
};


3.定义Obj类,这个类有点复杂,先上代码再解释:

#pragma once
#include <fstream>
#include <string>
#include <Windows.h>
#include <windef.h>
#include <io.h>
#include "Triple.h"
#include "Util.h"
class Obj
{
public:
	std::vector<Triple<GLdouble>> vVertex;
	//std::vector<Triple<GLdouble>> vVertexErr;//误差修正
	//GLdouble m_errAccuracy;//误差修正精度
	GLdouble m_offsetX, m_offsetY, m_offset;
	GLdouble m_scaleFactor;
	std::vector<std::vector<Triple<GLdouble>>> vfs;//面
	std::vector<std::vector<GLint>> ifaces;
	//vector<vector<string>> model;
	std::string objpath;
	GLint nVertex;//顶点数
	GLint nFace;//面数
	GLint m_winWidth, m_winHeight;
public:
	Obj(){
		m_winWidth = 1600,m_winHeight = 900;
		string moduleDir = getModueDir();
		vector<string> files;
		//cout<<"moduleDir:"<<moduleDir<<endl;
		getFiles(moduleDir, files);
		int item = getItem(files);
		objpath = files[item-1];
		loadObjFile();
		vfs.resize(nFace);
	}
	~Obj(){}

	int getItem(vector<string>& files){
		//cout<<"size:"<<files.size()<<endl;
		for(int i = 0; i <files.size(); i++)
			cout<<i+1<<"."<<files[i]<<endl;
		cout<<"chose one to perform:";
		int j;
		cin>>j;
		while(j<=0||j>files.size()){
			cout<<"error,again:";
			cin>>j;
		}
		return j;
	}
	void getFiles(string path, vector<string>& files ){
		//文件句柄
		long   hFile   =   0;
		//文件信息
		struct _finddata_t fileinfo;
		string p;
		if((hFile = _findfirst(p.assign(path).append("\\*").c_str(),&fileinfo)) !=  -1)
		{
			do
			{
				//如果是目录,迭代之
				//如果不是,加入列表
				if((fileinfo.attrib &  _A_SUBDIR))
				{
					if(strcmp(fileinfo.name,".") != 0  &&  strcmp(fileinfo.name,"..") != 0)
						getFiles( p.assign(path).append("\\").append(fileinfo.name), files );
				}
				else
				{
					files.push_back(p.assign(path).append("\\").append(fileinfo.name) );
				}
			}while(_findnext(hFile, &fileinfo)  == 0);
			_findclose(hFile);
		}
	}
	string getModueDir(){
		string exe = getEXEDir();
		int pos = exe.find_last_of("\\\\");
		pos = exe.substr(0,pos).find_last_of("\\\\");
		if(pos > 0){
			return exe.substr(0,pos) + "\\\\model";
		}
		return "";
	}
	string getEXEDir(){
		 TCHAR exeFullPath[MAX_PATH]; // MAX_PATH在WINDEF.h中定义了,等于260
		 memset(exeFullPath,0,MAX_PATH);

		 GetModuleFileName(NULL,exeFullPath,MAX_PATH);
		 return string(exeFullPath);
	}

	void loadObjFile(){
		/*第一遍扫描记录xy坐标的最小值和最大值,有如下两个作用:
		1.如果这两个值跨度太小,说明模型太小,为了显示模型,需要对坐标值进行放大
		2.对原始坐标进行偏移,使得模型基本可以完全展示出来*/
		GLdouble min_x = 999999.99, max_x = -999999.99;
		GLdouble min_y = 999999.99, max_y = -999999.99;
		Util util;
		std::ifstream ifs(objpath);
		std::string line;
		vVertex.push_back(Triple<GLdouble>(0,0,0));
		while(getline(ifs,line)){			
			vector<string> vline;
			//model[i].clear();
			util.split(vline, line);
			if(vline.size() == 0) continue;
			if("v" == vline[0]){
				//_ASSERT(vline.size() > 3);
				std::string s;
				GLdouble vertex[3];
				GLdouble d;//当前读取的坐标值
				for(int i = 0; i < 3; i++){
					s = vline[i+1];
					sscanf_s(s.c_str(), "%lf", &vertex[i]);
				}
				if(vertex[0] < min_x) min_x = vertex[0];
				if(vertex[0] > max_x) max_x = vertex[0];
				if(vertex[1] < min_y) min_y = vertex[1];
				if(vertex[1] > max_y) max_y = vertex[1];
				vVertex.push_back(Triple<GLdouble>(vertex));
				//vVertexErr.push_back(Triple<GLdouble>(0.0,0.0,0.0));
			}else if("f" == vline[0]){
				//_ASSERT(vline.size() > 3);
				std::vector<GLint> ele;
				int d;
				std::string s;
				for(GLuint i = 0; i < 4 && (i+1) < vline.size(); i++){
					s = vline[i+1];
					//丢弃材质等信息
					int slash_pos = s.find_first_of('/');
					if(slash_pos >= 0) s = s.substr(0, slash_pos);
					//...不支持负的顶点索引
					sscanf_s(s.c_str(), "%d", &d);
					ele.push_back(d);
				}
				ifaces.push_back(ele);
			}
		}
		//offset

		//m_offset = m_offsetX > m_offsetY ? m_offsetX : m_offsetY;

		//scale
		GLdouble spanx = max_x - min_x, spany = max_y - min_y;
		//GLdouble span = (spanx<spany?spanx:spany);
		//m_scaleFactor = (m_winHeight*100) / span / span;
		if(spanx <= 1 || spany <= 1) m_scaleFactor = 1000;
		else if(spanx < 5 || spany < 5) m_scaleFactor = 80;
		else if(spanx < 10 || spany < 10) m_scaleFactor = 40;
		else if(spanx < 20 || spany < 20) m_scaleFactor = 20;
		else if(spanx < 40 || spany < 40) m_scaleFactor = 10;
		else m_scaleFactor = 1;
		m_offsetX = (m_winWidth >> 1) - ((max_x + min_x)*m_scaleFactor / 2);//abs(min_x) + 0.5;
		m_offsetY = (m_winHeight >> 1) - ((max_y + min_y)*m_scaleFactor / 2);

		nVertex = vVertex.size() - 1;
		nFace = ifaces.size();
		//model.clear();
		ifs.close();
	}
	
	Obj& getObj(){
		translateObj(Triple<GLdouble>(0,0,0));
		return *this;
	};


	void Matrix_Multip(GLdouble *A_matrix,GLdouble *B_matrix,GLdouble *AB_matrix,
		int A_Rows,int A_Colunms,int B_Rows,int B_Colunms)
	{
		//矩阵乘法 double*AB_matrix最后的结果
		for (int i=0;i<A_Rows;i++)
		{
			for (int j=0;j<B_Colunms;j++)
			{
				AB_matrix[i*B_Colunms+j]=0.0;
				for (int k=0;k<A_Colunms;k++)
				{
					AB_matrix[i*B_Colunms+j]=AB_matrix[i*B_Colunms+j]+A_matrix[i*A_Colunms+k]*B_matrix[k*B_Colunms+j];
				}   
			}
		}
	}

	Obj& rotateObj(GLdouble angle, Triple<GLdouble>& p/*1, Triple<GLdouble>& p2*/){
		GLdouble a = 1.0;//rotVec.x / modRotateAxis;
		GLdouble b = 1.0;//rotVec.y / modRotateAxis;
		GLdouble c = 1.0;//rotVec.z / modRotateAxis;
		//GLdouble d = sqrt(b*b+c*c);
		GLdouble theta = 3.1415926536*angle/180;
		GLdouble sintheta = sin(theta);
		GLdouble costheta = cos(theta);
		GLdouble transMat[4][4] = {
			1.0,0.0,0.0,m_offsetX,
			0.0,1.0,0.0,m_offsetY,
			0.0,0.0,1.0,0.0,
			0.0,0.0,0.0,1.0
		};
		GLdouble scaleMat[4][4] = {
			m_scaleFactor,0.0,0.0,0.0,
			0.0,m_scaleFactor,0.0,0.0,
			0.0,0.0,m_scaleFactor,0.0,
			0.0,0.0,0.0,1.0
		};
		GLdouble rotateMat[4][4] = {
			(1-costheta)*a*a+costheta,	(1-costheta)*b*a-sintheta*c,		(1-costheta)*a*c+sintheta*b,0,
			(1-costheta)*a*b+sintheta*c,(1-costheta)*b*b+costheta,		(1-costheta)*b*c-sintheta*a,0,
			(1-costheta)*a*c-sintheta*b,(1-costheta)*b*c+sintheta*a,		(1-costheta)*c*c+costheta,	0,
			0,							0,								0,							1
		};
		GLdouble* vertices = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);//减1是由于顶点数组第一个顶点是占位元素
		for(int i = 0; i < nVertex; i++){
			*(vertices + i) = vVertex[i+1].x/* + vVertexErr[i+1].x*/;
			*(vertices + i + nVertex) = vVertex[i+1].y/* + vVertexErr[i+1].y*/;
			*(vertices + i + (nVertex<<1)) = vVertex[i+1].z/* + vVertexErr[i+1].z*/;
			*(vertices + i + ((nVertex<<1)+nVertex)) = 1.0;
		}

		
		GLdouble* res = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);
		//GLdouble* res2 = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);
		//先旋转
		Matrix_Multip(reinterpret_cast<GLdouble*>(rotateMat), vertices, res, 4, 4, 4, nVertex);
		//原始数据保存
		for(int i = 0; i < nVertex; i++){
			vVertex[i+1].x = *(res + i)/* + (*(res + i)>=0?0.5:-0.5)*/;//大于0向上取整
			vVertex[i+1].y = *(res + i + nVertex)/* + (*(res + i + nVertex)>=0?0.5:-0.5)*/;
			vVertex[i+1].z = *(res + i + (nVertex<<1))/* + (*(res + i + (nVertex<<1))>=0?0.5:-0.5)*/;
		}
		//再缩放
		Matrix_Multip(reinterpret_cast<GLdouble*>(scaleMat), res, vertices, 4, 4, 4, nVertex);
		//平移至屏幕中心
		Matrix_Multip(reinterpret_cast<GLdouble*>(transMat), vertices, res, 4, 4, 4, nVertex);

		for(GLuint i = 0; i < nFace; i++){
			vfs[i].clear();
			for(GLuint j = 0; j < ifaces[i].size(); j++)
				vfs[i].push_back(Triple<GLdouble>(*(res + ifaces[i][j]-1),
				*(res + ifaces[i][j]-1 + nVertex),
				*(res + ifaces[i][j]-1 + (nVertex<<1))));
		}

		free(vertices);
		free(res);
		//free(res2);
		return *this;
	}

	Obj& translateObj(Triple<GLdouble>& p = Triple<GLdouble>(0,0,0)){
		//GLdouble transX,transY;
		m_offsetX = m_offsetX + p.x;
		m_offsetY = m_offsetY + p.y;
		GLdouble transMat[4][4] = {
			1.0,0.0,0.0,m_offsetX,
			0.0,1.0,0.0,m_offsetY,
			0.0,0.0,1.0,0,
			0.0,0.0,0,1.0
		};
		GLdouble scaleMat[4][4] = {
			m_scaleFactor,0.0,0.0,0.0,
			0.0,m_scaleFactor,0.0,0.0,
			0.0,0.0,m_scaleFactor,0.0,
			0.0,0.0,0.0,1.0
		};

		GLdouble* vertices = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);
		for(int i = 0; i < nVertex; i++){
			*(vertices + i) = vVertex[i+1].x;
			*(vertices + i + nVertex) = vVertex[i+1].y;
			*(vertices + i + (nVertex<<1)) = vVertex[i+1].z;
			*(vertices + i + ((nVertex<<1)+nVertex)) = 1.0;
		}
		GLdouble* res = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);

		//先缩放
		Matrix_Multip(reinterpret_cast<GLdouble*>(scaleMat), vertices, res, 4, 4, 4, nVertex);
		//再平移
		Matrix_Multip(reinterpret_cast<GLdouble*>(transMat), res, vertices, 4, 4, 4, nVertex);
		//vfs.clear();
		vfs.resize(nFace);
		for(GLuint i = 0; i < nFace; i++){
			vfs[i].clear();
			for(GLuint j = 0; j < ifaces[i].size(); j++)
				vfs[i].push_back(Triple<GLdouble>(*(vertices + ifaces[i][j]-1),//x
				*(vertices + ifaces[i][j]-1 + nVertex),//y
				*(vertices + ifaces[i][j]-1 + (nVertex<<1))));//z
		}
		
		free(vertices);
		free(res);
		return *this;
	}

	Obj& scaleObj(Triple<GLdouble>& p1, Triple<GLdouble>& p2, bool smaller){
		//double _scaleFactor = 0.0;
		if(smaller) m_scaleFactor = m_winHeight/abs(p1.y-p2.y + m_winHeight);
		else m_scaleFactor = abs(p1.y-p2.y + m_winHeight)/m_winHeight;
		GLdouble scaleMat[4][4] = {
			m_scaleFactor,0,0,0,
			0,m_scaleFactor,0,0,
			0,0,m_scaleFactor,0,
			0,0,0,1
		};
		GLdouble transMat[4][4] = {
			1.0,0.0,0.0,m_offsetX,
			0.0,1.0,0.0,m_offsetY,
			0.0,0.0,1.0,0.0,
			0.0,0.0,0.0,1.0
		};
		GLdouble* vertices = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);
		for(int i = 0; i < nVertex; i++){
			*(vertices + i) = vVertex[i+1].x + 0.0;
			*(vertices + i + nVertex) = vVertex[i+1].y + 0.0;
			*(vertices + i + (nVertex<<1)) = vVertex[i+1].z + 0.0;
			*(vertices + i + ((nVertex<<1)+nVertex)) = 1.0;
		}
		GLdouble* res = (GLdouble*)malloc(sizeof(GLdouble)*4*nVertex);
		//放缩
		Matrix_Multip(reinterpret_cast<GLdouble*>(scaleMat), vertices, res, 4, 4, 4, nVertex);
		//保存原始数据
		for(int i = 0; i < nVertex; i++){
			vVertex[i+1].x = *(res + i);
			vVertex[i+1].y = *(res + i + nVertex);
			vVertex[i+1].z = *(res + i + (nVertex<<1));
		}
		//平移
		Matrix_Multip(reinterpret_cast<GLdouble*>(transMat), res, vertices, 4, 4, 4, nVertex);
		for(GLuint i = 0; i < nFace; i++){
			vfs[i].clear();
			for(GLuint j = 0; j < ifaces[i].size(); j++)
				vfs[i].push_back(Triple<GLdouble>(*(vertices + ifaces[i][j]-1),
				*(vertices + ifaces[i][j]-1 + nVertex),
				*(vertices + ifaces[i][j]-1 + (nVertex<<1))));
		}
		
		free(vertices);
		free(res);
		return *this;
	}
};

构造Obj类时首先需要读入模型文件,并将读入的顶点和表示面的顶点索引分别保存在nVertex和ifaces中。另外,在读入模型的时候需要确定模型的放缩倍数和平移量,以便在屏幕合适的位置用合适的大小将模型的消隐结果展示出来。

模型需要完成3个功能:平移,旋转和缩放。这三个功能都是为了能够更好地观察结果。

旋转方法rotateObj的参数是旋转角度和旋转轴。在整个工程中,我都用最原始的数据来进行计算,每一次旋转和缩放后都会保存对原始数据的操作结果,平移不会保存,这样是为了将变换产生的精度损失降到最低。
 

4.显示回调的主体框架如下:

void display(void)
{
    <span style="white-space:pre">	</span>glClear(GL_COLOR_BUFFER_BIT/* | GL_DEPTH_BUFFER_BIT*/);
	//glDrawBuffer(GL_FRONT_AND_BACK);
	glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	frameBuffer.Memset(g_bgColor.x);

	getFrameBuffer();

	glRasterPos2i(0,0);
	glDrawPixels(frameBuffer.getBufferWidth(), frameBuffer.getBufferHeight(), 
	GL_RGB, GL_UNSIGNED_BYTE, frameBuffer.getPixelAddr(0,0));
	glutSwapBuffers();

	displayInfo();
}

getFrameBuffer的主要任务就是用zbuffer消隐来写帧缓存对象frameBuffer,写完后再draw一下就perfect。displayInfo是在控制台上显示一些运行信息用的。

5.getFrameBuffer

void getFrameBuffer(){
	vector<face> vface;
	g_polygon_id = 0;
	if(bRotate){
		vface = obj.getObj().rotateObj(1,Triple<GLdouble>(1,1,1)).vfs;		
	}
	else
		vface = obj.getObj().vfs;
	constructDS(vface);
	zbuffer();
	clearData();
}

先根据是否需要旋转来获得各个面的顶点数据,然后constructDS构建消隐需要的tClassifiedPolygon、tClassifiedEdge。消隐结束后清空数据,以免对下一次消隐产生副作用。

6.constructDS

void constructDS(vector<face>& vface){
	//construct classified polygon and edge table
	//edge table
	for(GLuint i = 0; i < vface.size(); i++){
		g_polygon_id++;//每个面无论绘制与否都要编号,防止编号混乱
		//if(g_polygon_id == 129)
		//	g_polygon_id =g_polygon_id;
		//cout<<g_polygon_id<<endl;
		//if(!preProcessf(vface[i])) continue;
		//依次解决每个面
		vector<GLdouble> coffs(4);
		solveFaceCoffs(coffs, vface[i]);
		if(coffs[2] < 1e-8 && coffs[2] > -1e-8) continue;//垂直于投影xOy面的面不考虑

		int polygon_maxy = (vface[i][0].y>0?vface[i][0].y:0), polygon_miny = polygon_maxy; 
		for(GLuint j = 0; j < vface[i].size(); j++){
			//依次解决每个面的每条边
			Point a = roundVertex(vface[i][j]);
			Point b = roundVertex(vface[i][(j+1)%vface[i].size()]);//%--最后一个点和第一个点也能构成边
			//a.x += 
			if(a.y < b.y){//让a成为当前处理边的上顶点
				Point t = a;
				a = b;
				b = t;
			}
			if(a.y < 0) continue;//水平线下的边不考虑
			if(a.y - b.y < 1e-8 && a.y - b.y > -1e-8) continue;//水平边不考虑(不影响结果),因为绘制非水平边的时候已经绘制了水平边
			
			//构造分类边表结点
			nodeClassifiedEdge* pCE = (nodeClassifiedEdge*)malloc(sizeof(nodeClassifiedEdge));
			pCE->x = a.x;
			pCE->dx = (b.x - a.x + .0) / (a.y - b.y);//相邻两条扫描线交点的x坐标差dx (-1/k)
			pCE->dy = a.y - b.y + 1;
			pCE->id = g_polygon_id;
			pCE->used = false;
			pCE->next = NULL;
			//加入分类边表
			if(a.y >= tClassifiedEdge.size()) continue;
			if(tClassifiedEdge[a.y] == NULL) {
				tClassifiedEdge[a.y] = pCE;
			}else{
				nodeClassifiedEdge* p = tClassifiedEdge[a.y];
				while(p->next) p = p->next;
				p->next = pCE;
			}
			if(a.y > polygon_maxy) polygon_maxy = a.y;
			if(b.y < polygon_miny) polygon_miny = b.y;
		}//end for(int j = 0;...)

		nodeClassifiedPolygon* pCP = (nodeClassifiedPolygon*)malloc(sizeof(nodeClassifiedPolygon));		
		pCP->a = coffs[0];
		pCP->b = coffs[1];
		pCP->c = coffs[2];
		pCP->d = coffs[3];
		pCP->dy = polygon_maxy - polygon_miny + 1;
		pCP->id = g_polygon_id;
		pCP->color = getPolygonColor(coffs);
		pCP->next = NULL;
		if(polygon_maxy >=  tClassifiedPolygon.size()) continue;
		if(tClassifiedPolygon[polygon_maxy] == NULL){
			tClassifiedPolygon[polygon_maxy] = pCP;
		}else{
			nodeClassifiedPolygon* p = tClassifiedPolygon[polygon_maxy];
			while(p->next) p = p->next;
			p->next = pCP;
		}		
	}
}

感觉注释语句写得比较清楚了?

7.重头戏,zbuffer

void zbuffer(){
	tActivePolygonHead.next = NULL;
	tActiveEdgePairHead.next = NULL;
	//scan
	for(GLint y = g_winHeight-1; y >= 0; y--){
		init_g_zbuffer();//初始深度缓存
		activateNewPolygon(y);//添加新的多边形到活化多边形表
		deepthUpdate(y);//增量式深度更新帧缓存
		activeEdgeTableUpdate(y);//活化边表元素修改
		activePolygonTableUpdate();//更新活化多边形表
	}//end for(int i = 4;...)
}

8.activateNewPolygon

void activateNewPolygon(GLint y){
	//std::cout<<y<<std::endl;
	if(y < 0 || y >= tClassifiedPolygon.size() ) return;
	if(tClassifiedPolygon[y]){
	//将当前扫描线扫描到的所有多边形加入活化多边形表
		nodeClassifiedPolygon* pCP = tClassifiedPolygon[y];
		while(pCP){//1个多边形的处理,将一个多边形加入活化多边形表(垂直于xOy面的平面不处理)
			//if(pCP->c < 1e-6 && pCP->c > -1e-6){//再一次保证垂直于xOy面的平面不处理
			//	pCP = pCP->next;
			//	continue;
			//}
			//如果平面不垂直于xOy面,则分配结点放入活化多边形表
				
			nodeClassifiedPolygon *t_pCP = (nodeClassifiedPolygon*)malloc(sizeof(nodeClassifiedPolygon));
			*t_pCP = *pCP;
			pCP = t_pCP;//pcp的next指针仍然保留指向分类多边形表的指针
			t_pCP = pCP->next;
			pCP->next = NULL;
			nodeActivePolygon *pAP = tActivePolygonHead.next;
			if(!pAP) tActivePolygonHead.next = (nodeActivePolygon*)pCP;
			else{
				while(pAP->next) pAP = pAP->next;
				pAP->next = (nodeActivePolygon*)pCP;
			}
			//if(tActivePolygonHead.next == NULL){
			//	tActivePolygonHead.next = (nodeActivePolygon*)pCP;//活化多边形表结点的结构和分类多边形表的结构相同
			//	tailAP = (nodeActivePolygon*)pCP;
			//}else{
			//	tailAP->next = (nodeActivePolygon*)pCP;
			//	tailAP = tailAP->next;
			//}
			//多边形加入活化多边形表以后,其两条边加入活化边表
			//nodeClassifiedEdge* pCE = tClassifiedEdge[i];
			//while(pCE){
			//分类边表结点结构转化为活化边表结点结构
			//找到边对的左边和右边
				
			nodeClassifiedEdge *l, *r;//此处可能出现bug
			findEdge(&l, y, pCP->id);
			findEdge(&r, y, pCP->id);
			if(!l || !r) {
				#ifdef DEBUGGER
				cout<<__LINE__<<":find a polygon, but not find it's corresponding l & r edges."<<endl;
				#endif
				nodeClassifiedPolygon* next_pCP = pCP->next;
				pCP->next = NULL;
				pCP = next_pCP;
				continue;
			}
			if(l->x > r->x || (l->x == r->x && l->dx > r->dx)){
				nodeClassifiedEdge* _t = l;
				l = r;
				r = _t;
			}
			//l->used = true; r->used = true;
			//构造活化边表结点
			nodeActiveEdgePair* pAEP = (nodeActiveEdgePair*)malloc(sizeof(nodeActiveEdgePair));
			pAEP->xl = l->x;
			pAEP->dxl = l->dx;
			pAEP->dyl = l->dy;
			pAEP->xr = r->x;
			pAEP->dxr = r->dx;
			pAEP->dyr = r->dy;
			pAEP->zl = (- pCP->d - l->x * pCP->a - y * pCP->b) / pCP->c;
			pAEP->dzx = (- pCP->a) / pCP->c;
			pAEP->dzy = pCP->b / pCP->c;
			pAEP->id = l->id;
			pAEP->color = pCP->color;
			pAEP->next = NULL;
			//将活化边表结点加入活化边表
			if(tActiveEdgePairHead.next == NULL){
				tActiveEdgePairHead.next = pAEP;
			}else{
				//find active edge table's tail
				nodeActiveEdgePair* _pAEP = tActiveEdgePairHead.next;
				if(_pAEP == NULL) tActiveEdgePairHead.next = pAEP;
				else{
					while(_pAEP->next) _pAEP = _pAEP->next;
					_pAEP->next = pAEP;
				}
			}

			//画出多边形的上边界线
			GLdouble zx = pAEP->zl;
			int x = pAEP->xl ;
			//首先寻找上边界的左边界
			while(x < 0){
				zx += pAEP->dzx;
				x ++;
			}
			while(x < pAEP->xr){//此处不考虑边界
				if(x < g_zbuffer.size() && zx > g_zbuffer[x]){
					g_zbuffer[x] = zx;
					//cout<<"x:"<<x<<", y:"<<y<<endl;
					frameBuffer.SetPixel(x, y, g_bgColor);
				}
				zx += pAEP->dzx;
				x ++;
			}
			//}//while(pCE)
			//清除从分类多边形表中拷贝来的脏指针next
			//nodeClassifiedPolygon* next_pCP = pCP->next;
			//pCP->next = NULL;
			pCP = t_pCP;
		}//while(pCP)	
	}//if(tClassifiedPolygon[i])
}

首先检查下表,这一点相当重要!!!!在每一次使用[]形式的下标之前一定要检查!!检查!!检查!!说三遍!首先看当前扫描线是否有需要加入活化表的多边形,如果有,则加入,并且将其非水平的两条边组成边对加入活化边表,由于是刚加入,那么当前扫描线一定是当前多边形的上边界线,所以毫不犹豫画出边界线。该方法用到的findEdge:

void findEdge(nodeClassifiedEdge** e, GLint y, GLint polygon_id){
	//根据polygon_id寻找仍在活化多边形表中的活化边
	//e - 结果边的指针的地址
	//y - 当前扫描线位置
	//polygon_id - 多边形id
	*e = NULL;
	nodeActivePolygon* _pAP = (tActivePolygonHead.next);
	//检查多边形是否还在活化多边形表中
	while(_pAP && (_pAP->id != polygon_id || _pAP->dy <= 1)) //_pAP->dy <= 1 这样的多边形即将被移出活化表,所以不予考虑
		_pAP = _pAP->next;
	if(_pAP != NULL){//多边形还在活化表中
		if(!tClassifiedEdge[y]) {
			//输出下列语句也有可能是正常现象,因为有可能扫描到多边形最底部时,多边形还没有移出
			//活化多边形表
			#ifdef DEBUGGER
			cout<<"function:findedge accur logic error(1)"<<endl;
			#endif
			return;
		}
		nodeClassifiedEdge* _pCE = tClassifiedEdge[y];
		while(_pCE && (_pCE->id != polygon_id || _pCE->used))
			_pCE = _pCE->next;
		if(!_pCE){
			#ifdef DEBUGGER
			cout<<"function:findedge not find"<<endl;
			#endif
			return;
		}
		_pCE->used = true;
		*e = _pCE;
	}
}

8.深度式增量更新:

void deepthUpdate(GLint y){
	//增量式深度更新
	nodeActiveEdgePair* pAEP = tActiveEdgePairHead.next;
	while(pAEP){
		GLdouble zx = pAEP->zl;
		int x = pAEP->xl ;
		//init_g_zbuffer();

		//左右边界着背景色
		//首先寻找左边界
		while(x < 0){
			zx += pAEP->dzx;
			x ++;
		}
		//左边界着色
		if(x < g_zbuffer.size() && zx > g_zbuffer[x]){
			g_zbuffer[x] = zx;
			frameBuffer.SetPixel(x, y, g_bgColor);
		}
		zx += pAEP->dzx;
		x ++;
		//左边界着色完成

		while(x < g_zbuffer.size() && x </*=*/ pAEP->xr){//此处不考虑边界
			if(zx > g_zbuffer[x]){
				g_zbuffer[x] = zx;
				//cout<<"x:"<<x<<", y:"<<y<<endl;
				frameBuffer.SetPixel(x, y, pAEP->color);
			}
			zx += pAEP->dzx;
			x ++;
		}

		//右边界着色
		//if(zx > g_zbuffer[x]){
		//	g_zbuffer[x] = zx;
		//	frameBuffer.SetPixel(x, y, g_bgColor);
		//}
		//右边界着色完成

		pAEP = pAEP->next;
	}
}

这个方法比较简单,检查当前扫描线是否有边对,如果有,则根据当前边对之间的扫描线的像素深度和深度缓存对应位置的深度比较,根据比较结果对帧缓存着色。

9.更新活化边表:activeEdgeTableUpdate

void activeEdgeTableUpdate(GLint y){//活化边表元素修改
	nodeActiveEdgePair* pAEP_pre = &tActiveEdgePairHead;
	nodeActiveEdgePair* pAEP = pAEP_pre->next;
	while(pAEP){
		pAEP->dyl --;
		pAEP->dyr --;
		if(pAEP->dyl <= 0 && pAEP->dyr <= 0){//左右两边同时扫描完
			nodeClassifiedEdge *l, *r;
			findEdge(&l, y, pAEP->id);
			if(l == NULL){
				pAEP_pre->next = pAEP->next;
				free(pAEP);
				pAEP = pAEP_pre->next;
			}else{//如果找到了左边,根据多边形的封闭性,一定可以找到右边
				findEdge(&r, y, pAEP->id);
				if(l->x > r->x || (l->x == r->x && l->dx > r->dx)){
					nodeClassifiedEdge* _t = l;
					l = r;
					r = _t;
				}
				nodeActivePolygon *pAP;
				findPolygon(&pAP, pAEP->id);
				pAEP->xl = l->x;
				pAEP->dxl = l->dx;
				pAEP->dyl = l->dy;
				pAEP->xr = r->x;
				pAEP->dxr = r->dx;
				pAEP->dyr = r->dy;
				pAEP->zl = (- pAP->d - l->x * pAP->a - y * pAP->b) / pAP->c;
				pAEP->dzx = (- pAP->a) / pAP->c;
				pAEP->dzy = pAP->b / pAP->c;

				pAEP_pre = pAEP;
				pAEP = pAEP->next;
			}
		}
		else{
			if(pAEP->dyl <= 0){//左边扫描完
				nodeClassifiedEdge* l;
				findEdge(&l, y, pAEP->id);
				if(l){//pAEP不为空表示多边形仍在活化多边形行表中
					//nodeActivePolygon *pAP;
					//findPolygon(&pAP, pAEP->id);
					pAEP->xl = l->x;
					pAEP->dxl = l->dx;
					pAEP->dyl = l->dy;
					//pAEP->zl = (- pAP->d - l->x * pAP->a - i * pAP->b) / pAP->c;
					//pAEP->dzx = (- pAP->a) / pAP->c;
					//pAEP->dzy = pAP->b / pAP->c;
				}
				else{
					pAEP_pre->next = pAEP->next;
					free(pAEP);
					pAEP = pAEP_pre->next;
					continue;
				}
			}
			else{//左边未扫描完
				pAEP->xl += pAEP->dxl;
				pAEP->zl = pAEP->zl + pAEP->dxl * pAEP->dzx + pAEP->dzy;
			}
		
			if(pAEP->dyr <= 0){//右边扫描完
				nodeClassifiedEdge* r;
				findEdge(&r, y, pAEP->id);
				if(r){
					pAEP->xr = r->x;
					pAEP->dxr = r->dx;
					pAEP->dyr = r->dy;
				}
				else{
					pAEP_pre->next = pAEP->next;
					free(pAEP);
					pAEP = pAEP_pre->next;
					continue;
				}
			}
			else{//右边未扫描完
				pAEP->xr += pAEP->dxr;
			}
			pAEP_pre = pAEP;
			pAEP = pAEP->next;
		}
	}	
}

这个更新有几个特别的地方,首先需要区分同时扫描完和单边扫描完,同时扫描完则需要新的边对。当只有单边扫描完时,不用更新深度信息,因为在增量式深度更新当中已经完成了深度的更新,最根本的原因还是因为多边形的连贯性。

10.更新活化多边形表

void activePolygonTableUpdate(){//更新活化多边形表
	nodeActivePolygon* pAP = tActivePolygonHead.next;
	nodeActivePolygon* pAP_pre = &tActivePolygonHead;
	while(pAP){
		pAP->dy = pAP->dy - 1;
		if(pAP->dy <= 0){
			pAP_pre->next = pAP->next;
			free(pAP);
			pAP = pAP_pre->next;
		}
		else{
			pAP_pre = pAP;
			pAP = pAP_pre->next;
		}
	}
}

很简单,就是检查跨越的扫描线数目。

11.清除数据结构:

void clearData(){
	clearActivePolygonTable();//清空活化多边形表
	clearActiveEdgeTable();//清空活化边表
	clearClassifiedPolygon();
	clearClassifiedEdge();
}

void clearActivePolygonTable(){//清空活化多边形表
	nodeActivePolygon *p = tActivePolygonHead.next, *q;
	while(p){
		q = p->next;
		free(p);
		p = q;
	}
	tActivePolygonHead.next = NULL;
}

void clearActiveEdgeTable(){//清空活化边表
	nodeActiveEdgePair *p = tActiveEdgePairHead.next, *q;
	while(p){
		q = p->next;
		free(p);
		p = q;
	}
	tActiveEdgePairHead.next = NULL;
}

void clearClassifiedPolygon(){
	nodeClassifiedPolygon *p, *q; /*int _cnt = 0;*/
	for(GLuint i = 0; i < tClassifiedPolygon.size(); i++){
		if(tClassifiedPolygon[i]){
			p = tClassifiedPolygon[i];
			while(p){
				q = p->next;
				free(p);
				p = q;
				//_cnt++;
			}
			//free(p);
			tClassifiedPolygon[i] = NULL;
		}
	}
}

void clearClassifiedEdge(){
	nodeClassifiedEdge *p, *q;
	for(GLuint i = 0; i < tClassifiedEdge.size(); i++){
		if(tClassifiedEdge[i]){
			p = tClassifiedEdge[i];
			while(p){
				q = p->next;
				free(p);
				p = q;
			}
			tClassifiedEdge[i] = NULL;
		}
	}
}

四.结果

这个模型有34843个顶点,69451个面片,帧率是0.67.

下面是源代码【都看到这儿了,给个star呗,感谢感谢】

https://github.com/ooooooops/scan-line-Z-buffer



 

  • 40
    点赞
  • 105
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值