其总体设计流程图为如下图所示:
通过在Microsoft Visual Studio 2010开发环境下MFC类库搭建所需要的框架,创建应用程序框架,建立一个工程名称为Z_BufferEli的MFC单文档应用程序框架。运用框架制作该立体彩色渐变图形,建立一个基本MFC单文档应用基础程序,然后再在改程序的基础之上,添加了MyFace.h文件,该文件主要定义三个用于处理点,线,面的类,分别为:class DPoint、class DLine、class Dface。接着创建一个Matrix.h文件,主要用于矩阵处理以及计算。接着添加类CZ_BufferElilView的公有成员变量和成员函数。然后应用MFC的向导,添加视图类CZ_BufferElilView的消息映射WM_KEYDOWN。接着修改Z_BufferElilView.cpp中的代码,添加构造函数初始化各个变量,添加判断点是否在多边形内部的函数,添加对单个面z缓冲处理的函数,添加OnDraw绘制图形的代码,添加按键旋转函数OnKeyDown,到这里完成了对旋转立方体的设计。
接着在FaceBuffer函数中修改代码,添加3个字节形的数组,分别存储9种颜色的RGB值,分别有白、红、橙、黄、绿、蓝、靛、紫、黑。然后将函数改为按步长变化RGB值,使得输出的颜色实现多种颜色的渐变,丰富了图形的颜色,使得图形更加绚丽。
1. 打开VS2010->“新建项目”->“MFC应用程序”->名称:“Z_BufferEli” ->“确定”。
“下一步”->应用程序类型:单个文档;项目类型:MFC标准->“完成”。
2.右键头文件—>点击“添加”—>“新建项”,之后输入文件名MyFace,定义MyFace.h文件,该文件主要定义三个用于处理点,线,面的类。
输入的代码如下:
点类
class DPoint
{
public:
DPoint(){x=0;y=0;z=0;} //默认构造函数
DPoint(int a,int b,int c):x(a),y(b),z(c){} //构造函数
DPoint(const DPoint&p):x(p.x),y(p.y),z(p.z){} //复制构造函数
DPoint&operator=(const DPoint&rhs) //赋值操作
{
x=rhs.x;y=rhs.y;z=rhs.z;
return *this;
}
friend inline int operator==(const DPoint&p1,const DPoint&p2) //判断点是否相等,是返回1,否则返回0
{
if (p1.x==p2.x&&p1.y==p2.y&&p1.z==p2.z)
{
return 1;
}
else
return 0;
}
friend inline int operator!=(const DPoint&p1,const DPoint&p2) //判断点是否不相等,是返回0,否则返回1
{
if ((p1.x!=p2.x)||(p1.y!=p2.y)||(p1.z!=p2.z))
{
return 1;
}
else
return 0;
}
void setx(int a)
{
x=a;
}
void sety(int b)
{
y=b;
}
void setz(int c)
{
z=c;
}
int getx()
{
return x;
}
int gety()
{
return y;
}
int getz()
{
return z;
}
protected:
public:
int x;
int y;
int z;
};
/线类
class DLine
{
public:
DLine(int a,int b,int c,int d,int e,int f):p1(a,b,c),p2(d,e,f){} //构造函数
DLine():p1(0,0,0),p2(0,0,0){} //默认构造函数
DLine(DPoint x,DPoint y):p1(x),p2(y){}
DPoint getp1()
{
return p1;
}
DPoint getp2()
{
return p2;
}
DLine(const DLine&l) //复制构造函数
{
p1=l.p1;
p2=l.p2;
}
DLine&operator=(const DLine&rhs) //赋值操作
{
p1=rhs.p1;p2=rhs.p2;
return *this;
}
friend int intersect(const DLine&l1,const DLine&l2) //判断线段是否有公共点,有返回1,否则返回0
{
if ((l1.p1==l2.p1)||(l1.p1==l2.p2)||(l1.p2==l2.p1)||(l1.p2==l2.p2))
{
return 1;
}
else
return 0;
}
protected:
private:
DPoint p1;
DPoint p2;
};
面类
class DFace
{
public:
DFace():p1(0,0,0),p2(0,0,0),p3(0,0,0),p4(0,0,0),L1(0,0,0,0,0,0),L2(0,0,0,0,0,0),L3(0,0,0,0,0,0),L4(0,0,0,0,0,0),a(0),b(0),c(0),d(0)
{
// a=b=c=d=0;
} //默认构造函数
DFace(DPoint x,DPoint y,DPoint z,DPoint w):p1(x),p2(y),p3(z),p4(w),L1(x,y),L2(y,z),L3(z,w),L4(w,x) //构造函数
{
int x1=x.getx(),y1=x.gety(),z1=x.getz();
int x2=y.getx(),y2=y.gety(),z2=y.getz();
int x3=z.getx(),y3=z.gety(),z3=z.getz();
int x4=w.getx(),y4=w.gety(),z4=w.getz();
a=(int)((y1-y2)*(z1+z2)+(y2-y3)*(z2+z3)+(y3-y4)*(z3+z4)+(y4-y1)*(z4+z1));
b=(int)((z1-z2)*(x1+x2)+(z2-z3)*(x2+x3)+(z3-z4)*(x3+x4)+(z4-z1)*(x4+x1));
c=(int)((x1-x2)*(y1+y2)+(x2-x3)*(y2+y3)+(x3-x4)*(y3+y4)+(x4-x1)*(y4+y1));
d=(int)(-(a*x1+b*y1+c*z1));
}
int geta()
{
return a;
}
int getb()
{
return b;
}
int getcc()
{
return c;
}
int getd()
{
return d;
}
private:
DPoint p1,p2,p3,p4;
DLine L1,L2,L3,L4;
int a;
int b;
int c;
int d;
};
3.用2的方法创建Matrix.h文件,用于矩阵处理以及计算。
#pragma once
class Matrix
{
public:
int ROW; //行数
int COL; //列数
double **p;
public:
Matrix(int m=0,int n=0,double *A=NULL,int k=0)//; //矩阵的构造函数
{
ROW=m; //指定行数
COL=n; //指定列数
p=new double*[ROW]; //生成元素为指针,长度为ROW的数组
for(int i=0;i<ROW;++i)
{
p[i]=new double[COL]; //p[i]是长度为COL的数组的指针
for (int j=0;j<COL;++j)
{
p[i][j]=A[i*n+j]; //用一维数组构造二维数
}
}
}
~Matrix()//; //矩阵的析构函数
{
for(int i=0;i<ROW;++i)
{
delete []p[i]; //释放数组
}
}
Matrix&operator=(Matrix&s)//; //赋值运算符重载
{
ROW=s.ROW;
COL=s.COL;
p=new double*[ROW]; //生成元素为指针,长度为ROW的数组
for(int i=0;i<ROW;++i)
{
p[i]=new double[COL]; //p[i]是长度为COL的数组的指针
for (int j=0;j<COL;++j)
{
p[i][j]=s.p[i][j]; //用一维数组构造二维数
}
}
return *this;
}
Matrix&operator*(Matrix&t)//; //用友元声明乘法运算符重载
{
int m=ROW,n=COL,k=t.COL;
Matrix result;
result.ROW=m;result.COL=k;
result.p=new double*[m];
for(int i=0;i<m;++i)
{
result.p[i]=new double[k];
for(int j=0;j<k;++j)
{
result.p[i][j]=0;
for(int l=0;l<n;++l)
result.p[i][j]+=p[i][l]*t.p[l][j];
}
}
ROW=result.ROW;COL=result.COL;
for (int i=0;i<ROW;++i)
{
for (int j=0;j<COL;++j)
{
p[i][j]=result.p[i][j];
}
}
return *this;
}
};
4.在Z_BufferEliView.h中添加类CZ_BufferElilView的公有成员变量和成员函数的定义。
public:
int n;
int num[1000];
COLORREF color[400][400];//帧缓存
double z_distance[400][400];//z缓存
float angle;//旋转角度
DPoint P1,P2,P3,P4,P5,P6,P7,P8;//立方体的八个顶点
int IsInSide(CPoint Px,CPoint P1,CPoint P2,CPoint P3,CPoint P4);//判断一个点是否在多边形内
void FaceBuffer(DPoint P1,DPoint P2,DPoint P3,DPoint P4,int start,int end,CDC*pDC);
添加头文件:
#include "MyFace.h"
5. 打开应用MFC向导添加视图类CZ_BufferElilView的消息映射WM_KEYDOWN
6. 在Z_BufferElilView.cpp中添加程序结构代码。
1) 添加头文件
#include "math.h"
#include "Matrix.h"
2) CZ_BufferEliView的构造函数中对变量进行初始化。
CZ_BufferEliView::CZ_BufferEliView():P1(-100,100,100),P2(100,100,100),P3(100,100,-100),P4(-100,100,-100),P5(-100,-100,100),P6(100,-100,100),P7(100,-100,-100),P8(100,-100,-100)
{
// 初始化帧缓存和Z缓存
angle=float(3.1415926/3);
for (int i=0;i<400;i++)
{
for (int j=0;j<400;++j)
{
color[i][j]=RGB(255,255,255);//
z_distance[i][j]=-1000;
}
}
n=0;
}
3)添加判断一个点是否在多边形内部的函数。
int CZ_BufferEliView::IsInSide(CPoint Px, CPoint P1, CPoint P2, CPoint P3, CPoint P4)
{
double a1,b1,a2,b2,a3,b3,a4,b4;
int m=0; int x=(P1.x+P3.x)/2,y=(P1.y+P3.y)/2;//P1P3中点坐标
if ((P1.x!=P2.x)&&(P1.x!=P4.x))
{
a1=((double)(P2.y-P1.y))/(P2.x-P1.x);//P1P2斜率
b1=P1.y-a1*P1.x;//P1P2截距
a2=((double)(P3.y-P2.y))/(P3.x-P2.x);
b2=P2.y-a2*P2.x;
a3=a1;
b3=P3.y-a1*P3.x;
a4=a2;
b4=P1.y-a4*P1.x;
if (((a1*Px.x+b1-Px.y)*(a1*x+b1-y)>0)&&((a3*Px.x+b3-Px.y)*(a3*x+b3-y)>0)&&((a2*Px.x+b2-Px.y)*(a2*x+b2-y)>0)&&((a4*Px.x+b4-Px.y)*(a4*x+b4-y)>0))
{
m++;
}
}
else if (P1.x==P2.x)//水平边的处理
{
a2=((double)(P3.y-P2.y))/(P3.x-P2.x);
b2=P2.y-a2*P2.x;
a4=a2;
b4=P1.y-a4*P1.x;
if (P4.x>P1.x)
{
if (((a2*x+b2-y)*(a2*Px.x+b2-Px.y)>0)&&((a4*x+b4-y)*(a4*Px.x+b4-Px.y)>0)&&(P1.x<Px.x)&&(Px.x<P4.x))
{
m++;
}
}
else
{
if (((a2*x+b2-y)*(a2*Px.x+b2-Px.y)>0)&&((a4*x+b4-y)*(a4*Px.x+b4-Px.y)>0)&&(P4.x<Px.x)&&(Px.x<P1.x))
{
m++;
}
}
}
else if (P1.x==P4.x)//水平边的处理
{
a1=((double)(P2.y-P1.y))/(P2.x-P1.x);
b1=P1.y-a1*P1.x;
a3=a1;
b3=P3.y-a1*P3.x;
if (P2.x>P1.x)
{
if (((a1*x+b1-y)*(a1*Px.x+b1-Px.y)>0)&&((a3*x+b3-y)*(a3*Px.x+b3-Px.y)>0)&&(P1.x<Px.x)&&(Px.x<P2.x))
{
m++;
}
}
else
{
if (((a1*x+b1-y)*(a1*Px.x+b1-Px.y)>0)&&((a3*x+b3-y)*(a3*Px.x+b3-Px.y)>0)&&(P2.x<Px.x)&&(Px.x<P1.x))
{
m++;
}
}
}
return m;
}
4) 对单个面的Z缓冲处理函数和对颜色的渐变变化
void CZ_BufferEliView::FaceBuffer(DPoint P1, DPoint P2, DPoint P3, DPoint P4, int start,int end,CDC *pDC)
{
CPoint p[4];int xleft,xright,ytop,ybottom;
DFace f(P1,P2,P3,P4);
int a=f.geta(),b=f.getb(),c=f.getcc(),d=f.getd();
p[0].x=P1.getx();p[0].y=P1.gety();
p[1].x=P2.getx();p[1].y=P2.gety();
p[2].x=P3.getx();p[2].y=P3.gety();
p[3].x=P4.getx();p[3].y=P4.gety();
//确定平面的X,Y范围
xleft=xright=p[0].x;ytop=ybottom=p[0].y;
for (int i=0;i<4;++i)
{
if (p[i].x<xleft)
{
xleft=p[i].x;
}
if (p[i].x>xright)
{
xright=p[i].x;
}
if (p[i].y<ybottom)
{
ybottom=p[i].y;
}
if (p[i].y>ytop)
{
ytop=p[i].y;
}
}
BYTE r[9]={255,255,250,255,0,68,75,141,0};
BYTE g[9]={255,33,140,255,229,206,0,75,0};
BYTE blue[9]={255,33,53,0,0,246,130,187,0,};
double width=(xright-xleft)/4;
BYTE r9;
BYTE g9;
BYTE b9;
//对XY范围内的点逐个判断是否在多边形内
for (int y=ybottom;y<=ytop;++y)
{
int x=xleft;
for(int i=start;i<=end;i++)
{
double re,gr,bl;
re=(double)(r[i+1]-r[i])/width;
gr=(double)(g[i+1]-g[i])/width;
bl=(double)(blue[i+1]-blue[i])/width;
for(int k=0;k<=width;k++,x++)
{
r9=r[i]+k*re;
g9=g[i]+k*gr;
b9=blue[i]+k*bl;
CPoint mid(x,y);
if (IsInSide(mid,p[0],p[1],p[2],p[3]))
{
if (c!=0)
{
double z=((double)(-d-a*x-b*y))/c;
//z缓冲的过程
if (z>=z_distance[x+200][y+200])
{
color[x+200][y+200]=RGB(r9,g9,b9);
z_distance[x+200][y+200]=z;
}
}
}
}
}
}
}
5) OnDraw()函数中添加绘制图形的代码。
void CZ_BufferEliView::OnDraw(CDC* pDC)
{
CZ_BufferEliDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
pDC->TextOut(0,0,_T("按\"X\"、\"Y\"、\"Z\"键从各方位观看图形"));
// TODO: Add your message handler code here
CRect rect;
GetClientRect(&rect);
pDC->SetViewportOrg(rect.Width()/2,rect.Height()/2);
//每次重绘前重新初始化帧缓存和Z缓存
for (int i=0;i<400;++i)
{
for (int j=0;j<400;++j)
{
color[i][j]=RGB(255,255,255);
z_distance[i][j]=-1000;
}
}
FaceBuffer(P1,P2,P3,P4,0,3,pDC);
FaceBuffer(P5,P6,P7,P8,1,4,pDC);
FaceBuffer(P3,P4,P8,P7,2,5,pDC);
FaceBuffer(P1,P2,P6,P5,3,6,pDC);
FaceBuffer(P2,P3,P7,P6,4,7,pDC);
FaceBuffer(P1,P4,P8,P5,5,8,pDC);
for (int y=-200;y<200;++y)
{
for (int x=-200;x<200;++x)
{
pDC->SetPixel(x,y,color[x+200][y+200]);
}
}
P1=DPoint(-100,100,100);P2=DPoint(100,100,100);
P3=DPoint(100,100,-100);P4=DPoint(-100,100,-100);
P5=DPoint(-100,-100,100);P6=DPoint(100,-100,100);
P7=DPoint(100,-100,-100);P8=DPoint(-100,-100,-100);
}
6) 添加OnKeyDown函数,通过按X,Y,Z键可以旋转立方体,从不同角度来观察。
void CZ_BufferEliView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
double A[16]={1,0,0,0,0,cos(angle),-sin(angle),0,0,sin(angle),cos(angle),0,0,0,0,1};
Matrix RX(4,4,A,16);
double B[16]={cos(angle),0,sin(angle),0,0,1,0,0,-sin(angle),0,cos(angle),0,0,0,0,1};
Matrix RY(4,4,B,16);
double C[16]={cos(angle),-sin(angle),0,0,sin(angle),cos(angle),0,0,0,0,1,0,0,0,0,1};
Matrix RZ(4,4,C,16);
double D[16]={1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1};
Matrix M(4,4,D,16);
switch(nChar){
case 88://按下X,Y,Z键 图形绕三个轴旋转
num[n]=1;
break;
case 89:
num[n]=2;
break;
case 90:
num[n]=3;
break;
default:
break;
}
//保证多次旋转仍能正确显示
for (int j=0;j<=n;++j)
{
if (num[j]==1)
{
M=RX*M;
}
if (num[j]==2)
{
M=RY*M;
}
if (num[j]==3)
{
M=RZ*M;
}
}
n++;
DPoint p[8];
p[0]=P1;p[1]=P2;p[2]=P3;p[3]=P4;
p[4]=P5;p[5]=P6;p[6]=P7;p[7]=P8;
for (int i=0;i<8;++i)
{
double x,y,z;
x=M.p[0][0]*p[i].x+M.p[0][1]*p[i].y+M.p[0][2]*p[i].z+M.p[0][3];
y=M.p[1][0]*p[i].x+M.p[1][1]*p[i].y+M.p[1][2]*p[i].z+M.p[1][3];
z=M.p[2][0]*p[i].x+M.p[2][1]*p[i].y+M.p[2][2]*p[i].z+M.p[2][3];
p[i].x=(int)(x);p[i].y=(int)(y);p[i].z=(int)(z);
}
P1=p[0];P2=p[1];P3=p[2];P4=p[3];
P5=p[4];P6=p[5];P7=p[6];P8=p[7];
Invalidate();
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
最终结果:
改变默认的参数,也可以改变渐变的颜色数量
有问题欢迎随时指导。
[1] 潘云鹤,童若锋,唐敏. 计算机图形学. 高等教育出版社,2011.
[2] 博客园博主JustDoIT 原文链接:http://www.cnblogs.com/TenosDoIt/p/4024413.html
[3] CSDN博主Jurb原文链接:https://blog.csdn.net/Jurbo/article/details/75007260
[4] CSDN博主mycaibo原文链接:https://blog.csdn.net/mycaibo/article/details/6012063