一、项目描述
作为一个计算机系的学生,每个人离不开的就是电脑和鼠标,所以在这次实验中,我设计一个鼠标;有了鼠标之后,为了防止它丢失,就给它设计了一个盒子,所以在它的左边设计了一个长方体盒。
实验截图
左边是鼠标盒,右边是鼠标:
将鼠标装入鼠标盒:
鼠标装入鼠标盒后进行旋转:
二、项目设计
过程:使用贝塞尔曲面可以大致绘制出鼠标的形状,在这里使用的是回转体,一个半球是使用四个八分之一个球拼接而成;然后使用ZBuffer进行消隐;继续使用PhongSharder进行光照;最后使用Texture进行四个位置的分别贴图,在这四个位置都使用凹凸纹理;最后给这四个对象进行界面交互,可以使用方向键进行旋转,也可以使用按钮来进行动画。
1.首先,在这个实验中,我先构造了线框模型,在构造过程中,使用四个双三次贝塞尔曲面进行鼠标线框的绘制(如图所示)
CP2 P2[14];//二维控制点
P2[0] = CP2(0,-1);//14个二维点模拟鼠标
P2[1] = CP2(0.3,-1);
P2[2] = CP2(0.6,-1);
P2[3] = CP2(1,-1);
P2[4] = CP2(1,-0.9);
P2[5] = CP2(1, -0.8);
P2[6] = CP2(1,-0.5);
P2[7] = CP2(1,m-0.5);
P2[8] = CP2(m,0.5);
P2[9] = CP2(0,0.5);
P2[10] = CP2(1, 0.5);
P2[11] = CP2(1, 1);
P2[12] = CP2(m, 1.5);
P2[13] = CP2(0, 1.5);
2.然后在鼠标的右侧使用三维建立盒子的三维点,进行了盒子的绘制(如图所示):
P[0].x = 0, P[0].y = 0, P[0].z = 0;
P[1].x = 1, P[1].y = 0, P[1].z = 0;
P[2].x = 1, P[2].y = 1, P[2].z = 0;
P[3].x = 0, P[3].y = 1, P[3].z = 0;
P[4].x = 0, P[4].y = 0, P[4].z = 1;
P[5].x = 1, P[5].y = 0, P[5].z = 1;
P[6].x = 1, P[6].y = 1, P[6].z = 1;
P[7].x = 0, P[7].y = 1, P[7].z = 1;
在这里需要注意的就是我这个盒子的建模,我只绘制了五个面,后面会进行动画的设计,可以使鼠标装到盒子里面;
3.接下来,有了模型之后,观看鼠标,可以看出来,我们是可以看见鼠标的底的线框哒。所以,在这里,我们将使用ZBuffer 来进行消隐,以及使用PhongShader来进行对线框模型进一步的完善,使用PhongShader来进行对鼠标以及盒子进行双向性插值来插入颜色,在本实验中,我们采用金色;进行了消隐以及对物体进行填充颜色外,设计一个光照,给人们一种真实感。(如图所示)
4.最后,为了更具有真实感,增加了映射纹理,在这里,本人比较喜欢皮卡丘,所以这里的凹凸纹理是贴的皮卡丘的图片。(如图所示)
算法:
- 三维变换使用里面的(RotateX(绕X轴旋转)用于对物体进行旋转、RotateY(绕Y轴旋转)用于对物体进行旋转、Scale(缩放)可以将物体缩放成比较合理的大小,然后进行拼接、Transform(平移)用于将鼠标平移到鼠标盒里面);几何变换是使图形运动的一种方式,其中平移变换和旋转变换属于刚体变换,物体的形状不发生改变。
- Cube类主要是对鼠标盒线框的绘制,在这里定义了鼠标盒的八个点,五个面,依次读入;
- 双三次贝塞尔曲面绘制鼠标,双三次贝塞尔曲面由u,v方向的两组贝塞尔曲线交织而成;控制网格由16个控制点构成,只有角上的4个控制点位于曲面上;曲面的边界由4条三次贝塞尔曲线组成。在这里使用了四个双三次贝塞尔曲面来绘制出鼠标,一个是鼠标的底,一个是鼠标的侧面,一个是鼠标的上表面,一个是鼠标的按钮。
- 使用透视投影对鼠标盒以及鼠标进行投影。
- 使用ZBuffer进行消隐。
- 在这个实验中,我使用金色来描绘物体的颜色,使用反射光。
- 在这里使用纹理贴图,在这里我使用皮卡丘贴图,因为个人比较喜欢皮卡丘,然后盒子为了将其与鼠标看起来是一套,所以,盒子也是选用皮卡丘的图片。
原理:
平移变换矩阵:
比例变换矩阵:
绕X轴旋转:
绕Y轴旋转:
双三次贝塞尔曲面的定义:
Pij,i,j=0,1,2,3是4*4=16个控制点。Bi,3(u) 和Bj,3(v)是三次Bernstein基函数。
双三次贝塞尔曲面是一个“弯曲的四面体”,采用四叉树递归划分法进行细分,较好的细分方法是自适应细分法,根据曲面的复杂度来决定递归深度。如果用于着色,那么每个四边形需要再转换为两个小三角形。
双三次贝塞尔曲面的法向量:为曲面添加光照或纹理效果时,需要计算曲面每个细分网格点的法向量。
Pu=αP(u,v)/αu,
Pv=αP(u,v)/αv
该点的法向量为两个切向量的叉积。
从三维角度拼接4个双三次贝塞尔曲面可以表示一个回转体。控制点的z坐标全部取为零,控制点的x坐标代表回转半径。对于4个控制点,可以设计回转类CRevolution,使用4个双三次贝塞尔曲面构造一个相应的回转体。回转体控制点数组使用了数组名V。回转体是一段曲线绕y轴回转出的三维表面。回转面由4个双三次贝塞尔曲面拼接而成。每个回转体读入的二维曲线是一段三次贝塞尔曲线,有4个控制点。
在透视投影中,对于相同长度的直线,距离视点远的比距离视点近的线在屏幕上的投影要小。
Xs/ Xv = d/zv
Ys/ Yv = d/zv
世界坐标系到观察坐标系的变换:
透视投影变换分两步实施:第一步是,先将世界坐标系中的三维Pw变换为观察坐标系中的三维点Pv;第二步是,将Pv点变换为屏幕坐标系中的二维点Ps。
每组平行线都有不同的灭点。
透视投影中的主灭点数量由屏幕切割世界坐标系坐标轴的数量决定。
投影类是一个工具类,其作用是将三维点变换为二维点。
透视投影函数在变换x和y坐标的同时,也将世界坐标系中三维点的颜色传递给屏幕坐标系的二维点。
ZBuffer算法:
设置帧缓冲器颜色为背景色;
确定深度缓冲器(ZBuffer)的宽度、高度和初始深度。一般将初始深度为最大深度值;
对于多边形表面中的每个像素(x,y),计算其深度值z(x,y);
将z与存储在深度缓冲器中(x,y)处的深度值ZBuffer(x,y)进行比较;
若z(x,y)<=zBuffer(x,y),则将此像素的颜色写入帧缓冲器,且用z(x,y)重置ZBuffer(x,y)。
ZBuffer算法的最大优点在于算法简单,与场景复杂度无关,可以轻易地处理可见面问题及复杂曲面之间的交线。ZBuffer算法的缺点是需要占用大量的存储单元,若用1024*768的缓冲器,用32位的颜色表示和32位的深度值,则需要6MB的存储空间。场景中通常是先检测物体表面全部投影所覆盖的最大范围,然后确定深度缓冲器的宽度和高度,这可以有效减少深度缓冲器的大小。深度缓冲器常用二维数组实现,数组的每个元素对应于一个屏幕像素。
物体的材质属性是指物体表面对光的吸收、反射和投射的性能。材质决定物体的颜色,在进行光照计算时,材质的环境反射率与场景中的环境光分量相结合,漫反射率与光漫的漫反射光分量相结合,镜面反射率与光源的镜面反射光分量相结合。如果物体自身不发光,那么可以简单地将发射光定义为零,不与其他光强项进行叠加。物体接受的来自大地、天空、墙壁等周围景物投射的光的环境光。物体上一点P的环境光光强Ie可表示为
Ie=kaIa, ka∈[0,1]
Ia表示来自周围环境的入射光强,ka为材质的环境反射率。
漫反射光只与光源的位置有关,而与视点的位置无关。
Illuminate计算场景中物体表面上网格点Point所获得的反射光光强。
光照计算公式:
I=kaIa+f(d)[kdIpmax(N*L,0)+ksIpmax(N*H,0)n次方]
曲面上的某点需要读取纹理时,用曲面的u,v双向性插值结果作为坐标去查询位图的相应的纹素,就可以得到该点的纹理颜色。
三、项目实现
类:BezierPatch.h;Cube.h;Facet.h; Lighting.h; LightSource.h; Material.h; Mesh.h; P2.h; P3.h; Patch.h; Point2.h; Point3.h; Projection.h; Revolution.h; RGB.h; T2.h; Texture.h; Transform3.h; Vector3.h,ZBuffer.h。
关键代码:
1.交互界面的函数:
(1)OnGraphAnimation函数:
void CTestView::OnGraphAnimation()
{
// TODO: 在此添加命令处理程序代码
bPlay = !bPlay;
if (bPlay)//设置定时器
SetTimer(1, 1, NULL);
else
KillTimer(1);
}
(2)OnUpdateGraphAnimation函数:
void CTestView::OnUpdateGraphAnimation(CCmdUI *pCmdUI)
{
// TODO: 在此添加命令更新用户界面处理程序代码
if (bPlay)
pCmdUI->SetCheck(TRUE);
else
pCmdUI->SetCheck(FALSE);
}
(3)OnTimer函数:
void CTestView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
//将鼠标慢慢移到外包装
tranUp.Translate(-100, 0, 0);
tranUp1.Translate(-100, 0, 0);
tranDown.Translate(-100, 0, 0);
tranDown1.Translate(-100, 0, 0);
test++;
if (test == 5)
{
OnGraphAnimation();
}
//将鼠标平移进框里
/* tranUp.Translate(-500, 0, 0);
tranUp1.Translate(-500, 0, 0);
tranDown.Translate(-500, 0, 0);
tranDown1.Translate(-500, 0, 0);
OnGraphAnimation();*/
Invalidate(FALSE);
CView::OnTimer(nIDEvent);
}
(4)OnKeyDown函数:
void CTestView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
switch (nChar)
{
case VK_UP:
Alpha = -5;
tranUp.RotateX(Alpha);
tranUp1.RotateX(Alpha);
tranDown.RotateX(Alpha);
tranDown1.RotateX(Alpha);
transform.RotateX(Alpha);
break;
case VK_DOWN:
Alpha = +5;
tranUp.RotateX(Alpha);
tranUp1.RotateX(Alpha);
tranDown.RotateX(Alpha);
tranDown1.RotateX(Alpha);
transform.RotateX(Alpha);
break;
case VK_LEFT:
Beta = -5;
tranUp.RotateY(Beta);
tranUp1.RotateY(Beta);
tranDown.RotateY(Beta);
tranDown1.RotateY(Beta);
transform.RotateY(Beta);
break;
case VK_RIGHT:
Beta = +5;
tranUp.RotateY(Beta);
tranUp1.RotateY(Beta);
tranDown.RotateY(Beta);
tranDown1.RotateY(Beta);
transform.RotateY(Beta);
break;
default:
break;
}
Invalidate(FALSE);
CView::OnKeyDown(nChar, nRepCnt, nFlags);
}
2.制作线框模型的透视投影三维动画:
(1)鼠标和鼠标盒导入三维场景:
CTestView::CTestView() noexcept
{
// TODO: 在此处添加构造代码
bPlay = FALSE;
double R = 200;
double m = 0.5523;
CP2 P2[14];//二维控制点
P2[0] = CP2(0,-1);//14个二维点模拟鼠标
P2[1] = CP2(0.3,-1);
P2[2] = CP2(0.6,-1);
P2[3] = CP2(1,-1);
P2[4] = CP2(1,-0.9);
P2[5] = CP2(1, -0.8);
P2[6] = CP2(1,-0.5);
P2[7] = CP2(1,m-0.5);
P2[8] = CP2(m,0.5);
P2[9] = CP2(0,0.5);
P2[10] = CP2(1, 0.5);
P2[11] = CP2(1, 1);
P2[12] = CP2(m, 1.5);
P2[13] = CP2(0, 1.5);
CP3 DownPoint[4];//鼠标底座的三维控制点
DownPoint[0] = CP3(P2[0].x, P2[0].y, 0.0);
DownPoint[1] = CP3(P2[1].x, P2[1].y, 0.0);
DownPoint[2] = CP3(P2[2].x, P2[2].y, 0.0);
DownPoint[3] = CP3(P2[3].x, P2[3].y, 0.0);
revoDown.ReadCubicBezierControlPoint(DownPoint);
tranDown.SetMatrix(revoDown.GetVertexArrayName(), 48);
tranDown.Scale(R, R, 2 * R);
tranDown.Translate(300, 0, 0);
CP3 UpPoint[4];//鼠标侧面的三维控制点
UpPoint[0] = CP3(P2[3].x, P2[3].y, 0.0);
UpPoint[1] = CP3(P2[4].x, P2[4].y, 0.0);
UpPoint[2] = CP3(P2[5].x, P2[5].y, 0.0);
UpPoint[3] = CP3(P2[6].x, P2[6].y, 0.0);
revoUp.ReadCubicBezierControlPoint(UpPoint);
tranUp.SetMatrix(revoUp.GetVertexArrayName(), 48);
tranUp.Scale(R, R, 2 * R);
tranUp.Translate(300, 0, 0);
CP3 DownPoint1[4];//鼠标上面的三维控制点
DownPoint1[0] = CP3(P2[6].x, P2[6].y, 0.0);
DownPoint1[1] = CP3(P2[7].x, P2[7].y, 0.0);
DownPoint1[2] = CP3(P2[8].x, P2[8].y, 0.0);
DownPoint1[3] = CP3(P2[9].x, P2[9].y, 0.0);
revoDown1.ReadCubicBezierControlPoint(DownPoint1);
tranDown1.SetMatrix(revoDown1.GetVertexArrayName(), 48);
tranDown1.Scale(R, R, 2 * R);
tranDown1.Translate(300, 0, 0);
CP3 UpPoint1[4];//鼠标按钮的三维控制点
UpPoint1[0] = CP3(P2[10].x, P2[10].y, 0.0);
UpPoint1[1] = CP3(P2[11].x, P2[11].y, 0.0);
UpPoint1[2] = CP3(P2[12].x, P2[12].y, 0.0);
UpPoint1[3] = CP3(P2[13].x, P2[13].y, 0.0);
revoUp1.ReadCubicBezierControlPoint(UpPoint1);
tranUp1.SetMatrix(revoUp1.GetVertexArrayName(), 48);
tranUp1.Scale(R / 7, R / 3, R / 3);
tranUp1.Translate(300, -50, 300);
double nEdge = 400;
cube.ReadPoint();
cube.ReadFacet();
transform.SetMatrix(cube.GetVertexArrayName(), 8);
transform.Scale(1.1 * nEdge,nEdge, 2.0 * nEdge);
transform.Translate(-nEdge, -nEdge * 2 / 3, -nEdge);//平移变换
InitializeLightingScene();
revoDown.patch.SetScene(pLight, pMaterial);//设置场景
revoUp.patch.SetScene(pLight, pMaterial);//设置场景
revoDown1.patch.SetScene(pLight, pMaterial);//设置场景
revoUp1.patch.SetScene(pLight, pMaterial);//设置场景
cube.SetScene(pLight, pMaterial);//设置场景
/*-> 设置纹理*/
texture[0].PrepareBitmap(IDB_BITMAP1);//准备位图
texture[1].PrepareBitmap(IDB_BITMAP2);//准备位图
texture[2].PrepareBitmap(IDB_BITMAP1);//准备位图
texture[3].PrepareBitmap(IDB_BITMAP3);//准备位图
revoDown.patch.SetTexture(&texture[0]);
revoUp.patch.SetTexture(&texture[1]);
revoDown1.patch.SetTexture(&texture[2]);
revoUp1.patch.SetTexture(&texture[3]);
InitializeLightingScene();
texture[4].PrepareBitmap(IDB_BITMAP2);
texture[5].PrepareBitmap(IDB_BITMAP2);
texture[6].PrepareBitmap(IDB_BITMAP2);
texture[7].PrepareBitmap(IDB_BITMAP2);
texture[8].PrepareBitmap(IDB_BITMAP2);
cube.SetTexture(&texture[4]);
}
(2)在CBezierPatch类中绘制出鼠标的透视投影线框图:
void CBezierPatch::DrawFacet(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维投影点
CP3 ViewPoint = projection.GetEye();//视点
for(int nPoint = 0; nPoint < 4; nPoint++)
ScreenPoint[nPoint] = projection.PerspectiveProjection3(quadrP[nPoint]);//透视投影
pDC->MoveTo(ScreenPoint[0].x, ScreenPoint[0].y);
pDC->LineTo(ScreenPoint[1].x, ScreenPoint[1].y);
pDC->LineTo(ScreenPoint[2].x, ScreenPoint[2].y);
pDC->LineTo(ScreenPoint[3].x, ScreenPoint[3].y);
pDC->LineTo(ScreenPoint[0].x, ScreenPoint[0].y);
}
(3)在CCube类中绘制鼠标盒的投影线框图:
void CCube::Draw(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维屏幕点
CT2 ScreenTexture[4];//二维纹理坐标
for (int nFacet = 0; nFacet < 5; nFacet++)//面循环
{
CP3 ViewPoint = projection.GetEye();//视点
CVector3 ViewVector(P[F[nFacet].Index[0]], ViewPoint);// 面的视向量
ViewVector = ViewVector.Normalize();//视向量单位化
CVector3 Vector01(P[F[nFacet].Index[0]], P[F[nFacet].Index[1]]), Vector02(P[F[nFacet].Index[0]], P[F[nFacet].Index[2]]);//边向量
CVector3 FacetNormal = CrossProduct(Vector01, Vector02);//面法向量
FacetNormal = FacetNormal.Normalize();
for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环
ScreenPoint[nPoint] = projection.PerspectiveProjection3(P[F[nFacet].Index[nPoint]]);
pDC->MoveTo(ScreenPoint[0].x, ScreenPoint[0].y);
pDC->LineTo(ScreenPoint[1].x, ScreenPoint[1].y);
pDC->LineTo(ScreenPoint[2].x, ScreenPoint[2].y);
pDC->LineTo(ScreenPoint[3].x, ScreenPoint[3].y);
pDC->LineTo(ScreenPoint[0].x, ScreenPoint[0].y);
}
}
3.制作光照模型三维动画:
(1)初始化光照环境:
void CTestView::InitializeLightingScene(void)//初始化光照环境
{
//设置光源属性
nLightSourceNumber = 1;//光源个数
pLight = new CLighting(nLightSourceNumber);//一维光源动态数组
pLight->LightSource[0].SetPosition(500, 500, 900);//设置光源位置坐标
for (int i = 0; i < nLightSourceNumber; i++)
{
pLight->LightSource[i].L_Diffuse = CRGB(1.0, 1.0, 1.0);//光源的漫反射颜色
pLight->LightSource[i].L_Specular = CRGB(1.0, 1.0, 1.0);//光源镜面高光颜色
pLight->LightSource[i].L_C0 = 1.0;//常数衰减因子
pLight->LightSource[i].L_C1 = 0.0000001;//线性衰减因子
pLight->LightSource[i].L_C2 = 0.00000001;//二次衰减因子
pLight->LightSource[i].L_OnOff = TRUE;//光源开启
}
//设置材质属性
pMaterial = new CMaterial;
pMaterial->SetAmbient(CRGB(0.847, 0.10, 0.075));//环境反射率
pMaterial->SetDiffuse(CRGB(0.752, 0.606, 0.226));//漫反射率
pMaterial->SetSpecular(CRGB(1.0, 1.0, 1.0));//镜面反射率
pMaterial->SetEmission(CRGB(0.0, 0.0, 0.0));//自身辐射的颜色
pMaterial->SetExponent(300);//高光指数
}
(2)填充三角形网格:
void CBezierPatch::DrawFacet(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维投影点
CP3 ViewPoint = projection.GetEye();//视点
for(int nPoint = 0; nPoint < 4; nPoint++)
ScreenPoint[nPoint] = projection.PerspectiveProjection3(quadrP[nPoint]);//透视投影
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[2], ScreenPoint[3], CVector3(quadrP[0]), CVector3(quadrP[2]), CVector3(quadrP[3]), quadrT[0], quadrT[2], quadrT[3]);
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, pTexture);
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2], CVector3(quadrP[0]), CVector3(quadrP[1]), CVector3(quadrP[2]), quadrT[0], quadrT[1], quadrT[2]);
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, pTexture);
}
void CCube::Draw(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维屏幕点
CT2 ScreenTexture[4];//二维纹理坐标
for (int nFacet = 0; nFacet < 5; nFacet++)//面循环
{
CP3 ViewPoint = projection.GetEye();//视点
CVector3 ViewVector(P[F[nFacet].Index[0]], ViewPoint);// 面的视向量
ViewVector = ViewVector.Normalize();//视向量单位化
CVector3 Vector01(P[F[nFacet].Index[0]], P[F[nFacet].Index[1]]), Vector02(P[F[nFacet].Index[0]], P[F[nFacet].Index[2]]);//边向量
CVector3 FacetNormal = CrossProduct(Vector01, Vector02);//面法向量
FacetNormal = FacetNormal.Normalize();
for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环
ScreenPoint[nPoint] = projection.PerspectiveProjection3(P[F[nFacet].Index[nPoint]]);
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[2], ScreenPoint[3], FacetNormal, FacetNormal, FacetNormal, CT2(0, 0), CT2((pTexture[nFacet].bmp.bmWidth - 1), pTexture[nFacet].bmp.bmHeight - 1), CT2(0, pTexture[nFacet].bmp.bmHeight - 1));
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, &pTexture[nFacet]);
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2], FacetNormal, FacetNormal, FacetNormal, CT2(0, 0), CT2(pTexture[nFacet].bmp.bmWidth - 1, 0), CT2(pTexture[nFacet].bmp.bmWidth - 1, pTexture[nFacet].bmp.bmHeight - 1));
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, &pTexture[nFacet]);
}
}
4.制作图像纹理三维动画:
(1)获得纹理:
CRGB CZBuffer::GetTexture(int u, int v, CTexture* pTexture)
{
v = pTexture->bmp.bmHeight - 1 - v;
/*检测图片的边界,防止越界*/
if (u < 0) u = 0; if (v < 0) v = 0;
if (u > pTexture->bmp.bmWidth - 1) u = pTexture->bmp.bmWidth - 1;
if (v > pTexture->bmp.bmHeight - 1) v = pTexture->bmp.bmHeight - 1;
/*查找对应纹理空间的颜色值*/
int position = v * pTexture->bmp.bmWidthBytes + 4 * u;//循环每一列,每行读四个字节
return CRGB(pTexture->image[position + 2] / 255.0, pTexture->image[position + 1] / 255.0, pTexture->image[position] / 255.0);
}
5.制作凹凸纹理模型三维动画:
void CZBuffer::PhongShader(CDC* pDC, CP3 ViewPoint, CLighting* pLight, CMaterial* pMaterial, CTexture* pTexture) {
double CurrentDepth = 0.0;//当前扫描线的深度
CVector3 Vector01(P0, P1), Vector02(P0, P2);
CVector3 fNormal = CrossProduct(Vector01, Vector02);
double A = fNormal.x, B = fNormal.y, C = fNormal.z;//平面方程Ax+By+Cz +D=0的系数
double D = -A * P0.x - B * P0.y - C * P0.z;//当前扫描线随着x增长的深 度步长
if (fabs(C) < 1e-4) C = 1.0;
double DepthStep = -A / C;//计算扫描线深度步长增量
SortVertex();//三角形顶点排序
//定义三角形覆盖的扫描线条数
int nTotalLine = point1.y - point0.y + 1; //定义span的起点与终点数组
SpanLeft = new CPoint2[nTotalLine];
SpanRight = new CPoint2[nTotalLine]; //判断三角形与P0P1边的位置关系,0-1-2为右手系
int nDeltz = (point1.x - point0.x) * (point2.y - point1.y) - (point1.y - point0.y) * (point2.x - point1.x);//点向量叉积的z坐标
if (nDeltz > 0)//三角形位于P0P1边的左侧
{
nIndex = 0;
EdgeFlag(point0, point2, TRUE);
EdgeFlag(point2, point1, TRUE);
nIndex = 0;
EdgeFlag(point0, point1, FALSE);
}
else//三角形位于P0P1边的右侧
{
nIndex = 0;
EdgeFlag(point0, point1, TRUE);
nIndex = 0;
EdgeFlag(point0, point2, FALSE);
EdgeFlag(point2, point1, FALSE);
}
for (int y = point0.y; y < point1.y; y++)//下闭上开
{
int n = y - point0.y;
BOOL bInFlag = FALSE;//跨度内外测试标志,初始值为假表示三角形外部
for (int x = SpanLeft[n].x; x < SpanRight[n].x; x++)//左闭右开
{
if (bInFlag == FALSE)
{
CurrentDepth = -(A * x + B * y + D) / C;//z=-(Ax+By+D)/C
bInFlag = TRUE; x -= 1;
}
else {
CVector3 Normal = LinearInterp(x, SpanLeft[n].x, SpanRight[n].x, SpanLeft[n].n, SpanRight[n].n);
Normal = Normal.Normalize();
CT2 Texture = LinearInterp(x, SpanLeft[n].x, SpanRight[n].x, SpanLeft[n].t, SpanRight[n].t);//纹理地址插值
//高度场凹凸纹理
CRGB frontu = GetTexture(ROUND(Texture.u - 7), ROUND(Texture.v), pTexture);
CRGB behindu = GetTexture(ROUND(Texture.u + 7), ROUND(Texture.v), pTexture);
double Bu = frontu.blue - behindu.blue; //U向高度差
CRGB frontv = GetTexture(ROUND(Texture.u), ROUND(Texture.v - 7), pTexture);
CRGB behindv = GetTexture(ROUND(Texture.u), ROUND(Texture.v + 7), pTexture);
double Bv = frontv.blue - behindv.blue;//V向高度差
CVector3 DNormal = CVector3(Bu, Bv, 0);//扰动法向量
Normal = Normal + DNormal;//扰动后法矢量
Texture.c = GetTexture(ROUND(Texture.u), ROUND(Texture.v), pTexture);//获取纹理空间对应颜色值
pMaterial->SetDiffuse(Texture.c);//纹理颜色作为材质的漫反 射率
pMaterial->SetAmbient(Texture.c);//设置材质的镜面反射率
CRGB Intensity = pLight->Illuminate(ViewPoint, CP3(x, y, CurrentDepth), Normal, pMaterial);
if (CurrentDepth <= zBuffer[x + nWidth / 2][y + nHeight / 2])// 深度缓冲器算法
{
zBuffer[x + nWidth / 2][y + nHeight / 2] = CurrentDepth;
pDC->SetPixelV(x, y, RGB(Intensity.red * 255, Intensity.green * 255, Intensity.blue * 255));
}
CurrentDepth += DepthStep;
}
}
}
if (SpanLeft)
{
delete[]SpanLeft; SpanLeft = NULL;
}
if (SpanRight)
{
delete[]SpanRight; SpanRight = NULL;
}
}
Cube.h
#pragma once
#include"Projection.h"
#include"Facet.h"
#include"Lighting.h"
#include"Vector3.h"
#include"Texture.h"
#include"ZBuffer.h"
class CCube
{
public:
CCube(void);
virtual ~CCube(void);
CP3* GetVertexArrayName(void);//获得数组名
void ReadPoint(void);//读入点表
void ReadFacet(void);//读入面表
void SetScene(CLighting* pLight, CMaterial* pMaterial);//设置场景
void SetTexture(CTexture* pTexture);//设置纹理
void Draw(CDC* pDC, CZBuffer* pZBuffer);//绘制图形
private:
CP3 P[8];//点表
CFacet F[5];//面表
CProjection projection;//投影
CLighting* pLight;//光照
CMaterial* pMaterial;//材质
CTexture pTexture[6];
};
Cube.cpp:
#include "pch.h"
#include "Cube.h"
#include <math.h>//包含数学头文件
CCube::CCube(void)
{
}
CCube::~CCube(void)
{
}
CP3* CCube::GetVertexArrayName(void)//获得数组名
{
return P;
}
void CCube::SetScene(CLighting* pLight, CMaterial* pMaterial)//设置场景
{
this->pLight = pLight;
this->pMaterial = pMaterial;
}
void CCube::SetTexture(CTexture* pTexture)
{
this->pTexture[0] = pTexture[0];
this->pTexture[1] = pTexture[1];
this->pTexture[2] = pTexture[2];
this->pTexture[3] = pTexture[3];
this->pTexture[4] = pTexture[4];
this->pTexture[5] = pTexture[5];
}
void CCube::ReadPoint(void)//点表
{
P[0].x = 0, P[0].y = 0, P[0].z = 0;
P[1].x = 1, P[1].y = 0, P[1].z = 0;
P[2].x = 1, P[2].y = 1, P[2].z = 0;
P[3].x = 0, P[3].y = 1, P[3].z = 0;
P[4].x = 0, P[4].y = 0, P[4].z = 1;
P[5].x = 1, P[5].y = 0, P[5].z = 1;
P[6].x = 1, P[6].y = 1, P[6].z = 1;
P[7].x = 0, P[7].y = 1, P[7].z = 1;
}
void CCube::ReadFacet(void)//面表
{
F[0].Number = 4;F[0].Index[0] = 4;F[0].Index[1] = 5;F[0].Index[2] = 6;F[0].Index[3] = 7;//前面
F[1].Number = 4;F[1].Index[0] = 0;F[1].Index[1] = 3;F[1].Index[2] = 2;F[1].Index[3] = 1;//后面
F[2].Number = 4;F[2].Index[0] = 0;F[2].Index[1] = 4;F[2].Index[2] = 7;F[2].Index[3] = 3;//左面
//F[3].Number = 4;F[3].Index[0] = 1;F[3].Index[1] = 2;F[3].Index[2] = 6;F[3].Index[3] = 5;//右面
F[3].Number = 4;F[3].Index[0] = 2;F[3].Index[1] = 3;F[3].Index[2] = 7;F[3].Index[3] = 6;//顶面
F[4].Number = 4;F[4].Index[0] = 0;F[4].Index[1] = 1;F[4].Index[2] = 5;F[4].Index[3] = 4;//底面
}
void CCube::Draw(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维屏幕点
CT2 ScreenTexture[4];//二维纹理坐标
for (int nFacet = 0; nFacet < 5; nFacet++)//面循环
{
CP3 ViewPoint = projection.GetEye();//视点
CVector3 ViewVector(P[F[nFacet].Index[0]], ViewPoint);// 面的视向量
ViewVector = ViewVector.Normalize();//视向量单位化
CVector3 Vector01(P[F[nFacet].Index[0]], P[F[nFacet].Index[1]]), Vector02(P[F[nFacet].Index[0]], P[F[nFacet].Index[2]]);//边向量
CVector3 FacetNormal = CrossProduct(Vector01, Vector02);//面法向量
FacetNormal = FacetNormal.Normalize();
for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环
ScreenPoint[nPoint] = projection.PerspectiveProjection3(P[F[nFacet].Index[nPoint]]);
/*pDC->MoveTo(ScreenPoint[0].x, ScreenPoint[0].y);
pDC->LineTo(ScreenPoint[1].x, ScreenPoint[1].y);
pDC->LineTo(ScreenPoint[2].x, ScreenPoint[2].y);
pDC->LineTo(ScreenPoint[3].x, ScreenPoint[3].y);
pDC->LineTo(ScreenPoint[0].x, ScreenPoint[0].y);*/
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[2], ScreenPoint[3], FacetNormal, FacetNormal, FacetNormal, CT2(0, 0), CT2((pTexture[nFacet].bmp.bmWidth - 1), pTexture[nFacet].bmp.bmHeight - 1), CT2(0, pTexture[nFacet].bmp.bmHeight - 1));
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, &pTexture[nFacet]);
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2], FacetNormal, FacetNormal, FacetNormal, CT2(0, 0), CT2(pTexture[nFacet].bmp.bmWidth - 1, 0), CT2(pTexture[nFacet].bmp.bmWidth - 1, pTexture[nFacet].bmp.bmHeight - 1));
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, &pTexture[nFacet]);
}
}
BezierPatch.h:
#pragma once
#include"Mesh.h"
#include"Projection.h"
#include"ZBuffer.h"
class CBezierPatch
{
public:
CBezierPatch(void);
virtual ~CBezierPatch(void);
void ReadControlPoint(CP3 CtrPt[4][4], int ReNumber);//读入16个控制点和递归深度
void SetScene(CLighting* pLight, CMaterial* pMaterial);//设置场景
void SetTexture(CTexture* pTexture);
void DrawCurvedPatch(CDC* pDC, CZBuffer* pZBuffer);//绘制曲面
void DrawControlGrid(CDC* pDC);//绘制控制网格
private:
void Recursion(CDC* pDC, CZBuffer* pZBuffer, int ReNumber, CMesh Mesh);//递归函数
void Tessellation(CMesh Mesh);//细分函数
void DrawFacet(CDC* pDC, CZBuffer* pZBuffer);//绘制四边形网格
void LeftMultiplyMatrix(double M[4][4], CP3 P[4][4]);//左乘顶点矩阵
void RightMultiplyMatrix(CP3 P[4][4], double M[4][4]);//右乘顶点矩阵
void TransposeMatrix(double M[4][4]);//转置矩阵
private:
int ReNumber;//递归深度
CP3 CtrPt[4][4];//曲面的16个控制点
CP3 quadrP[4];//四边形的顶点
CT2 quadrT[4];//四边形的纹理坐标
CProjection projection;//投影
CLighting* pLight;//光照
CMaterial* pMaterial;//材质
CTexture* pTexture;//纹理
};
BezierPatch.cpp:
#include "pch.h"
#include "BezierPatch.h"
#define ROUND(d) int(d + 0.5)
CBezierPatch::CBezierPatch(void)
{
ReNumber = 0;
}
CBezierPatch::~CBezierPatch(void)
{
}
void CBezierPatch::ReadControlPoint(CP3 CtrPt[4][4], int ReNumber)
{
for (int i = 0; i< 4; i++)
for (int j = 0; j < 4; j++)
this->CtrPt[i][j] = CtrPt[i][j];
this->ReNumber = ReNumber;
}
void CBezierPatch::SetScene(CLighting* pLight, CMaterial* pMaterial)//设置场景
{
this->pLight = pLight;
this->pMaterial = pMaterial;
}
void CBezierPatch::SetTexture(CTexture* pTexture)
{
this->pTexture = pTexture;
}
void CBezierPatch::DrawCurvedPatch(CDC* pDC, CZBuffer* pZBuffer)
{
CMesh Mesh;
Mesh.BL = CT2(0, 0), Mesh.BR = CT2(1, 0);//初始化uv
Mesh.TR = CT2(1, 1), Mesh.TL = CT2(0, 1);
Recursion(pDC, pZBuffer, ReNumber, Mesh);//递归函数
}
void CBezierPatch::Recursion(CDC* pDC, CZBuffer* pZBuffer, int ReNumber, CMesh Mesh)//递归函数
{
if (0 == ReNumber)
{
Tessellation(Mesh);//细分曲面,将(u,v)点转换为(x,y)点
DrawFacet(pDC, pZBuffer);//绘制小平面
return;
}
else
{
CT2 Mid = (Mesh.BL + Mesh.TR) / 2.0;
CMesh SubMesh[4];//一分为四个
//左下子长方形
SubMesh[0].BL = Mesh.BL;
SubMesh[0].BR = CT2(Mid.u, Mesh.BL.v);
SubMesh[0].TR = CT2(Mid.u, Mid.v);
SubMesh[0].TL = CT2(Mesh.BL.u, Mid.v);
//右下子长方形
SubMesh[1].BL = SubMesh[0].BR;
SubMesh[1].BR = Mesh.BR;
SubMesh[1].TR = CT2(Mesh.BR.u, Mid.v);
SubMesh[1].TL = SubMesh[0].TR;
//右上子长方形
SubMesh[2].BL = SubMesh[1].TL;
SubMesh[2].BR = SubMesh[1].TR;
SubMesh[2].TR = Mesh.TR;
SubMesh[2].TL = CT2(Mid.u, Mesh.TR.v);
//左上子长方形
SubMesh[3].BL = SubMesh[0].TL;
SubMesh[3].BR = SubMesh[2].BL;
SubMesh[3].TR = SubMesh[2].TL;
SubMesh[3].TL = Mesh.TL;
Recursion(pDC, pZBuffer, ReNumber - 1, SubMesh[0]);//递归绘制4个子曲面
Recursion(pDC, pZBuffer, ReNumber - 1, SubMesh[1]);
Recursion(pDC, pZBuffer, ReNumber - 1, SubMesh[2]);
Recursion(pDC, pZBuffer, ReNumber - 1, SubMesh[3]);
}
}
void CBezierPatch::Tessellation(CMesh Mesh)//细分曲面函数
{
double M[4][4];//系数矩阵M
M[0][0] = -1, M[0][1] = 3, M[0][2] = -3, M[0][3] = 1;
M[1][0] = 3, M[1][1] = -6, M[1][2] = 3, M[1][3] = 0;
M[2][0] = -3, M[2][1] = 3, M[2][2] = 0, M[2][3] = 0;
M[3][0] = 1, M[3][1] = 0, M[3][2] = 0, M[3][3] = 0;
CP3 P3[4][4];//曲线计算用控制点数组
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
P3[i][j] = CtrPt[i][j];
LeftMultiplyMatrix(M, P3);//系数矩阵左乘三维点矩阵
TransposeMatrix(M);//计算转置矩阵
RightMultiplyMatrix(P3, M);//系数矩阵右乘三维点矩阵
double u0, u1, u2, u3, v0, v1, v2, v3;//u,v参数的幂
double u[4] = { Mesh.BL.u,Mesh.BR.u ,Mesh.TR.u ,Mesh.TL.u };
double v[4] = { Mesh.BL.v,Mesh.BR.v ,Mesh.TR.v ,Mesh.TL.v };
for (int i = 0; i < 4; i++)
{
u3 = pow(u[i], 3.0), u2 = pow(u[i], 2.0), u1 = u[i], u0 = 1;
v3 = pow(v[i], 3.0), v2 = pow(v[i], 2.0), v1 = v[i], v0 = 1;
quadrP[i] = (u3 * P3[0][0] + u2 * P3[1][0] + u1 * P3[2][0] + u0 * P3[3][0]) * v3
+ (u3 * P3[0][1] + u2 * P3[1][1] + u1 * P3[2][1] + u0 * P3[3][1]) * v2
+ (u3 * P3[0][2] + u2 * P3[1][2] + u1 * P3[2][2] + u0 * P3[3][2]) * v1
+ (u3 * P3[0][3] + u2 * P3[1][3] + u1 * P3[2][3] + u0 * P3[3][3]) * v0;
quadrT[i].u = (pTexture->bmp.bmWidth - 1) * u[i];
quadrT[i].v = (pTexture->bmp.bmHeight - 1) * v[i];
}
}
void CBezierPatch::DrawFacet(CDC* pDC, CZBuffer* pZBuffer)
{
CP3 ScreenPoint[4];//三维投影点
CP3 ViewPoint = projection.GetEye();//视点
for(int nPoint = 0; nPoint < 4; nPoint++)
ScreenPoint[nPoint] = projection.PerspectiveProjection3(quadrP[nPoint]);//透视投影
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[2], ScreenPoint[3], CVector3(quadrP[0]), CVector3(quadrP[2]), CVector3(quadrP[3]), quadrT[0], quadrT[2], quadrT[3]);
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, pTexture);
pZBuffer->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2], CVector3(quadrP[0]), CVector3(quadrP[1]), CVector3(quadrP[2]), quadrT[0], quadrT[1], quadrT[2]);
pZBuffer->PhongShader(pDC, ViewPoint, pLight, pMaterial, pTexture);
}
void CBezierPatch::LeftMultiplyMatrix(double M[4][4], CP3 P[4][4])//左乘矩阵M*P
{
CP3 PTemp[4][4];//临时矩阵
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
PTemp[i][j] = M[i][0] * P[0][j] + M[i][1] * P[1][j] + M[i][2] * P[2][j] + M[i][3] * P[3][j];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
P[i][j] = PTemp[i][j];
}
void CBezierPatch::RightMultiplyMatrix(CP3 P[4][4], double M[4][4])//右乘矩阵P*M
{
CP3 PTemp[4][4];//临时矩阵
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
PTemp[i][j] = P[i][0] * M[0][j] + P[i][1] * M[1][j] + P[i][2] * M[2][j] + P[i][3] * M[3][j];
for (int i = 0; i < 4; i++)
for (int j = 0; j < 4; j++)
P[i][j] = PTemp[i][j];
}
void CBezierPatch::TransposeMatrix(double M[4][4])//转置矩阵
{
double PTemp[4][4];//临时矩阵
for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
PTemp[j][i] = M[i][j];
for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
M[i][j] = PTemp[i][j];
}
void CBezierPatch::DrawControlGrid(CDC* pDC)//绘制控制网格
{
CP2 P2[4][4];//二维控制点
for(int i = 0; i < 4; i++)
for(int j = 0; j < 4; j++)
P2[i][j] = projection.PerspectiveProjection2(CtrPt[i][j]);//透视投影
CPen NewPen, *pOldPen;
NewPen.CreatePen(PS_SOLID, 3, RGB(0, 128, 0));
pOldPen = pDC->SelectObject(&NewPen);
for(int i = 0; i < 4; i++)
{
pDC->MoveTo(ROUND(P2[i][0].x), ROUND(P2[i][0].y));
for(int j = 1; j < 4; j++)
pDC->LineTo(ROUND(P2[i][j].x),ROUND(P2[i][j].y));
}
for(int j = 0; j < 4; j++)
{
pDC->MoveTo(ROUND(P2[0][j].x), ROUND(P2[0][j].y));
for(int i = 1; i < 4; i++)
pDC->LineTo(ROUND(P2[i][j].x), ROUND(P2[i][j].y));
}
pDC->SelectObject(pOldPen);
NewPen.DeleteObject();
}
四、项目总结
本项目制作了鼠标和鼠标盒的线框图、光照图和凹凸纹理图。在这次实验中,想法主要是源于生活中我们经常用的鼠标,所以,做了这个鼠标以及鼠标盒。
五、参考文献
计算机图形学,孔令德编著。
计算几何算法与实现,孔令德编著。