第27课:阴影 (参照NeHe)
这次教程中,我们将介绍阴影的绘制,这将是一个高级的主题,请确信你已经熟练地了基本OpenGL,并熟悉蒙板缓存(我们第26课的内容)。当然,我们会一一解释,这次教程中的重点和难度,我希望你能喜欢它!
这一课中我们将介绍的阴影(影子),其效果好得有些让人不可思议,它可以变形,混合在其它的物体上,是不是很棒呢!当然,进入这一课之前,你必须对OpenGL比较了解,至少得懂得蒙板缓存了吧。还有,这一课中我们会用到部分高数和线代知识,我只会告诉大家是与哪方面的内容,详细的需要大家自己去查阅资料,当然你之前已经掌握就更好了。
程序运行时效果如下:
下面进入教程:
我们这次将在第01课的基础上修改代码,当然新增代码有不少你肯定已经懂的了(不懂请看前面教程吧),我不会再解释了。首先我们新创建一个类叫glObject,并打开globject.h文件,将类声明更改如下:
#ifndef GLOBJECT_H
#define GLOBJECT_H
#include <QWidget>
#include <QGLWidget>
struct sPoint //3D顶点结构体
{
GLfloat x, y, z;
};
struct sPlaneEq //平面结构体(平面方程为ax+by+cz+d=0)
{
GLfloat a, b, c, d;
};
struct sPlane //三角形面结构体
{
unsigned int p[3]; //三角形面的三个顶点的编号
sPoint normals[3]; //三角形面的法线
int neigh[3]; //与三角形三条边相邻的面的编号
sPlaneEq planeEq; //三角形所在平面的平面方程
bool visible; //指明这个三角形是否面向光源
};
class glObject //产生阴影的模型
{
public:
glObject(QString filename);
void draw(); //绘制模型
void castShadow(GLfloat *lightPos); //绘制阴影
private:
void readData(QString filename); //读取模型数据
void calPlane(sPlane &plane); //计算平面方程的参数
void setConnectivity(); //设置相邻平面信息
void doShadowPass(GLfloat *lightPos); //绘制阴影边界的投影
private:
int nPoints; //模型的顶点数
QVector<sPoint> vPoints; //储存顶点的向量
int nPlanes; //模型的三角形面数
QVector<sPlane> vPlanes; //储存三角形面的向量
};
#endif // GLOBJECT_H
可以看到,我们在声明glObject之前,先定义了sPoint、sPlaneEq、sPlane三个结构体,依次代表3D顶点、平面方程、三角形面,结构体包含的内容大家自己看注释吧。然后我们声明glObject类,我们有nPoints、vPoints、nPlanes、vPlanes四个数据成员,依次指模型的顶点数、储存顶点的向量、三角形面数、储存三角形面的向量,说简单点,四个变量就代表模型的点和面的数据。
然后是3个public函数和4个private函数的声明,这些函数都是我们完成模型绘制和阴影绘制的重点。
我们打开object.cpp文件,加上声明#include <GL/glu.h>、#include <QFile>、#include <QTextStream>。我们先来看与模型数据读取以及初始化设置相关的readData()、setConnectivity()、calPlane()、glObject()(构造函数)等函数。四个函数下面我会分开解释,具体代码如下:
void glObject::readData(QString filename) //读取模型数据
{
QFile file(filename);
file.open(QIODevice::ReadOnly | QIODevice::Text);//将要读入数据的文本打开
QTextStream in(&file); //创建文本流
in >> nPoints; //读取模型的顶点数
vPoints.push_back(sPoint());
for (int i=0; i<nPoints; i++) //循环读取每个顶点数据
{
sPoint tPoint;
in >> tPoint.x >> tPoint.y >> tPoint.z;
vPoints.push_back(tPoint);
}
in >> nPlanes; //读取模型的三角形面数
for (int i=0; i<nPlanes; i++) //循环读取每个三角形面数据
{
sPlane tPlane;
in >> tPlane.p[0] >> tPlane.p[1] >> tPlane.p[2]
>> tPlane.normals[0].x >> tPlane.normals[0].y >> tPlane.normals[0].z
>> tPlane.normals[1].x >> tPlane.normals[1].y >> tPlane.normals[1].z
>> tPlane.normals[2].x >> tPlane.normals[2].y >> tPlane.normals[2].z;
//初始化三角形面的邻面编号为-1(表示未设置或没有邻面)
tPlane.neigh[0] = tPlane.neigh[1] = tPlane.neigh[2] = -1;
vPlanes.push_back(tPlane);
}
file.close();
}
<pre name="code" class="cpp">void glObject::setConnectivity() //设置相邻平面信息
{
for (int i=0; i<nPlanes-1; i++) //对于模型中的每一个面A
{
for (int j=i+1; j<nPlanes; j++) //对于除了此面的其它面B
{
for (int ki=0; ki<3; ki++) //对于A中的每一条边(当前顶点与下一顶点组成一条边)
{
if (vPlanes[i].neigh[ki] == -1) //如果这条边的邻面没有被设置
{
for (int kj=0; kj<3; kj++) //对于B中的每一条边
{
int p1i = ki;
int p1j = kj;
int p2i = (ki+1)%3;
int p2j = (kj+1)%3;
p1i = vPlanes[i].p[p1i]; //A当前顶点编号