平面投影Demo说明文档.docx
- 文档编号:18572940
- 上传时间:2023-08-19
- 格式:DOCX
- 页数:17
- 大小:213.09KB
平面投影Demo说明文档.docx
《平面投影Demo说明文档.docx》由会员分享,可在线阅读,更多相关《平面投影Demo说明文档.docx(17页珍藏版)》请在冰点文库上搜索。
平面投影Demo说明文档
基于OpenGL的平面投影Demo说明文档
学院:
计算机学院
专业:
计算机科学与技术专业
姓名:
张王成
学号:
2120111220
邮箱:
zhangwagncheng0816@
日期:
2011年11月19日
目录
1.绪论1
2.简介1
3.平面阴影1
4.平面投影实例4
5.实例运行效果图12
1.绪论
在现实世界中,阴影无处不在。
在计算机图形的领域内,阴影的作用越来越被人们所认识。
使用阴影可以创造出很多生动的、甚至是神奇的视觉效果。
没有阴影的作用,计算机图形就会逊色很多。
通过添加阴影,其生成的图像无论是在真实感或是质量上都会得到很大的提高。
本文档中介绍了一下用OpenGL实现简单的实时阴影效果的方法。
2.简介
一个物体之所以处在阴影中,是由于在它和光源之间存在着遮挡物,也就是说它处于没有被光源直接照射到的暗区里,遮挡物(occluder)是在接受物(receiver)上投射阴影的物体。
其实可以用纹理映射的方法来产生阴影,这种方法是预先计算好的,因此不需要运行时的开销;但是当物体或光源其中一个位置发生变化时,这种方法就不能使用了。
所以这种静态阴影的应用有很大的局限性,这里要介绍的是关于实时阴影渲染方面的一种技术——平面投影算法
3.平面阴影
随着图形硬件的发展,加上人们的潜心研究,现在生成的实时阴影已经取得了很好的效果,也有很多成型的算法,下面简单的介绍一下本实例用的一些相关知识。
图3-1平面投射
最早的实时阴影是1988年Blinn提出的平面投射算法在平面上实现的,如图3-1所示。
这种阴影用途很广泛,比如场景中的地平面一般可以看成是一个平面。
如果将这个算法进行一般化,还可以适用于任意平面。
图3-2平面投影模型
在这个算法中,需要对一个三维物体进行两次绘制,这样可以得到一个物体顶点投影到一个平面上的矩阵,如图3-2左图所示,其中位于l处的光源在平面y=0上投射了一个阴影,顶点v投影到平面上,投影点位p。
首先推导x坐标的投影,这里可以使用相似三角形,然后得到下面的方程式:
(3-1)
同样地,可以得到z坐标的投影:
,而y轴的值是0。
转化成投影矩阵M如公式3-2所示。
(3-2)
很容易证明
,即M是投影矩阵。
如图3-2右图所示,一般情况下,阴影投射平面不是y=0的平面,而是平面:
。
为了找出将点v投影到点p处的投影矩阵,通过与平面的相交计算,可以得到投影点p的表达式为:
(3-3)
将这个方程式转换为一个投影矩阵,并满足
。
(3-4)
如果y=0时(取n=(010)T,d=0),那么得到的结果与公式(3-2)一样。
用这种方法我们只要简单地把这个矩阵应用到平面上进行投射阴影的物体上,然后用黑颜色或无光照对此物体进行绘制就可以对任何平面做出阴影的效果。
如果有多个平面,可以分别对每个平面都做一次。
不过,这种方法显然是没办法把阴影投影到曲面上,所以,这种方法通常称为平面投影(planarshadow)。
4.平面投影实例
/**墙结构体*/
structWall
{
vector
Vector3vNormal;/**<法向量*/
Vector3vColor;/**<颜色*/
};
/**墙四面体的信息数组*/
vector
上面这段程序定义了一个结构体,它用来保存一个墙平面的信息。
它包括该面的顶点数组、法向量和颜色。
然后创建了一个包含该结构体的向量容器。
平面投影实例类为CPlanarShadow,该类的具体定义如下:
/**从GL_Application派生出一个子类*/
classCPlanarShadow:
GLApplication
{
public:
boolInit();/**<执行所有的初始化工作,如果成功函数返回true*/
voidUninit();/**<执行所有的卸载工作*/
voidUpdate(DWORDmilliseconds);/**<执行所有的更新操作,传入的参数为两次操作经过的时间,以毫秒为单位*/
voidDraw();/**<执行所有的绘制操作*/
voidCaculateFrameRate();/**<计算帧速*/
voidPrintText();/**<输出文字信息*/
voidRenderWall(constWall&wall);/**<绘制墙四面体*/
voidRenderObjects();/**<绘制物体*/
voidRenderLight();/**<绘制光源*/
/**创建投影矩阵*/
voidCreateShadowMatrix(floatm[16],Vector3point,Vector3normal,floatlp[4]);
/**绘制物体和阴影*/
voidRender();
private:
friendclassGLApplication;/**父类为它的一个友元类,可以用来创建程序的实例*/
CPlanarShadow(constchar*class_name);/**<构造函数*/
/**用户自定义的程序变量*/
GLFontm_Font;/**<字体类*/
floatm_Fps;/**<帧速*/
};
下面分别详细的介绍一下这些函数的实现:
(1)绘制物体函数RenderObjects()
/**绘制物体*/
voidCPlanarShadow:
:
RenderObjects()
{
/**创建一个二次对象指针*/
GLUquadricObj*pObj=gluNewQuadric();
gluQuadricDrawStyle(pObj,GLU_FILL);
/**绘制球体*/
glColor4f(1.0f,0.0f,0.0f,1.0);/**<指定颜色*/
glPushMatrix();
glTranslatef(-3.0f,objPos1,4.0f);
gluSphere(pObj,0.5f,75,75);
glPopMatrix();
/**绘制球体*/
glColor4f(0.0f,1.0f,0.0f,1.0);/**<指定颜色*/
glPushMatrix();
glTranslatef(3,objPos2,4);
gluSphere(pObj,0.5f,75,75);
glPopMatrix();
/**绘制球体*/
glColor4f(0.0f,0.0f,1.0f,1.0);/**<指定颜色*/
glPushMatrix();
glTranslatef(0,objPos3,4);
gluSphere(pObj,0.5f,75,75);
glPopMatrix();
/**下面是对物体位置进行限定*/
///球体
if(objPos1<-4.5f)
{
obj1=obj1*-1;
objPos1=-4.5f;
}
if(objPos1>4.5f)
{
obj1=obj1*-1;
objPos1=4.5f;
}
///球体
if(objPos2<-4.5f)
{
obj2=obj2*-1;
objPos2=-4.5f;
}
if(objPos2>4.5f)
{
obj2=obj2*-1;
objPos2=4.5f;
}
///球体
if(objPos3<-4.5f)
{
obj3=obj3*-1;
objPos3=-4.5f;
}
if(objPos3>4.5f)
{
obj3=obj3*-1;
objPos3=4.5f;
}
/**更新位置*/
objPos1+=obj1;
objPos2+=obj2;
objPos3+=obj3;
/**恢复颜色*/
glColor4f(1.0f,1.0f,1.0f,1.0);
/**删除二次对象*/
gluDeleteQuadric(pObj);
}
上面这个函数绘制球体并对球体的位置和运动方向进行控制。
首先定义一个二次对象,并指定绘制模式为填充模式。
接着绘制3个球体,对3个球体的Y轴坐标进行限定,当小于-4.5或大于4.5时变换小球的运行方向,不断的对3个球体的Y轴位置进行更新。
最后恢复颜色为默认的白色,删除二次对象。
(2)绘制光源函数RenderLight()
/**绘制光源*/
voidCPlanarShadow:
:
RenderLight()
{
/**关闭光照*/
glDisable(GL_LIGHTING);
glPushMatrix();
GLUquadricObj*pObj=gluNewQuadric();/**<创建二次对象*/
gluQuadricDrawStyle(pObj,GLU_FILL);
///在光源位置处绘制一个黄色小球
glColor4f(1.0f,1.0f,0.0f,1.0f);
glTranslatef(lightPos[0],lightPos[1],lightPos[2]);
gluSphere(pObj,0.03f,25,25);
gluDeleteQuadric(pObj);/**<删除二次对象*/
glPopMatrix();
/**开启光照*/
glEnable(GL_LIGHTING);
/**对光源位置移动位置和方向进行更新*/
if(lightPos[0]<-5.0f)
{
direction=direction*-1;
lightPos[0]=-5.0f;
}
if(lightPos[0]>5.0f)
{
direction=direction*-1;
lightPos[0]=5.0f;
}
/**更新光源位置*/
lightPos[0]+=direction;
glLightfv(GL_LIGHT0,GL_POSITION,lightPos);
}
首先关闭光照,创建一个二次对象,然后进行模型变换将位置移动到光源位置处,绘制一个黄色小球,绘制完成后删除二次对象。
开启光照,对光源位置进行限制和更新,本例中我们让光源沿X轴左右移动,这样可以清楚的看到阴影的具体效果,最后重新制定更新后的光源的位置。
(3)CreateShadowMatrix()构造将物体投射到平面上的投影矩阵
/**创建投射矩阵*/
voidCPlanarShadow:
:
CreateShadowMatrix(floatm[16],Vector3point,Vector3normal,floatlp[4])
{
/**计算顶点到平面的距离*/
floatd=-((normal.x*point.x)+(normal.y*point.y)+(normal.z*point.z));
/**计算光源向量和法向量的点积*/
floatdot=normal.x*lp[0]+normal.y*lp[1]+normal.z*lp[2]+d*lp[3];
/**设置矩阵元素值*/
m[0]=dot-lp[0]*normal.x;
m[1]=-lp[1]*normal.x;
m[2]=-lp[2]*normal.x;
m[3]=-lp[3]*normal.x;
m[4]=-lp[0]*normal.y;
m[5]=dot-lp[1]*normal.y;
m[6]=-lp[2]*normal.y;
m[7]=-lp[3]*normal.y;
m[8]=-lp[0]*normal.z;
m[9]=-lp[1]*normal.z;
m[10]=dot-lp[2]*normal.z;
m[11]=-lp[3]*normal.z;
m[12]=-lp[0]*d;
m[13]=-lp[1]*d;
m[14]=-lp[2]*d;
m[15]=dot-lp[3]*d;
}
在该函数中,参数m保存构造的矩阵;参数point为当前一顶点;参数normal为该顶点处的法向量;参数lp为光源位置。
首先计算顶点到平面的距离。
如果平面的方程为AX+BY+CZ+D=0,其中(A,B,C)为该平面的法向量,(X,Y,Z)为平面上顶点坐标,那么可以使用公式D=-(AX+BY+CZ)来求得顶点到平面的距离。
然后计算光源向量和法向量的点积。
最后根据其中的各公式来构造矩阵m。
(4)Render()本实例最重要的一个函数,它将完成墙面的渲染,并渲染出阴影的效果。
/**渲染墙面和阴影*/
voidCPlanarShadow:
:
Render()
{
glClear(GL_STENCIL_BUFFER_BIT);/**<清除模版缓存*/
glEnable(GL_STENCIL_TEST);/**<开始模版测试*/
/**循环处理每个墙面*/
for(inti=0;i<(int)walls.size();i++)
{
glStencilFunc(GL_ALWAYS,1,1);/**<设置每个像素模版值为1*/
glStencilOp(GL_KEEP,GL_KEEP,GL_REPLACE);
/**绘制当前墙面*/
glDisable(GL_LIGHT0);
RenderWall(walls[i]);
glEnable(GL_LIGHT0);
glDisable(GL_DEPTH_TEST);/**<关闭深度测试*/
glColorMask(GL_FALSE,GL_FALSE,GL_FALSE,GL_FALSE);
/**<禁止颜色缓存写*/
glStencilFunc(GL_EQUAL,1,1);/**<绘制模版值为1的象素*/
glStencilOp(GL_KEEP,GL_KEEP,GL_INCR);/**<模版值加1*/
/**开始投射阴影*/
glPushMatrix();
/**创建投射矩阵*/
floatm[16]={0};
CreateShadowMatrix(m,walls[i].vVerts[0],walls[i].vNormal,lightPos);
glMultMatrixf(m);
/**绘制物体,得到阴影*/
glPushMatrix();
RenderObjects();
glPopMatrix();
glPopMatrix();
glColorMask(GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE);
/**<恢复颜色缓存写*/
RenderWall(walls[i]);/**<绘制除阴影外的部分*/
glEnable(GL_DEPTH_TEST);/**<开始深度测试*/
}
/**关闭模版测试*/
glDisable(GL_STENCIL_TEST);
}
该函数的调用关系如下图4-1所示。
图4-1Render()函数的调用关系图
首先清除模板缓存,并开启模板测试。
由于在平面投射产生阴影时,投影的阴影可能落在接收面之外,这时需使用模板缓冲器,将接收面绘制到屏幕和模板缓冲器之后,关闭z缓冲器,仅在绘制接收面的地方绘制投影多边形。
使用模板缓存可以将作图限制在屏幕的某些部分中进行,而不需要对所有的场景进行绘制。
比如当我们编写模拟驾驶系统时,对于驾驶室内的物体我们只需绘制一次,当汽车运行时只更新车外的场景。
对于模板测试只在有模板缓存的情况下才会发生。
如果没有模板缓存,则模板测试总能通过。
模板测试对存储于模板缓存中的像素值与参考值进行比较,根据比较的结果,对模板缓存中的值进行修改。
其函数如下:
voidglStencilFunc(GLenumfunc,Glintref,GLuintmask);
此函数为模板测试比较设置函数,利用比较函数可以对参考值(ref)和模板缓存中的值进行比较,而比较仅适用于掩码(mask)的相应位为1的位。
voidgltencilop(GLenumfail,GLenumzfail,GLenumzpass);
此函数说明,当片元通过或未通过模板测试时,如何对模板缓存中的数据进行修正。
3个参数fail、zfail、zpass可以为:
ØGL_KEEP:
保持当前值。
ØGL_ZERO:
以0替换。
ØGL_REPLACE:
以参考值替换。
ØGL_INCR:
增加该值(在0~最大无符号整数值之间)。
ØGL_DECR:
减小该值(在0~最大无符号整数值之间)。
ØGL_INVERT:
对该值按位取反。
若片元未通过模板测试,则应用fail函数。
若片元通过模板测试,但深度测试失败,则应用zfail函数。
若片元通过模板测试,且通过深度测试,或没有深度测试,则应用zpass函数。
默认情况下,3个模板操作都是GL_KEEP。
在本程序中,循环处理每个墙面(墙平面的数据定义在初始化函数Init()中)。
制定模板操作方式为测试总是通过,并将通过模板测试的像素值替换为1。
此时由于模板测试总是通过,所有该区域的模板值都将被替换为1,这样我们绘制某一特定墙面的阴影时,就可以对区域进行限定了。
关闭深度测试,并禁止颜色缓存写入是为了保证阴影区域的颜色为黑色。
随后再制定模板操作方式为只绘制模板值为1的区域,并将通过深度测试的像素的模板值加1。
根据光源位置和当前墙面上的顶点和法线构造该平面上的投影矩阵,并与当前矩阵相乘。
然后绘制要产生阴影的物体,此时即产生了阴影区域,而且该区域的模板值为2。
绘制完阴影后,恢复颜色缓存的写入,再次绘制墙面,此时将绘制除阴影区域外的部分,恢复深度测试。
最后关闭模板测试。
5.实例运行效果图
本实例的运行结果如图5-1所示。
图5-1平面投影Demo运行截图
本实例中,共绘制了上下左右和后5个平面,对每个平面都产生了投影矩阵,而且光源和3个球体都是运动的。
随着光源和球体的运动,阴影也在不断改变位置,同时墙和秋的亮度也不断变化。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 平面 投影 Demo 说明 文档