【Qt OpenGL教程】27:阴影

这篇教程详细介绍了如何在OpenGL中实现阴影效果,包括阴影边界投影、阴影锥的概念,以及利用蒙板缓存进行阴影绘制的方法。教程涉及面向灯光的面判断、邻面检查和模型数据的读取与初始化。通过实例代码展示了如何创建glObject类,以及在myglwidget类中进行矩阵运算和场景绘制,最终实现阴影与模型的结合。
摘要由CSDN通过智能技术生成

第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当前顶点编号
                  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值