可视化报告
一、 所选题目
长方体的光照效果可视化显示。
二、可视化简介
可视化(Visualization)是利用计算机图形学和图像处理技术,将数据转换成图形或图像在屏幕上显示出来,并进行交互处理的理论、方法和技术。它涉及到计算机图形学、图像处理、计算机视觉、计算机辅助设计等多个领域,成为研究数据表示、数据处理、决策分析等一系列问题的综合技术。目前正在飞速发展的虚拟现实技术也是以图形图像的可视化技术为依托的。
最近几年计算机图形学的发展使得三维表现技术得以形成,这些三维表现技术使我们能够再现三维世界中的物体,能够用三维形体来表示复杂的信息,这种技术就是可视化(Visualization)技术。可视化技术使人能够在三维图形世界中直接对具有形体的信息进行操作,和计算机直接交流。这种技术已经把人和机器的力量以一种直觉而自然的方式加以统一,这种革命性的变化无疑将极大地提高人们的工作效率。可视化技术赋予人们一种仿真的、三维的并且具有实时交互的能力,这样人们可以在三维图形世界中用以前不可想象的手段来获取信息或发挥自己创造性的思维。机械工程师可以从二维平面图中得以解放直接进入三维世界,从而很快得到自己设计的三维机械零件模型。医生可以从病人的三维扫描图象分析病人的病灶。军事指挥员可以面对用三维图形技术生成的战场地形,指挥具有真实感的三维飞机、军舰、坦克向目标开进并分析战斗方案的效果。
更令人惊奇的是目前正在发展的虚拟现实技术,人们对计算机可视化技术的研究已经历了一个很长的历程,而且形成了许多可视化工具,其中SGI公司推出的GL三维图形库表现突出,易于使用而且功能强大。利用GL开发出来的三维应用软件颇受许多专业技术人员的喜爱,这些三维应用软件已涉及建筑、产品设计、医学、地球科学、流体力学等领域。随着计算机技术的继续发展,GL已经进一步发展成为OpenGL,OpenGL已被认为是高性能图形和交互式视景处理的标准,包括ATT公司UNIX软件实验室、IBM公司、DEC公司、SUN公司、HP公司、Microsoft公司和SGI公司在内的几家在计算机市场占领导地位的大公司都采用了OpenGL图形标准。
值得一提的是,由于Microsoft公司在Windows NT中提供OpenGL图形标准,OpenGL将在微机中广泛应用,尤其是OpenGL三维图形加速卡和微机图形工作站的推出,人们可以在微机上实现三维图形应用,如CAD设计、仿真模拟、三维游戏等,从而更有机会、更方便地使用OpenGL及其应用软件来建立自己的三维图形世界。
三、题目要求
a) 题目要求
对长方体,建立一个点光源,采用环境光和点光源漫反射光的光照模型,应用FLAT明暗处理方法,显示平行投影后的长方体光照效果。
b) 任务分析
本题目主要包括五个任务,1)长方体表面模型的建立 2)长方体的可见面判断 3)可见面的背光性判断4)可见面光照计算5)可见面光照效果显示。
其中,任务1)中,定义三维齐次坐标结构和面的结构;定义顶点表和面表,对长方体绕X轴旋转和绕Y轴旋转。
任务2)中对每一个面计算其外法向量及可见性
任务3)中对每个可见面计算其光线向量,并判断其是否为背光面。
任务4)计算每个见光面的环境光和点光源的漫反射分量。
任务5)用该面的光强显示该可见面
四、数据可视化流程
五、算法流程图
六、核心算法描述
(1)判断长方体六个面的可见性
首先计算每个面的外法向量N,视线方向为预先给定的方向eye,两个向量进行点积即可判断可见性;若点积结果>0,即cos>0,则此面可见;
点积结果<0,即cos<0,则此面不可见;若点积结果=0,即cos=0,则外法向量与视线方向垂直,长方形退化为一条直线。
(2)判断长方体六个面的向光性
先取每个面的中心点(对角线的中点)与点光源做差,得到的方向为照射此面的光线方向,根据光线方向与外法向量的点积判断每个面的向光性;
若点积结果>=0,即cos>=0,则此面为向光面;点积结果<0,即cos<0,则此面为背光面;
(3)环境光与漫反射光的颜色分量的叠加
照射到每个面的光照强度根据cos的不同而不同,因此,在计算向光性的时候将cos的值计算出,并保存在数组中,然后根据公式
计算出每个面的总光照强度,同时也计算出RGB模型的三个颜色分量,以便图形的显示。
(4)平行投影与窗视变换
平行投影:将三维的图形平行投影到XOY平面上。
窗视变换:
假定把窗口内的一点变换为视区中的一点
根据公式
和
进行窗口—视区变换,图形在视区中显示
(5)图形的显示
调用BeginPath()和EndPath()函数标记填充路径,并调用FillPath()选用当前画刷分别填充向光面(因每个面选用的画刷的颜色分量不同,而分别显示每个面的光照效果);背光面不进行填充。
七、算法步骤:
① 定义结构和变量
定义三维齐次坐标结构和面结构;定义凸多面体表面模型,顶点表为顶点结构数组,面表为面结构数组,定义光照颜色分量结构,定义环境光模型与漫反射模型;
② 变量初始化
在视图类构造函数中给顶点表赋值;给面表赋值(注意顶点序列顺序);
③ 分别对凸多面体绕X轴旋转和绕Y轴旋转。
④ 设置环境光光强,反射系数,点光源光强,点光源漫反射系数
⑤ 计算各面的可见性
由相邻两条边的叉积计算面的外法向量,由外法向量与视线方向的点积判断面的可见性,若该面可见,继续进行下面步骤,否则该面结束。
⑥ 计算每个面的中心点,即各顶点求平均;计算入射光方向,即点光源坐标-面的中心点;计算入射光线与外法向量的夹角,若大于等于90度,则点光源无法直接照射到该中心点,即该面的点光源漫反射分量为0;否则根据入射光线与法向量夹角计算该面的点光源漫反射分量;该面的光强为环境光强分量与点光源漫反射分量之和。
⑦ 对该面的四个顶点作投影平面为XOY平面的平行投影。
⑧ 对该面的四个顶点作窗口-视区变换。
⑨ 用该面的光强显示其投影后的多变形区域
八、程序代码:
(1) 定义的结构体
//定义三维齐次坐标结构
typedef struct tagHOMOCOORD
{
float x;
float y;
float z;
float w;
}HOMOCOORD;
//定义面的结构
typedef struct tagPLANE
{
int v0, v1, v2, v3;
bool bvisible;
bool HeadLight;
}PLANE;
//定义点的结构,需要浮点数的x,y
typedef struct tagMYPOINT
{
float x,y;
}MYPOINT;
//定义颜色分量结构,需要的浮点数red,green,blue
typedef struct tagCOLOR
{
float red,green,blue;
}LCOLOR;
//定义环境光模型与漫反射模型,需要变量为光强和反射系数,I_a表示环境光的光强,I_p表示漫反射光的光强;K_a表示环境光的反射系数,K_d表示漫反射光的反射系数
typedef struct tagINTENSE
{
float I_a,I_p;
LCOLOR K_a,K_d,I;
}LINTENSE;
(2) Draw3Dview的源代码
// Draw3DView.cpp : implementation of the CDraw3DView class
#include "stdafx.h"
#include "Draw3D.h"
#include "Draw3DDoc.h"
#include "Draw3DView.h"
#include "math.h"
#define ROUND(a) int(a+0.5)//四舍五入
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView
IMPLEMENT_DYNCREATE(CDraw3DView, CView)
BEGIN_MESSAGE_MAP(CDraw3DView, CView)
//{{AFX_MSG_MAP(CDraw3DView)
// NOTE - the ClassWizard will add and remove mapping macros here.
// DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG_MAP
// Standard printing commands
ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)
ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)
END_MESSAGE_MAP()
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView construction/destruction
CDraw3DView::CDraw3DView()
{
// TODO: add construction code here
}
CDraw3DView::~CDraw3DView()
{
}
BOOL CDraw3DView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: Modify the Window class or styles here by modifying
// the CREATESTRUCT cs
return CView::PreCreateWindow(cs);
}
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView drawing
void CDraw3DView::OnDraw(CDC* pDC)
{
CDraw3DDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
DrawMy3DGraphics(); //绘图函数
}
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView printing
BOOL CDraw3DView::OnPreparePrinting(CPrintInfo* pInfo)
{
// default preparation
return DoPreparePrinting(pInfo);
}
void CDraw3DView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add extra initialization before printing
}
void CDraw3DView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
// TODO: add cleanup after printing
}
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView diagnostics
#ifdef _DEBUG
void CDraw3DView::AssertValid() const
{
CView::AssertValid();
}
void CDraw3DView::Dump(CDumpContext& dc) const
{
CView::Dump(dc);
}
CDraw3DDoc* CDraw3DView::GetDocument() // non-debug version is inline
{
ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CDraw3DDoc)));
return (CDraw3DDoc*)m_pDocument;
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CDraw3DView message handlers
void CDraw3DView::DrawMy3DGraphics()
{
int i;
//形体定义
ptn=8;
pts=new HOMOCOORD[ptn];
//设置长方体
pts[0].x=1; pts[0].y=2; pts[0].z=1; pts[0].w=1;
pts[1].x=-1; pts[1].y=2; pts[1].z=1; pts[1].w=1;
pts[2].x=-1; pts[2].y=-2; pts[2].z=1; pts[2].w=1;
pts[3].x=1; pts[3].y=-2; pts[3].z=1; pts[3].w=1;
pts[4].x=1; pts[4].y=2; pts[4].z=-1; pts[4].w=1;
pts[5].x=-1; pts[5].y=2; pts[5].z=-1; pts[5].w=1;
pts[6].x=-1; pts[6].y=-2; pts[6].z=-1; pts[6].w=1;
pts[7].x=1; pts[7].y=-2; pts[7].z=-1; pts[7].w=1;
///给定义面的指针分配内存;
fn=6;
faces=new PLANE[fn];
//设置立方体各面
faces[0].v0=0; faces[0].v1=1; faces[0].v2=2; faces[0].v3=3;
faces[1].v0=4; faces[1].v1=5; faces[1].v2=1; faces[1].v3=0;
faces[2].v0=5; faces[2].v1=6; faces[2].v2=2; faces[2].v3=1;
faces[3].v0=6; faces[3].v1=7; faces[3].v2=3; faces[3].v3=2;
faces[4].v0=7; faces[4].v1=4; faces[4].v2=0; faces[4].v3=3;
faces[5].v0=7; faces[5].v1=6; faces[5].v2=5; faces[5].v3=4;
pts2D=new MYPOINT[ptn];
RotateX(30); //绕X轴逆时针旋转30度
RotateY(30); //绕Y轴逆时针旋转30度
//外法向量
//N为外法向量,eye为视线方向,light表示点光源位置;u1和u2分别是中间量
HOMOCOORD *N,eye,*u1,*u2,light;
//L_tensep为点光源强度,L_tense为镜面反射光照强度,ADD_tense为叠加后的光照强度
LINTENSE *L_tensep,L_tense,*ADD_tense;
L_tensep = new LINTENSE[fn];
ADD_tense=new LINTENSE[fn];
float cos_theta[6];
//cos_theta为入射光线与外法向量的夹角
N=new HOMOCOORD[fn];
u1=new HOMOCOORD[fn];
u2=new HOMOCOORD[fn];
eye.x=0;
eye.y=0;
eye.z=1;
light.x=1;
light.y=1;
light.z=100;
//判断每个面的可见性,并标记
for(i=0;i<fn;i++)
{
u1[i].x=pts[faces[i].v1].x-pts[faces[i].v0].x;
u1[i].y=pts[faces[i].v1].y-pts[faces[i].v0].y;
u1[i].z=pts[faces[i].v1].z-pts[faces[i].v0].z;
u2[i].x=pts[faces[i].v2].x-pts[faces[i].v1].x;
u2[i].y=pts[faces[i].v2].y-pts[faces[i].v1].y;
u2[i].z=pts[faces[i].v2].z-pts[faces[i].v1].z;
N[i].x=u1[i].y*u2[i].z-u2[i].y*u1[i].z;
N[i].y=u1[i].z*u2[i].x-u1[i].x*u2[i].z;
N[i].z=u1[i].x*u2[i].y-u1[i].y*u2[i].x;
if(N[i].x*eye.x+N[i].y*eye.y+N[i].z*eye.z > 0)
faces[i].bvisible=1;
else
faces[i].bvisible=0;
}
//判断每个面的向光性,见光面为1,背光面为0
for(i=0;i<fn;i++)
{
u1[i].x=(pts[faces[i].v0].x+pts[faces[i].v2].x)/2;
u1[i].y=(pts[faces[i].v0].y+pts[faces[i].v2].y)/2;
u1[i].z=(pts[faces[i].v0].z+pts[faces[i].v2].z)/2;
u2[i].x=light.x-u1[i].x;
u2[i].y=light.y-u1[i].y;
u2[i].z=light.z-u1[i].z;
cos_theta[i]=(N[i].x*u2[i].x+N[i].y*u2[i].y+N[i].z*u2[i].z)/(sqrt(N[i].x*N[i].x+N[i].y*N[i].y+N[i].z*N[i].z)*sqrt(u2[i].x*u2[i].x+u2[i].y*u2[i].y+u2[i].z*u2[i].z));
//计算总的入射光强与外法向量的夹角的余弦值
if(cos_theta[i] > 0) //判断面的向光性
{
faces[i].HeadLight=1;
L_tensep[i].I_p=1;
}
else
{
faces[i].HeadLight=0;
L_tensep[i].I_p=0;
cos_theta[i]=0;
}
}
//初始化已知数据,材质为金的环境光反射率和漫反射光反射率的RGB分量值
L_tense.K_a.red=0.247;
L_tense.K_d.red=0.752;
L_tense.K_a.blue=0.075;
L_tense.K_d.blue=0.226;
L_tense.K_a.green=0.200;
L_tense.K_d.green=0.606;
L_tense.I_a=1;
//计算叠加后的光照强度的RGB分量
for(i=0;i<fn;i++)
{
ADD_tense[i].I.red=L_tense.I_a*L_tense.K_a.red+L_tensep[i].I_p*L_tense.K_d.red*cos_theta[i];
ADD_tense[i].I.red=ADD_tense[i].I.red>1?1:ADD_tense[i].I.red;
ADD_tense[i].I.green=L_tense.I_a*L_tense.K_a.green+L_tensep[i].I_p*L_tense.K_d.green*cos_theta[i];
ADD_tense[i].I.green=ADD_tense[i].I.green>1?1:ADD_tense[i].I.green;
ADD_tense[i].I.blue=L_tense.I_a*L_tense.K_a.blue+L_tensep[i].I_p*L_tense.K_d.blue*cos_theta[i];
ADD_tense[i].I.blue=ADD_tense[i].I.blue>1?1:ADD_tense[i].I.blue;
}
//平行投影变换
for(i=0;i<ptn;i++)
{
pts2D[i].x=pts[i].x;
pts2D[i].y=pts[i].y;
}
//窗口-视区变换实现过程
float wxl=-3,wxr=3,wyb=-3,wyt=3;
int vxl=0,vxr=500,vyb=0,vyt=400;
// 窗口-视区变换
float a=(vxr-vxl)/(wxr-wxl);
float b=vxl-wxl*a;
float c=(vyt-vyb)/(wyt-wyb);
float d=vyb-wyb*c;
for(i=0;i<ptn;i++)
{
pts2D[i].x=a*pts2D[i].x+b;
pts2D[i].y=c*pts2D[i].y+d;
}
//图形显示
CClientDC dc(this);
CBrush NewBrush,*pOldBrush;
for(i=0;i<fn;i++)
{
dc.BeginPath();
NewBrush.CreateSolidBrush(RGB(ROUND(ADD_tense[i].I.red*255),ROUND(ADD_tense[i].I.green*255),ROUND(ADD_tense[i].I.blue*255)));
pOldBrush=dc.SelectObject(&NewBrush);
PLANE f=faces[i];
if(faces[i].HeadLight==1)
{
dc.MoveTo(pts2D[f.v0].x,pts2D[f.v0].y);
dc.LineTo (pts2D[f.v1].x,pts2D[f.v1].y);
dc.LineTo (pts2D[f.v2].x,pts2D[f.v2].y);
dc.LineTo (pts2D[f.v3].x,pts2D[f.v3].y);
dc.LineTo (pts2D[f.v0].x,pts2D[f.v0].y);
}
dc.EndPath();
dc.FillPath();
}
}
void CDraw3DView::RotateX(int angle) //绕x轴逆时针旋转
{
float a=angle*PI/180;
for(int i=0;i<ptn;i++)
{
float y,z;
y=pts[i].y; z=pts[i].z;
pts[i].y=y*cos(a)-z*sin(a);
pts[i].z=y*sin(a)+z*cos(a);
}
}
void CDraw3DView::RotateY(int angle) //绕y轴逆时针旋转
{
float b=angle*PI/180;
for(int i=0;i<ptn;i++)
{
float x,z;
x=pts[i].x; z=pts[i].z;
pts[i].x=x*cos(b)+z*sin(b);
pts[i].z=-x*sin(b)+z*cos(b);
}
}
九、程序运行结果截图:
长方体光照后的效果显示:
十、参考文献
[1].孔令德著.计算机图形学基础教程 (第二版) [M].清华大学
出版社.2013.03.
[2].陈为张嵩等著.数据可视化的基本原理与方法[M].科学
出版社.2013.06.
[3].谭浩强.C++面向对象程序设计[M].北京:清华大学出版
社,2006
[4].孔令德,叶瑶,杨慧炯. C++程序设计案列精编[M].北京:
中国铁道工业出版社,2004