图形学作业要到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
#include
#include
#include
#include
#include
#define FREEGLUT_STATIC
#include
#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;
}