一、实验项目要求
1. 建立立方体类。
2. 背面剔除算法消隐。
二、理论分析或算法分析
1.建立立方体类:
在实验中,建立一个Cube类,在Cube类中定义立方体的八个顶点,六个面,为了可以方便后面绘制面,所以定义了一个Facet类,用来定义表面顶点和面的顶点索引号,为了绘制出这个立方体,在立方体类中,需要定义这八个顶点的坐标(void CCube::ReadPoint(void)),然后读入面表(void CCube::ReadFacet(void)//面表),在这个类中定义Draw函数(void CCube::Draw(CDC* pDC))用来绘制出立方体,在这个里面,利用循环来绘制出一个面(for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环),然后在循环来绘制出六个面(for(int nFacet = 0; nFacet < 6; nFacet++)//面循环);
2.背面剔除算法消隐:
这个算法是针对凸多边形而设计的,其表面要么完全可见,要么不可见。背面剔除算法的的关键是给出测试其每个表面可见性的判别式,可以根据其外法向量N与视向量V(从表面上的一个顶点指向视点)的夹角@来进行可见性检测。在这个算法中,主要用到的就是三维向量类Vectors3,主要成员函数为点乘(数量积)函数和叉乘(向量积)函数。使用背面剔除算法对立方体进行消隐,主要是对条件的判定(if (DotProduct(ViewVector, FacetNormal)>=0)//背面剔除),满足此条件,就可以看见,从而达到了消隐的目的;
三、实现方法
1.建立立方体类:
Cube.h:
#pragma once
#include"Projection.h"
#include"Facet.h"
#include"Triangle.h"
#include"Vector3.h"
#include"Lighting.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 Draw(CDC* pDC);//绘制图形
private:
CP3 P[8];//点表
CFacet F[6];//面表
CProjection projection;//投影
CLighting* pLight;//光照
CMaterial* pMaterial;//材质
};
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::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[4].Number = 4;F[4].Index[0] = 2;F[4].Index[1] = 3;F[4].Index[2] = 7;F[4].Index[3] = 6;//顶面
F[5].Number = 4;F[5].Index[0] = 0;F[5].Index[1] = 1;F[5].Index[2] = 5;F[5].Index[3] = 4;//底面
}
void CCube::Draw(CDC* pDC)
{
CP3 ScreenPoint[4];//三维屏幕点
CTriangle* pFill = new CTriangle; //申请内存
for(int nFacet = 0; nFacet < 6; 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();
if (DotProduct(ViewVector, FacetNormal)>=0)//背面剔除
{
for (int nPoint = 0; nPoint < F[nFacet].Number; nPoint++)//顶点循环
ScreenPoint[nPoint] = projection.PerspectiveProjection3(P[F[nFacet].Index[nPoint]]);
pFill->SetPoint(ScreenPoint[0], ScreenPoint[2], ScreenPoint[3]);
pFill->GouraudShader(pDC);
pFill->SetPoint(ScreenPoint[0], ScreenPoint[1], ScreenPoint[2]);
pFill->GouraudShader(pDC);
}
}
}
2.背面剔除消隐算法:
Vectors3.h:
#pragma once
#include "P3.h"
class CVector3
{
public:
CVector3(void);
virtual ~CVector3(void);
CVector3(double x, double y, double z);//绝对向量
CVector3(const CP3 &vertex);
CVector3(const CP3 &Vertex0, const CP3 &Vertex1);//相对向量
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;
};
Vectors3.cpp:
#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 &vertex)
{
x = vertex.x;
y = vertex.y;
z = vertex.z;
}
CVector3::CVector3(const CP3 &Vertex0, const CP3 &Vertex1)//相对向量
{
x = Vertex1.x - Vertex0.x;
y = Vertex1.y - Vertex0.y;
z = Vertex1.z - Vertex0.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;
}
投影方式:
Projection.h:
#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 PerspectiveProjection2(CP3 WorldPoint);//二维透视投影
CP3 PerspectiveProjection3(CP3 WorldPoint);//三维透视投影
private:
CP3 EyePoint;//视点
double R, d;//视径和视距
};
Projection.cpp:
#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::PerspectiveProjection2(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;
}
CP3 CProjection::PerspectiveProjection3(CP3 WorldPoint)
{
CP3 ViewPoint;//观察坐标系三维点
ViewPoint.x = WorldPoint.x;
ViewPoint.y = WorldPoint.y;
ViewPoint.z = EyePoint.z - WorldPoint.z;
ViewPoint.c = WorldPoint.c;
CP3 ScreenPoint;//屏幕坐标系三维点
ScreenPoint.x = d * ViewPoint.x / ViewPoint.z;
ScreenPoint.y = d * ViewPoint.y / ViewPoint.z;
ScreenPoint.z = (ViewPoint.z - d) * d / ViewPoint.z;//Bouknight公式
ScreenPoint.c = ViewPoint.c;
return ScreenPoint;
}
在TestView.h里面定义:
protected:
CCube cube;
double Alpha, Beta;
CTransform3 transform;
BOOL bPlay;//动画按钮
int nLightSourceNumber;//光源数量
CLighting* pLight;//光照环境
CMaterial* pMaterial;//物体材质
public:
void DoubleBuffer(CDC* pDC);//双缓冲
void DrawObject(CDC* pDC);//绘制图形
void InitializeLightingScene(void);//初始化光照场景
在TestView.cpp里面定义:
// TestView.cpp: CTestView 类的实现
//
#include "pch.h"
#include "framework.h"
// SHARED_HANDLERS 可以在实现预览、缩略图和搜索筛选器句柄的
// ATL 项目中进行定义,并允许与该项目共享文档代码。
#ifndef SHARED_HANDLERS
#include "Test.h"
#endif
#include "TestDoc.h"
#include "TestView.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CTestView
IMPLEMENT_DYNCREATE(CTestView, CView)
BEGIN_MESSAGE_MAP(CTestView, CView)
// 标准打印命令
ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
ON_COMMAND(ID_GRAPH_ANIMATION, &CTestView::OnGraphAnimation)
ON_UPDATE_COMMAND_UI(ID_GRAPH_ANIMATION, &CTestView::OnUpdateGraphAnimation)
ON_WM_TIMER()
ON_WM_KEYDOWN()
END_MESSAGE_MAP()
// CTestView 构造/析构
CTestView::CTestView() noexcept
{
// TODO: 在此处添加构造代码
bPlay = FALSE;
double nEdge = 400;
cube.ReadPoint();
cube.ReadFacet();
transform.SetMatrix(cube.GetVertexArrayName(), 8);
transform.Scale(nEdge, nEdge, nEdge);
transform.Translate(-nEdge / 2, -nEdge / 2, -nEdge / 2);//平移变换
InitializeLightingScene();
cube.SetScene(pLight, pMaterial);//设置场景
}
CTestView::~CTestView()
{
if (pLight != NULL)
{
delete pLight;
pLight = NULL;
}
if (pMaterial != NULL)
{
delete pMaterial;
pMaterial = NULL;
}
}
void CTestView::InitializeLightingScene(void)//初始化光照环境
{
//设置光源属性
nLightSourceNumber = 1;//光源个数
pLight = new CLighting(nLightSourceNumber);//一维光源动态数组
pLight->LightSource[0].SetPosition(0, 0, 500);//设置光源位置坐标
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.247, 0.200, 0.075));//环境反射率
pMaterial->SetDiffuse(CRGB(0.752, 0.606, 0.226));//漫反射率
pMaterial->SetSpecular(CRGB(0.628, 0.556, 0.366));//镜面反射率
pMaterial->SetEmission(CRGB(0.0, 0.0, 0.0));//自身辐射的颜色
pMaterial->SetExponent(50);//高光指数
}
BOOL CTestView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: 在此处通过修改
// CREATESTRUCT cs 来修改窗口类或样式
return CView::PreCreateWindow(cs);
}
// CTestView 绘图
void CTestView::OnDraw(CDC* pDC)
{
CTestDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
DoubleBuffer(pDC);
}
void CTestView::DoubleBuffer(CDC* pDC)//双缓冲
{
CRect rect;//定义客户区矩形
GetClientRect(&rect);//获得客户区的大小
pDC->SetMapMode(MM_ANISOTROPIC);//pDC自定义坐标系
pDC->SetWindowExt(rect.Width(), rect.Height());//设置窗口范围
pDC->SetViewportExt(rect.Width(), -rect.Height());//设置视区范围,x轴水平向右,y轴垂直向上
pDC->SetViewportOrg(rect.Width() / 2, rect.Height() / 2);//客户区中心为原点
CDC memDC;//内存DC
memDC.CreateCompatibleDC(pDC);//创建一个与显示pDC兼容的内存memDC
CBitmap NewBitmap, *pOldBitmap;//内存中承载的临时位图
NewBitmap.CreateCompatibleBitmap(pDC, rect.Width(), rect.Height());//创建兼容位图
pOldBitmap = memDC.SelectObject(&NewBitmap);//将兼容位图选入memDC
//memDC.FillSolidRect(rect, pDC->GetBkColor());//按原来背景填充客户区,否则是黑色
memDC.SetMapMode(MM_ANISOTROPIC);//memDC自定义坐标系
memDC.SetWindowExt(rect.Width(), rect.Height());
memDC.SetViewportExt(rect.Width(), -rect.Height());
memDC.SetViewportOrg(rect.Width() / 2, rect.Height() / 2);
rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);
DrawObject(&memDC);//向memDC绘制图形
pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &memDC, -rect.Width() / 2, -rect.Height() / 2, SRCCOPY);//将内存memDC中的位图拷贝到显示pDC中
memDC.SelectObject(pOldBitmap);//恢复位图
NewBitmap.DeleteObject();//删除位图
}
void CTestView::DrawObject(CDC* pDC)//绘制图形
{
cube.Draw(pDC);
}
// CTestView 打印
BOOL CTestView::OnPreparePrinting(CPrintInfo* pInfo)
{
// 默认准备
return DoPreparePrinting(pInfo);
}
void CTestView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 添加额外的打印前进行的初始化过程
}
void CTestView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: 添加打印后进行的清理过程
}
// CTestView 诊断
#ifdef _DEBUG
void CTestView::AssertValid() const
{
CView::AssertValid();
}
void CTestView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CTestDoc* CTestView::GetDocument() const // 非调试版本是内联的
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CTestDoc)));
return (CTestDoc*)m_pDocument;
}
#endif //_DEBUG
// CTestView 消息处理程序
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::OnGraphAnimation()
{
// TODO: 在此添加命令处理程序代码
bPlay = !bPlay;
if (bPlay)//设置定时器
SetTimer(1, 150, NULL);
else
KillTimer(1);
}
void CTestView::OnTimer(UINT_PTR nIDEvent)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
Alpha = 5, Beta = 5;
transform.RotateX(Alpha);
transform.RotateY(Beta);
Invalidate(FALSE);
CView::OnTimer(nIDEvent);
}
void CTestView::OnUpdateGraphAnimation(CCmdUI *pCmdUI)
{
// TODO: 在此添加命令更新用户界面处理程序代码
if (bPlay)
pCmdUI->SetCheck(TRUE);
else
pCmdUI->SetCheck(FALSE);
}
四、实验结果分析
实验结果
实验总结
在这个实验中,刚开始的时候以为是自己设计的问题,后来找问题的时候才知道是光源坐标的问题,刚开始离得太近,但是就好像没有光源一样,所以找到了光源的问题,最后得以解决。
实验帮助
在同学的帮助下,刚开始把光源那块插进去,可以有光源了。
每次在进行实验时,不要一直自己在那里想,要在同学的帮助下,进行帮助。