计算机图像学设计鼠标

一、项目描述

作为一个计算机系的学生,每个人离不开的就是电脑和鼠标,所以在这次实验中,我设计一个鼠标;有了鼠标之后,为了防止它丢失,就给它设计了一个盒子,所以在它的左边设计了一个长方体盒。

实验截图

左边是鼠标盒,右边是鼠标:

将鼠标装入鼠标盒:

鼠标装入鼠标盒后进行旋转:

二、项目设计

过程:使用贝塞尔曲面可以大致绘制出鼠标的形状,在这里使用的是回转体,一个半球是使用四个八分之一个球拼接而成;然后使用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.最后,为了更具有真实感,增加了映射纹理,在这里,本人比较喜欢皮卡丘,所以这里的凹凸纹理是贴的皮卡丘的图片。(如图所示)

算法:

  1. 三维变换使用里面的(RotateX(绕X轴旋转)用于对物体进行旋转、RotateY(绕Y轴旋转)用于对物体进行旋转、Scale(缩放)可以将物体缩放成比较合理的大小,然后进行拼接、Transform(平移)用于将鼠标平移到鼠标盒里面);几何变换是使图形运动的一种方式,其中平移变换和旋转变换属于刚体变换,物体的形状不发生改变。
  2. Cube类主要是对鼠标盒线框的绘制,在这里定义了鼠标盒的八个点,五个面,依次读入;
  3. 双三次贝塞尔曲面绘制鼠标,双三次贝塞尔曲面由u,v方向的两组贝塞尔曲线交织而成;控制网格由16个控制点构成,只有角上的4个控制点位于曲面上;曲面的边界由4条三次贝塞尔曲线组成。在这里使用了四个双三次贝塞尔曲面来绘制出鼠标,一个是鼠标的底,一个是鼠标的侧面,一个是鼠标的上表面,一个是鼠标的按钮。
  4. 使用透视投影对鼠标盒以及鼠标进行投影。
  5. 使用ZBuffer进行消隐。
  6. 在这个实验中,我使用金色来描绘物体的颜色,使用反射光。
  7. 在这里使用纹理贴图,在这里我使用皮卡丘贴图,因为个人比较喜欢皮卡丘,然后盒子为了将其与鼠标看起来是一套,所以,盒子也是选用皮卡丘的图片。

原理:

平移变换矩阵:

比例变换矩阵:

绕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();
}

四、项目总结

本项目制作了鼠标和鼠标盒的线框图、光照图和凹凸纹理图。在这次实验中,想法主要是源于生活中我们经常用的鼠标,所以,做了这个鼠标以及鼠标盒。

五、参考文献

 计算机图形学,孔令德编著。

计算几何算法与实现,孔令德编著。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值