使用重心坐标算法绘制颜色渐变的正八面体

一、实验项目要求

任务1:设计立方体类,八个顶点的颜色定义为白、红、绿、黄、蓝、品红、青、黑;

任务2:使用重心坐标算法,基于顶点颜色填充立方体的四边形表面。

任务3:使用背面剔除算法对立方体进行消隐。

任务4:使用鼠标或键盘方向键,与图形进行交互。

二、理论分析或算法分析

任务1的实现

构建一个立方体类,里面包括读点、读面以及对点的初始化,包含三角形的填充类,在这里采用斜投影来绘制线框图,再构建一个点类,用来初始化顶点颜色的变化,在立方体类中,直接初始化,顶点坐标时赋予颜色,在绘制函数中,定义一个三角形填充类的指针并开辟空间;

任务2的实现

用三角形表示法,有两种方式:第一种是平面着色模式,使用任意顶点的颜色填充三角形内部,颜色具有单一性,会出现马赫带效应,导致颜色不真实;光滑着色模式,三角形内部任意一点的颜色由三个顶点的颜色进行双线性插值得到,采用“左闭右开,下闭上开”的原则,在这次实验中,我们采用第二种着色模式,颜色是渐变性的。取一个三角形的最高点P0和最低点P1,P2位于右侧为右三角形,P2位于左侧为左三角形;设计三角形填充类Triangle,包含带颜色的浮点数二维点类和带颜色的二维整数类,填充时,将四边形分为上三角形和下三角形,最后释放内存。

任务3的实现

背面剔除就是把那些需要看见的能看见,看不见的给它弄的看不见,让你可以更直观,更有立体感,建立一个Vector来建立背面剔除类,包含三维点类,在n*v>=0的时候,是可以看见的,反之,是不可以看见的。在类中,计算向量的点积和计算向量的叉积(v0*v1=(y0*z1+z0*y1,z0*x1+x0*z1,x0*y1+y0*x1)以及重载运算符。构造向量的方法有两种,一种是坐标,另一种是.p来构造,用末点减初点,在绘制平面函数中进行消影,将四边形平分为两个三角形,采用三角形来两个面向量相加,进行背面剔除n*v>=0。

任务4的实现

使用键盘方向键:当按动左键时,可以转动5,按动右键时,又可以转动5;

使用鼠标键:设置一个定时器,当按动动画按钮(OnGraphAnimation)的时候,可以进行旋转,当再次按动动画按钮(OnUpdateGraphAnimation)的时候,可以停下来。

实现了动画的交互。

三、实现方法

主代码:

投影:

#pragma once
#include "P3.h"

class CProjection
{
public:
	CProjection(void);
	virtual ~CProjection(void);
	void SetEye(double R);//设置视点
	CP3 GetEye(void);//读取视点
	CP2 ObliqueProjection(CP3 WorldPoint);//斜投影
	CP2 OrthogonalProjection(CP3 WorldPoint);//正交投影
	CP2 PerspectiveProjection(CP3 WorldPoint);//透视投影
private:
	CP3 EyePoint;//视点
	double R, d;//视径和视距
};
#include "pch.h"
#include "Projection.h"

CProjection::CProjection(void)
{
	R = 1200, d = 800;
	EyePoint.x = 0, EyePoint.y = 0, EyePoint.z = R;//视点位于屏幕正前方
}

CProjection::~CProjection(void)
{
}

void CProjection::SetEye(double R)//设置视径
{
	EyePoint.z = R;
}

CP3 CProjection::GetEye(void)//读取视点
{
	return EyePoint;
}

CP2 CProjection::ObliqueProjection(CP3 WorldPoint)//斜二测投影
{
	CP2 ScreenPoint;//屏幕坐标系二维点
    ScreenPoint.x = WorldPoint.x - 0.3536 * WorldPoint.z;
	ScreenPoint.y = WorldPoint.y - 0.3536 * WorldPoint.z;
	return ScreenPoint;
}

CP2 CProjection::OrthogonalProjection(CP3 WorldPoint)//正交投影
{
	CP2 ScreenPoint;//屏幕坐标系二维点
	ScreenPoint.x = WorldPoint.x;
	ScreenPoint.y = WorldPoint.y;
	return ScreenPoint;
}
   
CP2 CProjection::PerspectiveProjection(CP3 WorldPoint)
{
	CP3 ViewPoint;//观察坐标系三维点
	ViewPoint.x = WorldPoint.x;
	ViewPoint.y = WorldPoint.y;
    ViewPoint.z = EyePoint.z - WorldPoint.z;
	ViewPoint.c = WorldPoint.c;
	CP2 ScreenPoint;//屏幕坐标系二维点
	ScreenPoint.x = d * ViewPoint.x / ViewPoint.z;
	ScreenPoint.y = d * ViewPoint.y / ViewPoint.z;
	ScreenPoint.c = ViewPoint.c;
	return ScreenPoint;
}

背面剔除:

#pragma once
#include "P3.h"

class CVector3
{
public:
	CVector3(void);
	virtual ~CVector3(void);
	CVector3(double x, double y, double z);//绝对向量
	CVector3(const CP3 &p);
	CVector3(const CP3 &p0, const CP3 &p1);//相对向量
	double Magnitude(void);//计算向量的模
	CVector3 Normalize(void);//归一化向量
	friend CVector3 operator + (const CVector3 &v0, const CVector3 &v1);//运算符重载
	friend CVector3 operator - (const CVector3 &v0, const CVector3 &v1);
	friend CVector3 operator * (const CVector3 &v, double scalar);
	friend CVector3 operator * (double scalar, const CVector3 &v);
	friend CVector3 operator / (const CVector3 &v, double scalar);
	friend double DotProduct(const CVector3 &v0, const CVector3 &v1);//计算向量的点积
	friend CVector3 CrossProduct(const CVector3 &v0, const CVector3 &v1);//计算向量的叉积
private:
	double x,y,z;
};
#include "pch.h"
#include "Vector3.h"

CVector3::CVector3(void)
{
	x = 0.0,y = 0.0, z = 1.0;//指向z轴正向
}


CVector3::~CVector3(void)
{
}

CVector3::CVector3(double x, double y, double z)//绝对向量
{
	this->x = x;
	this->y = y;
	this->z = z;	
}

CVector3::CVector3(const CP3 &p)
{
	x = p.x;
	y = p.y;
	z = p.z;
}

CVector3::CVector3(const CP3 &p0, const CP3 &p1)//相对向量
{
	x = p1.x - p0.x;
	y = p1.y - p0.y;
	z = p1.z - p0.z;
}

double CVector3::Magnitude(void)//向量的模
{
	return sqrt(x * x + y * y + z * z);
}

CVector3 CVector3::Normalize(void)//归一化为单位向量
{
	CVector3 vector;
	double magnitude = sqrt(x * x + y * y + z * z);
	if(fabs(magnitude) < 1e-4)
		magnitude  = 1.0;
	vector.x = x / magnitude;
	vector.y = y / magnitude;
	vector.z = z / magnitude;
	return vector;
}

CVector3 operator + (const CVector3 &v0, const CVector3 &v1)//向量的和
{
	CVector3 vector;
	vector.x = v0.x + v1.x;
	vector.y = v0.y + v1.y;
	vector.z = v0.z + v1.z;
	return vector;
}

CVector3 operator - (const CVector3 &v0, const CVector3 &v1)//向量的差
{
	CVector3 vector;
	vector.x = v0.x - v1.x;
	vector.y = v0.y - v1.y;
	vector.z = v0.z - v1.z;
	return vector;
}

CVector3 operator * (const CVector3 &v, double scalar)//向量与常量的积
{
	CVector3 vector;
	vector.x = v.x * scalar;
	vector.y = v.y * scalar;
	vector.z = v.z * scalar;
	return vector;
}

CVector3 operator * (double scalar, const CVector3 &v)//常量与向量的积
{
	CVector3 vector;
	vector.x = v.x * scalar;
	vector.y = v.y * scalar;
	vector.z = v.z * scalar;
	return vector;
}

CVector3 operator / (const CVector3 &v, double scalar)//向量数除
{
	if(fabs(scalar) < 1e-4)
		scalar = 1.0;
	CVector3 vector;
	vector.x = v.x / scalar;
	vector.y = v.y / scalar;
	vector.z = v.z / scalar;
	return vector;
}

double DotProduct(const CVector3 &v0, const CVector3 &v1)//向量的点积
{
	return(v0.x * v1.x + v0.y * v1.y + v0.z * v1.z);
}

CVector3 CrossProduct(const CVector3 &v0, const CVector3 &v1)//向量的叉积
{
	CVector3 vector;
	vector.x = v0.y * v1.z - v0.z * v1.y;
	vector.y = v0.z * v1.x - v0.x * v1.z;
	vector.z = v0.x * v1.y - v0.y * v1.x;
	return vector;
}

填充三角形:

#pragma once
#include "P2.h"//带颜色的浮点数二维点类
#include "Point2.h"//带颜色的整数二维点类

class CTriangle//三角形填充类
{
public:
	CTriangle(void);
	virtual ~CTriangle(void);
	void SetPoint(CP2 P0, CP2 P1, CP2 P2);//浮点数顶点构造三角形
	void GouraudShader(CDC* pDC);//填充三角形
private:
	void EdgeFlag(CPoint2 PStart, CPoint2 PEnd, BOOL bFeature);//边标记
	CRGB LinearInterp(double t, double tStart, double tEnd, CRGB cStart, CRGB cEnd);//颜色线性插值
	void SortVertex(void);//三角形顶点排序
private:
	CPoint2 point0, point1, point2;//三角形的整数顶点
	CPoint2* SpanLeft; //跨度的起点数组标志
	CPoint2* SpanRight;//跨度的终点数组标志
	int nIndex;//扫描线索引
};
#include "pch.h"
#include "Triangle.h"
#define ROUND(d) int(d + 0.5)

CTriangle::CTriangle(void)
{

}

CTriangle::~CTriangle(void)
{

}

void CTriangle::SetPoint(CP2 P0, CP2 P1, CP2 P2)
{
	point0.x = ROUND(P0.x);
	point0.y = ROUND(P0.y);
	point0.c = P0.c;
	point1.x = ROUND(P1.x);
	point1.y = ROUND(P1.y);
	point1.c = P1.c;
	point2.x = ROUND(P2.x);
	point2.y = ROUND(P2.y);
	point2.c = P2.c;
}

void CTriangle::GouraudShader(CDC* pDC)
{
	SortVertex();//point0点为y坐标最小的点,point1点为y坐标最大的点,point2点的y坐标位于二者之间。如果y值相同,取x最小的点
	//定义三角形覆盖的扫描线条数
	int nTotalScanLine = point1.y - point0.y + 1;
	//定义span的起点与终点数组
	SpanLeft = new CPoint2[nTotalScanLine];
	SpanRight = new CPoint2[nTotalScanLine];
	//判断三角形与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;
		for (int x = SpanLeft[n].x; x < SpanRight[n].x; x++)//左闭右开
		{
			CRGB clr = LinearInterp(x, SpanLeft[n].x, SpanRight[n].x, SpanLeft[n].c, SpanRight[n].c);
			pDC->SetPixelV(x, y, RGB(clr.red * 255, clr.green * 255, clr.blue * 255));
		}
	}
	if (SpanLeft)
	{
		delete[]SpanLeft;
		SpanLeft = NULL;
	}
	if (SpanRight)
	{
		delete[]SpanRight;
		SpanRight = NULL;
	}
}

void CTriangle::EdgeFlag(CPoint2 PStart, CPoint2 PEnd, BOOL bFeature)
{
	int dx = PEnd.x - PStart.x;
	int dy = PEnd.y - PStart.y;
	double m = double(dx) / dy;
	double x = PStart.x;
	for(int y = PStart.y; y < PEnd.y; y++)
	{
		CRGB crColor = LinearInterp(y, PStart.y, PEnd.y, PStart.c, PEnd.c);
		if(bFeature)
			SpanLeft[nIndex++] = CPoint2(ROUND(x), y, crColor);
		else
			SpanRight[nIndex++] = CPoint2(ROUND(x), y, crColor);
		x += m;
	}
}

void CTriangle::SortVertex(void)
{
	CPoint2 pt[3];
	pt[0] = point0;
	pt[1] = point1;
	pt[2] = point2;
	for (int i = 0; i < 2; i++)
	{
		for (int j = i + 1; j < 3; j++)
		{
			int k = i;
			if (pt[k].y >= pt[j].y)
				k = j;
			if (k == j)
			{
				CPoint2 ptTemp = pt[i];
				pt[i] = pt[k];
				pt[k] = ptTemp;
			}
		}
	}
	point0 = pt[0];
	point1 = pt[2];
	point2 = pt[1];
}

CRGB CTriangle::LinearInterp(double t, double tStart, double tEnd, CRGB cStart, CRGB cEnd)//颜色线性插值
{
	CRGB color;
	color = (tEnd - t) / (tEnd - tStart) * cStart + (t - tStart) / (tEnd - tStart) * cEnd;
	return color;
}

绘制立方体:

#pragma once
#include"P3.h"
#include "Facet.h"
#include"Vector3.h"
#include"Projection.h"
#include"Triangle.h"

class CCube
{
public:
	CCube(void);
	virtual ~CCube(void);
	void ReadPoint(void);//读入点表
	void ReadFacet(void);//读入面表
	CP3*  GetVertexArrayName(void);//得到顶点数组名
	void Draw(CDC* pDC);//绘制立方体线框
private:
	CP3 P[6];//点表
	CFacet F[8];//面表
	CProjection projection;  //投影
};
#include "pch.h"
#include "Cube.h"

#define ROUND(d) int(d + 0.5)

CCube::CCube(void)
{

}

CCube::~CCube(void)
{

}

CP3* CCube::GetVertexArrayName(void)
{
	return	P;
}

void CCube::ReadPoint(void)//点表
{
	//顶点的三维坐标(x,y,z)
	P[0].x = 0, P[0].y = 1, P[0].z = 0; P[0].c = CRGB(0.0, 0.0, 0.0);
	P[1].x = -1, P[1].y = 0, P[1].z = 0; P[1].c = CRGB(1.0, 0.0, 0.0);
	P[2].x = 0, P[2].y = 0, P[2].z = 1; P[2].c = CRGB(1.0, 1.0, 0.0);
	P[3].x = 1, P[3].y = 0, P[3].z = 0; P[3].c = CRGB(0.0, 1.0, 0.0);
	P[4].x = 0, P[4].y = 0, P[4].z = -1; P[4].c = CRGB(0.0, 0.0, 1.0);
	P[5].x = 0, P[5].y = -1, P[5].z = 0; P[5].c = CRGB(1.0, 0.0, 1.0);
}

void CCube::ReadFacet(void)//面表
{
	//面的顶点序号
	F[0].Index[0] = 0; F[0].Index[1] = 2; F[0].Index[2] = 3; //上面的右前面
	F[1].Index[0] = 0; F[1].Index[1] = 3; F[1].Index[2] = 4; //上面的右后面
	F[2].Index[0] = 0; F[2].Index[1] = 1; F[2].Index[2] = 2; //上面的左前面
	F[3].Index[0] = 0; F[3].Index[1] = 4; F[3].Index[2] = 1; //上面的右后面
	F[4].Index[0] = 2; F[4].Index[1] = 5; F[4].Index[2] = 3; //下面的右前面
	F[5].Index[0] = 3; F[5].Index[1] = 5; F[5].Index[2] = 4; //下面的右后面
	F[6].Index[0] = 1; F[6].Index[1] = 5; F[6].Index[2] = 2; //下面的左前面
	F[7].Index[0] = 1; F[7].Index[1] = 4; F[7].Index[2] = 5; //下面的左后面
}

void CCube::Draw(CDC* pDC)
{

	CP2 ScreenPoint[4];//屏幕二维点
	CP3 ViewPoint = projection.GetEye();//视点
	CTriangle* pFill = new CTriangle;//申请内存
	for (int nFacet = 0; nFacet < 8; 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]]);//边向量
		CVector3 Vector02(P[F[nFacet].Index[0]], P[F[nFacet].Index[2]]);
		CVector3 FacetNormal = CrossProduct(Vector01, Vector02);//面法向量
		FacetNormal = FacetNormal.Normalize();
		if (DotProduct(ViewVector, FacetNormal) >= 0)//背面剔除算法
		{
			for (int nPoint = 0; nPoint < 3; nPoint++)
			{
				ScreenPoint[nPoint] = projection.PerspectiveProjection(P[F[nFacet].Index[nPoint]]);
			}
			pFill->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2]);//下三角形			
			pFill->GouraudShader(pDC);
		}
	}
	delete pFill;//释放内存
}

使用键盘方向键或者鼠标进行交互:

键盘方向键:

void CTestView::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	switch (nChar)
	{
	case VK_UP:
		Alpha = -5;
		transform.RotateX(Alpha);
		break;
	case VK_DOWN:
		Alpha = +5;
		transform.RotateX(Alpha);
		break;
	case VK_LEFT:
		Beta = -5;
		transform.RotateY(Beta);
		break;
	case VK_RIGHT:
		Beta = +5;
		transform.RotateY(Beta);
		break;
	default:
		break;
	}
	Invalidate(FALSE);
	CView::OnKeyDown(nChar, nRepCnt, nFlags);
}

鼠标按钮:

void CTestView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	Alpha = 10, Beta = 10;
	transform.RotateX(Alpha);
	transform.RotateY(Beta);
	Invalidate(FALSE);
	CView::OnTimer(nIDEvent);
}


void CTestView::OnGraphAnimation()
{
	// TODO: 在此添加命令处理程序代码
	bPlay = !bPlay;
	if (bPlay)//设置定时器
		SetTimer(1, 300, NULL);
	else
		KillTimer(1);
}


void CTestView::OnUpdateGraphAnimation(CCmdUI *pCmdUI)
{
	// TODO: 在此添加命令更新用户界面处理程序代码
	if (bPlay)
		pCmdUI->SetCheck(TRUE);
	else
		pCmdUI->SetCheck(FALSE);
}

四、实验结果分析

实验结果

实验总结

在这次实验中,刚开始出现了线连不起来,然后在仔细观察后,是绘制颜色那块出了错误,所以在之后的实验中要仔细,这样就不会出错。

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值