 |
第27课 |
 |
 |
影子:
这是一个高级的主题,请确信你已经熟练的掌握了基本的OpenGL,并熟悉蒙板缓存。当然它会给你留下深刻的印象的。 |
|
 |
 |
欢迎来到另一个有些复杂的课程,阴影。这一课的效果好的有些让人不可思议,阴影可以变形,混合在其他的物体上。
这一课要求你必须对OpenGL比较了解,它假设你知道许多OpenGL的知识,你必须知道蒙板缓存,基本的OpenGL步骤。如果你对这些不太熟悉,我建议你可以看看前面的教程。当然,在这一课里,我们用到了很多数学知识,请准备好一本数学手册在你的身边。
首先我们定义阴影体可以延伸的距离。 |
 |
#define INFINITY 100
 |
下面定义一个3D顶点结构 |
 |
struct sPoint
{
GLfloat x, y, z;
};
 |
定义一个平面结构 |
 |
struct sPlaneEq
{
GLfloat a, b, c, d;
};
 |
下面定义一个用来投影的三角形的结构
- 3个整形索引指定了模型中三角形的三个顶点
- 第二个变量指定了三角形面的法线
- 平面方程描述了三角所在的平面
- 临近的3个顶点索引,指定了与这个三角形相邻的三个顶点
- 最后一个变量指定这个三角形是否投出阴影
|
 |
struct sPlane
{
unsigned int p[3];
sPoint normals[3];
unsigned int neigh[3];
sPlaneEq PlaneEq;
bool visible;
};
 |
最后我们用下面的结构描述一个产生阴影的物体。 |
 |
struct glObject{
GLuint nPlanes, nPoints;
sPoint points[100];
sPlane planes[200];
};
 |
下面的代码用来读取模型,它的代码本身就解释了它的功能。它从文件中读取数据,并把顶点和索引存储在上面定义的结构中,并把所有的临近顶点初始化为-1,它代表这没有任何顶点与它相邻,我们将在以后计算它。 |
 |
bool readObject( const char *filename, glObject*o)
{
FILE *file;
unsigned int i;
file = fopen(st, "r");
if (!file) return FALSE;
fscanf(file, "%d", &(o->nPoints));
for (i=1;i<=o->nPoints;i++){
fscanf(file, "%f", &(o->points[i].x));
fscanf(file, "%f", &(o->points[i].y));
fscanf(file, "%f", &(o->points[i].z));
}
fscanf(file, "%d", &(o->nPlanes));
for (i=0;inPlanes;i++){
fscanf(file, "%d", &(o->planes[i].p[0]));
fscanf(file, "%d", &(o->planes[i].p[1]));
fscanf(file, "%d", &(o->planes[i].p[2]));
fscanf(file, "%f", &(o->planes[i].normals[0].x));
fscanf(file, "%f", &(o->planes[i].normals[0].y));
fscanf(file, "%f", &(o->planes[i].normals[0].z));
fscanf(file, "%f", &(o->planes[i].normals[1].x));
fscanf(file, "%f", &(o->planes[i].normals[1].y));
fscanf(file, "%f", &(o->planes[i].normals[1].z));
fscanf(file, "%f", &(o->planes[i].normals[2].x));
fscanf(file, "%f", &(o->planes[i].normals[2].y));
fscanf(file, "%f", &(o->planes[i].normals[2].z));
}
return true;
}
 | 现在从setConnectivity函数开始,事情变得越来越复杂了,这个函数用来查找每个面的相邻的顶点,下面是它的伪代码:
|  |
对于模型中的每一个面A
对于面A中的每一条边
如果我们不只到这条边相邻的顶点
那么对于模型中除了面A外的每一个面B
对于面B中的每一条边
如果面A的边和面B的边是同一条边,那么这两个面相邻
设置面A和面B的相邻属性
 |
下面的代码完成上面伪代码中最后两行的内容,你先获得每个面中边的两个顶点,然后检测他们是否相邻,如果是则设置各自的相邻顶点信息 |
 |
int vertA1 = pFaceA->vertexIndices[edgeA];
int vertA2 = pFaceA->vertexIndices[( edgeA+1 )%3];
int vertB1 = pFaceB->vertexIndices[edgeB];
int vertB2 = pFaceB->vertexIndices[( edgeB+1 )%3];
if (( vertA1 == vertB1 && vertA2 == vertB2 ) || ( vertA1 == vertB2 && vertA2 == vertB1 ))
{
pFaceA->neighbourIndices[edgeA] = faceB;
pFaceB->neighbourIndices[edgeB] = faceA;
edgeFound = true;
break;
}
 |
完整的SetConnectivity函数的代码如下 |
 |
inline void SetConnectivity(glObject *o){
unsigned int p1i, p2i, p1j, p2j;
unsigned int P1i, P2i, P1j, P2j;
unsigned int i,j,ki,kj;
for(i=0;inPlanes-1;i++)
{
for(j=i+1;jnPlanes;j++)
{
for(ki=0;ki<3;ki++)
{
if(!o->planes[i].neigh[ki])
{
for(kj=0;kj<3;kj++)
{
p1i=ki;
p1j=kj;
p2i=(ki+1)%3;
p2j=(kj+1)%3;
p1i=o->planes[i].p[p1i];
p2i=o->planes[i].p[p2i];
p1j=o->planes[j].p[p1j];
p2j=o->planes[j].p[p2j];
P1i=((p1i+p2i)-abs(p1i-p2i))/2;
P2i=((p1i+p2i)+abs(p1i-p2i))/2;
P1j=((p1j+p2j)-abs(p1j-p2j))/2;
P2j=((p1j+p2j)+abs(p1j-p2j))/2;
if((P1i==P1j) && (P2i==P2j))
{
o->planes[i].neigh[ki] = j+1;
o->planes[j].neigh[kj] = i+1;
}
}
}
}
}
}
}
 |
下面的函数用来绘制模型 |
 |
void drawObject( const ShadowedObject& object )
{
glBegin( GL_TRIANGLES );
for ( int i = 0; i < object.nFaces; i++ )
{
const Face& face = object.pFaces[i];
for ( int j = 0; j < 3; j++ )
{
const Point3f& vertex = object.pVertices[face.vertexIndices[j]];
glNormal3f( face.normals[j].x, face.normals[j].y, face.normals[j].z );
glVertex3f( vertex.x, vertex.y, vertex.z );
}
}
glEnd();
}
 |
下面的函数用来计算平面的方程参数 |
 |
void calculatePlane( const ShadowedObject& object, Face& face )
{
const Point3f& v1 = object.pVertices[face.vertexIndices[0]];
const Point3f& v2 = object.pVertices[face.vertexIndices[1]];
const Point3f& v3 = object.pVertices[face.vertexIndices[2]];
face.planeEquation.a = v1.y*(v2.z-v3.z) + v2.y*(v3.z-v1.z) + v3.y*(v1.z-v2.z);
face.planeEquation.b = v1.z*(v2.x-v3.x) + v2.z*(v3.x-v1.x) + v3.z*(v1.x-v2.x);
face.planeEquation.c = v1.x*(v2.y-v3.y) + v2.x*(v3.y-v1.y) + v3.x*(v1.y-v2.y);
face.planeEquation.d = -( v1.x*( v2.y*v3.z - v3.y*v2.z ) +
v2.x*(v3.y*v1.z - v1.y*v3.z) +
v3.x*(v1.y*v2.z - v2.y*v1.z) );
}
 |
你还可以呼吸么?好的,我们继续:) 接下来你将学习如何去投影,castShadow函数几乎用到了所有OpenGL的功能,完成这个函数后,把它传递到doShadowPass函数来通过两个渲染通道绘制出阴影.
首先,我们看看哪些面面对着灯光,我们可以通过灯光位置和平面方程计算出.如果灯光到平面的位置大于0,则位于灯光的上方,否则位于灯光的下方(如果有什么问题,翻一下你高中的解析几何). |
 |
void castShadow( ShadowedObject& object, GLfloat *lightPosition )
{
for ( int i = 0; i < object.nFaces; i++ )
{
const Plane& plane = object.pFaces[i].planeEquation;
GLfloat side = plane.a*lightPosition[0]+
plane.b*lightPosition[1]+
plane.c*lightPosition[2]+
plane.d;
if ( side > 0 )
object.pFaces[i].visible = true;
else
object.pFaces[i].visible = false;
}
 |
下面设置必要的状态来渲染阴影.
首先,禁用灯光和绘制颜色,因为我们不计算光照,这样可以节约计算量.
接着,设置深度缓存,深度测试还是需要的,但我们不希望我们的阴影体向实体一样具有深度,所以关闭深度缓存.
最后我们启用蒙板缓存,让阴影体的位置在蒙板中被设置为1. |
 |
glDisable( GL_LIGHTING );
glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE );
glDepthFunc( GL_LEQUAL );
glDepthMask( GL_FALSE );
glEnable( GL_STENCIL_TEST );
glStencilFunc( GL_ALWAYS, 1, 0xFFFFFFFFL );
 |
现在到了阴影被实际渲染得地方了,我们使用了下面提到的doShadowPass函数,它用来绘制阴影体的边界面.我们通过两个步骤来绘制阴影体,首先使用前向面增加阴影体在蒙板缓存中的值,接着使用后向面减少阴影体在蒙板缓存中的值. |
 |
glFrontFace( GL_CCW );
glStencilOp( GL_KEEP, GL_KEEP, GL_INCR );
doShadowPass( object, lightPosition );
glFrontFace( GL_CW );
glStencilOp( GL_KEEP, GL_KEEP, GL_DECR );
doShadowPass( object, lightPosition );
 |
为了更好的理解这两个步骤,我建议你把第二步注释掉看看效果,如下所示:
 |
 |
图 1: 步骤1 |
图 2: 步骤2 |
最后一步就是把阴影体所在的位置绘制上阴影的颜色 |
 |
glFrontFace( GL_CCW );
glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
glColor4f( 0.0f, 0.0f, 0.0f, 0.4f );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
glStencilFunc( GL_NOTEQUAL, 0, 0xFFFFFFFFL );
glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
glPushMatrix();
glLoadIdentity();
glBegin( GL_TRIANGLE_STRIP );
glVertex3f(-0.1f, 0.1f,-0.10f);
glVertex3f(-0.1f,-0.1f,-0.10f);
glVertex3f( 0.1f, 0.1f,-0.10f);
glVertex3f( 0.1f,-0.1f,-0.10f);
glEnd();
glPopMatrix();
}
 |
下面的部分我们绘制构成阴影体边界的四边形,当我们循环所有的三角形面的时候,我们检测它是否是边界边,如果是我们绘制从灯光到这个边界边的射线,并衍生它用来构成四边形.
这里要用一个蛮力,我们检测物体模型中每一个三角形面,找出其边界并连接灯光到边界的直线,把直线延长出一定的距离,构成阴影体.
下面的代码完成这些功能,它看起来并没有想象的复杂. |
 |
void doShadowPass(glObject *o, float *lp)
{
unsigned int i, j, k, jj;
unsigned int p1, p2;
sPoint v1, v2;
for (i=0; inPlanes;i++)
{
if (o->planes[i].visible)
{
for (j=0;j<3;j++)
{
k = o->planes[i].neigh[j];
if ((!k) || (!o->planes[k-1].visible))
{
p1 = o->planes[i].p[j];
jj = (j+1)%3;
p2 = o->planes[i].p[jj];
v1.x = (o->points[p1].x - lp[0])*100;
v1.y = (o->points[p1].y - lp[1])*100;
v1.z = (o->points[p1].z - lp[2])*100;
v2.x = (o->points[p2].x - lp[0])*100;
v2.y = (o->points[p2].y - lp[1])*100;
v2.z = (o->points[p2].z - lp[2])*100;
glBegin(GL_TRIANGLE_STRIP);
glVertex3f(o->points[p1].x,
o->points[p1].y,
o->points[p1].z);
glVertex3f(o->points[p1].x + v1.x,
o->points[p1].y + v1.y,
o->points[p1].z + v1.z);
glVertex3f(o->points[p2].x,
o->points[p2].y,
o->points[p2].z);
glVertex3f(o->points[p2].x + v2.x,
o->points[p2].y + v2.y,
o->points[p2].z + v2.z);
glEnd();
}
}
}
}
}
 |
既然我们已经能绘制阴影了,那么我们开始绘制我们的场景吧 |
 |
bool drawGLScene()
{
GLmatrix16f Minv;
GLvector4f wlp, lp;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -20.0f);
glLightfv(GL_LIGHT1, GL_POSITION, LightPos);
glTranslatef(SpherePos[0], SpherePos[1], SpherePos[2]);
gluSphere(q, 1.5f, 32, 16);
 |
下面我们计算灯光在物体坐标系中的位置 |
 |
glLoadIdentity();
glRotatef(-yrot, 0.0f, 1.0f, 0.0f);
glRotatef(-xrot, 1.0f, 0.0f, 0.0f);
glTranslatef(-ObjPos[0], -ObjPos[1], -ObjPos[2]);
glGetFloatv(GL_MODELVIEW_MATRIX,Minv);
lp[0] = LightPos[0];
lp[1] = LightPos[1];
lp[2] = LightPos[2];
lp[3] = LightPos[3];
VMatMult(Minv, lp);
 |
下面绘制房间,物体和它的阴影 |
 |
glLoadIdentity();
glTranslatef(0.0f, 0.0f, -20.0f);
DrawGLRoom();
glTranslatef(ObjPos[0], ObjPos[1], ObjPos[2]);
glRotatef(xrot, 1.0f, 0.0f, 0.0f);
glRotatef(yrot, 0.0f, 1.0f, 0.0f);
DrawGLObject(obj);
CastShadow(&obj, lp);
 |
下面的代码绘制一个黄色的球代表了灯光的位置 |
 |
glColor4f(0.7f, 0.4f, 0.0f, 1.0f);
glDisable(GL_LIGHTING);
glDepthMask(GL_FALSE);
glTranslatef(lp[0], lp[1], lp[2]);
gluSphere(q, 0.2f, 16, 8);
glEnable(GL_LIGHTING);
glDepthMask(GL_TRUE);
 |
最后设置物体的控制 |
 |
xrot += xspeed;
yrot += yspeed;
glFlush();
return TRUE;
}
 |
绘制房间墙面 |
 |
void DrawGLRoom()
{
glBegin(GL_QUADS);
glNormal3f(0.0f, 1.0f, 0.0f);
glVertex3f(-10.0f,-10.0f,-20.0f);
glVertex3f(-10.0f,-10.0f, 20.0f);
glVertex3f( 10.0f,-10.0f, 20.0f);
glVertex3f( 10.0f,-10.0f,-20.0f);
glNormal3f(0.0f,-1.0f, 0.0f);
glVertex3f(-10.0f, 10.0f, 20.0f);
glVertex3f(-10.0f, 10.0f,-20.0f);
glVertex3f( 10.0f, 10.0f,-20.0f);
glVertex3f( 10.0f, 10.0f, 20.0f);
glNormal3f(0.0f, 0.0f, 1.0f);
glVertex3f(-10.0f, 10.0f,-20.0f);
glVertex3f(-10.0f,-10.0f,-20.0f);
glVertex3f( 10.0f,-10.0f,-20.0f);
glVertex3f( 10.0f, 10.0f,-20.0f);
glNormal3f(0.0f, 0.0f,-1.0f);
glVertex3f( 10.0f, 10.0f, 20.0f);
glVertex3f( 10.0f,-10.0f, 20.0f);
glVertex3f(-10.0f,-10.0f, 20.0f);
glVertex3f(-10.0f, 10.0f, 20.0f);
glNormal3f(1.0f, 0.0f, 0.0f);
glVertex3f(-10.0f, 10.0f, 20.0f);
glVertex3f(-10.0f,-10.0f, 20.0f);
glVertex3f(-10.0f,-10.0f,-20.0f);
glVertex3f(-10.0f, 10.0f,-20.0f);
glNormal3f(-1.0f, 0.0f, 0.0f);
glVertex3f( 10.0f, 10.0f,-20.0f);
glVertex3f( 10.0f,-10.0f,-20.0f);
glVertex3f( 10.0f,-10.0f, 20.0f);
glVertex3f( 10.0f, 10.0f, 20.0f);
glEnd();
}
 |
下面的函数完成矩阵M与向量V的乘法M=M*V
|
 |
void VMatMult(GLmatrix16f M, GLvector4f v)
{
GLfloat res[4];
res[0]=M[ 0]*v[0]+M[ 4]*v[1]+M[ 8]*v[2]+M[12]*v[3];
res[1]=M[ 1]*v[0]+M[ 5]*v[1]+M[ 9]*v[2]+M[13]*v[3];
res[2]=M[ 2]*v[0]+M[ 6]*v[1]+M[10]*v[2]+M[14]*v[3];
res[3]=M[ 3]*v[0]+M[ 7]*v[1]+M[11]*v[2]+M[15]*v[3];
v[0]=res[0];
v[1]=res[1];
v[2]=res[2];
v[3]=res[3];
}
 |
下面的函数用来初始化模型对象 |
 |
int InitGLObjects()
{
if (!ReadObject("Data/Object2.txt", &obj))
{
return FALSE;
}
SetConnectivity(&obj);
for ( int i=0;i < obj.nPlanes;i++)
CalcPlane(obj, &obj.planes[i]);
return TRUE;
}
 |
其他的函数我们不做过多解释了,这会分散你的注意力,好好享受阴影带给你的快感吧. 下面还有一些说明:
球体不会产生阴影,因为我们没有设置其投影.
如果你发现程序很慢,买块好的显卡吧.
最后我希望你喜欢它,如果有什么好的建议,请告诉我.
 |
版权与使用声明:
我是个对学习和生活充满激情的普通男孩,在网络上我以DancingWind为昵称,我的联系方式是[email protected],如果你有任何问题,都可以联系我。
引子
网络是一个共享的资源,但我在自己的学习生涯中浪费大量的时间去搜索可用的资料,在现实生活中花费了大量的金钱和时间在书店中寻找资料,于是我给自己起了个昵称DancingWind,其意义是想风一样从各个知识的站点中吸取成长的养料。在飘荡了多年之后,我决定把自己收集的资料整理为一个统一的资源库。
版权声明
所有DancingWind发表的内容,大多都来自共享的资源,所以我没有资格把它们据为己有,或声称自己为这些资源作出了一点贡献。故任何人都可以复制,修改,重新发表,甚至以自己的名义发表,我都不会追究,但你在做以上事情的时候必须保证内容的完整性,给后来的人一个完整的教程。最后,任何人不能以这些资料的任何部分,谋取任何形式的报酬。
发展计划
在国外,很多资料都是很多人花费几年的时间慢慢积累起来的。如果任何人有兴趣与别人共享你的知识,我很欢迎你与我联系,但你必须同意我上面的声明。
感谢
感谢我的母亲一直以来对我的支持和在生活上的照顾。
感谢我深爱的女友田芹,一直以来默默的在精神上和生活中对我的支持,她甚至把买衣服的钱都用来给我买书了,她真的是我见过的最好的女孩,希望我能带给她幸福。
源码 RAR格式 |
| <
第26课 |
第28课
> |