扫描线zbuffer消隐算法

来源:互联网 发布:大数据分析模型代码 编辑:程序博客网 时间:2024/06/05 08:02

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

       题外话:上学期上过冯结青老师的课,首先为老师的认真态度点赞,不但课件做得好,课也上得好,讲课条理清晰,难易适中,是我上过的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 oncetemplate <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;//scaleGLdouble 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;}elsevface = obj.getObj().vfs;constructDS(vface);zbuffer();clearData();}
先根据是否需要旋转来获得各个面的顶点数据,然后constructDS构建消隐需要的tClassifiedPolygon、tClassifiedEdge。消隐结束后清空数据,以免对下一次消隐产生副作用。

6.constructDS

void constructDS(vector<face>& vface){//construct classified polygon and edge table//edge tablefor(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;//scanfor(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;//此处可能出现bugfindEdge(&l, y, pCP->id);findEdge(&r, y, pCP->id);if(!l || !r) {#ifdef DEBUGGERcout<<__LINE__<<":find a polygon, but not find it's corresponding l & r edges."<<endl;#endifnodeClassifiedPolygon* 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 tailnodeActiveEdgePair* _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 DEBUGGERcout<<"function:findedge accur logic error(1)"<<endl;#endifreturn;}nodeClassifiedEdge* _pCE = tClassifiedEdge[y];while(_pCE && (_pCE->id != polygon_id || _pCE->used))_pCE = _pCE->next;if(!_pCE){#ifdef DEBUGGERcout<<"function:findedge not find"<<endl;#endifreturn;}_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.


代码










1 0
原创粉丝点击