当前位置:首页 > 芯闻号 > 充电吧
[导读]这一课就要解释一个基本的3D世界"结构",以及如何在这个世界里游走。lesson9.h#ifndef LESSON9_H #define LESSON9_H #include#include#inc

这一课就要解释一个基本的3D世界"结构",以及如何在这个世界里游走。

lesson9.h

#ifndef LESSON9_H
#define LESSON9_H

#include#include#include#include//三角形本质上是由一些(两个以上)顶点组成的多边形,顶点同时也是我们的最基本的分类单位。
//顶点包含了OpenGL真正感兴趣的数据。我们用3D空间中的坐标值(x,y,z)以及它们的纹理坐标(u,v)来定义三角形的每个顶点。
typedef struct tagVERTEX	// 创建Vertex顶点结构
{
    float x, y, z;			// 3D 坐标
    float u, v;				// 纹理坐标
} VERTEX;					// 命名为VERTEX
//一个sector(区段)包含了一系列的多边形,所以下一个目标就是triangle(我们将只用三角形,这样写代码更容易些)。
typedef struct tagTRIANGLE	// 创建Triangle三角形结构
{
    VERTEX vertex[3];	    // VERTEX矢量数组,大小为3
}TRIANGLE;                  // 命名为 TRIANGLE
typedef struct tagSECTOR	// 创建Sector区段结构
{
    int numtriangles;		// Sector中的三角形个数
    TRIANGLE* triangle;		// 指向三角数组的指针
} SECTOR;					// 命名为SECTOR

const float piover180 = 0.0174532925f;

class QPainter;
class QOpenGLContext;
class QOpenGLPaintDevice;

class Lesson9 : public QWindow, QOpenGLFunctions_1_1
{
    Q_OBJECT
public:
    explicit Lesson9(QWindow *parent = 0);
    ~Lesson9();

    virtual void render(QPainter *);
    virtual void render();
    virtual void initialize();

public slots:
    void renderNow();

protected:
    void exposeEvent(QExposeEvent *);
    void resizeEvent(QResizeEvent *);
    void keyPressEvent(QKeyEvent *); // 键盘事件

private:
    void setupWorld();
    void readStr(QTextStream *stream, QString &string);
    void loadGLTexture();

private:
    QOpenGLContext *m_context;

    SECTOR m_sector1;

    GLfloat m_yrot;
    GLfloat m_xpos;
    GLfloat m_zpos;
    GLfloat m_heading;
    GLfloat m_walkbias;
    GLfloat m_walkbiasangle;
    GLfloat m_lookupdown;

    GLuint	m_filter;
    GLuint	m_texture[3];
};

#endif // LESSON9_H

lessson9.cpp

#include "lesson9.h"

#include#include#include#include#include#includeLesson9::Lesson9(QWindow *parent) :
    QWindow(parent)
  , m_context(0)
  , m_yrot(0.0f)
  , m_xpos(0.0f)
  , m_zpos(0.0f)
  , m_heading(0.0f)
  , m_walkbias(0.0f)
  , m_walkbiasangle(0.0f)
  , m_lookupdown(0.0f)
  , m_filter(0)
{
    setSurfaceType(QWindow::OpenGLSurface);
}

Lesson9::~Lesson9()
{
    glDeleteTextures(3, &m_texture[0]);
}

void Lesson9::render(QPainter *painter)
{
    Q_UNUSED(painter);
}
//显示世界
//现在区段已经载入内存,我们下一步要在屏幕上显示它。到目前为止,我们所作过的都是些简单的旋转和平移。
//但我们的镜头始终位于原点(0,0,0)处。任何一个不错的3D引擎都会允许用户在这个世界中游走和遍历,我们的这个也一样。
//实现这个功能的一种途径是直接移动镜头并绘制以镜头为中心的3D环境。这样做会很慢并且不易用代码实现。我们的解决方法如下:
//围绕原点,以与镜头相反的旋转方向来旋转世界。(让人产生镜头旋转的错觉)。
//以与镜头平移方式相反的方式来平移世界(让人产生镜头移动的错觉)。
void Lesson9::render()
{
    glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

    glViewport(0,0,(GLint)width(),(GLint)height()); // 重置当前视口
    glMatrixMode(GL_PROJECTION);                    // 选择投影矩阵
    glLoadIdentity();                               // 重置投影矩阵为单位矩阵
    gluPerspective(45.0f,(GLdouble)width()/(GLdouble)height(),0.1f,100.0f);

    glMatrixMode(GL_MODELVIEW);// 选择模型视图矩阵
    glLoadIdentity();          // 重置模型视图矩阵为单位矩阵

    GLfloat x_m, y_m, z_m, u_m, v_m;        // 顶点的临时 X, Y, Z, U 和 V 的数值
    GLfloat xtrans = -m_xpos;				// 用于游戏者沿X轴平移时的大小
    GLfloat ztrans = -m_zpos;				// 用于游戏者沿Z轴平移时的大小
    GLfloat ytrans = -m_walkbias-0.25f;		// 用于头部的上下摆动
    GLfloat sceneroty = 360.0f - m_yrot;	// 位于游戏者方向的360度角
    int numtriangles;						// 保有三角形数量的整数
    glRotatef(m_lookupdown, 1.0f, 0 ,0);    // 上下旋转
    glRotatef(sceneroty, 0, 1.0f, 0);		// 左右旋转
    glTranslatef(xtrans, ytrans, ztrans);	// 以游戏者为中心的平移场景
    glBindTexture(GL_TEXTURE_2D, m_texture[m_filter]);	   // 根据filter选择的纹理
    numtriangles = m_sector1.numtriangles;				   // 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)  // 遍历所有的三角形
    {
        glBegin(GL_TRIANGLES);					        // 开始绘制三角形
        x_m = m_sector1.triangle[loop_m].vertex[0].x;	// 第一点的 X 分量
        y_m = m_sector1.triangle[loop_m].vertex[0].y;	// 第一点的 Y 分量
        z_m = m_sector1.triangle[loop_m].vertex[0].z;	// 第一点的 Z 分量
        u_m = m_sector1.triangle[loop_m].vertex[0].u;	// 第一点的 U  纹理坐标
        v_m = m_sector1.triangle[loop_m].vertex[0].v;	// 第一点的 V  纹理坐标

        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点
        x_m = m_sector1.triangle[loop_m].vertex[1].x;	// 第二点的 X 分量
        y_m = m_sector1.triangle[loop_m].vertex[1].y;	// 第二点的 Y 分量
        z_m = m_sector1.triangle[loop_m].vertex[1].z;	// 第二点的 Z 分量
        u_m = m_sector1.triangle[loop_m].vertex[1].u;	// 第二点的 U  纹理坐标
        v_m = m_sector1.triangle[loop_m].vertex[1].v;	// 第二点的 V  纹理坐标

        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点
        x_m = m_sector1.triangle[loop_m].vertex[2].x;	// 第三点的 X 分量
        y_m = m_sector1.triangle[loop_m].vertex[2].y;	// 第三点的 Y 分量
        z_m = m_sector1.triangle[loop_m].vertex[2].z;	// 第三点的 Z 分量
        u_m = m_sector1.triangle[loop_m].vertex[2].u;	// 第二点的 U  纹理坐标
        v_m = m_sector1.triangle[loop_m].vertex[2].v;	// 第二点的 V  纹理坐标
        glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 设置纹理坐标和顶点
        glEnd();						                // 三角形绘制结束
    }
}

void Lesson9::initialize()
{
    loadGLTexture();                      // 加载纹理
    glEnable(GL_TEXTURE_2D);              // 启用纹理映射
    glShadeModel(GL_SMOOTH);              // 启用平滑着色
    glClearColor(0.0f, 0.0f, 0.0f, 0.0f); // 黑色背景
    glClearDepth(1.0f);                   // 设置深度缓存
    glEnable(GL_DEPTH_TEST);              // 启用深度测试
    glDepthFunc(GL_LEQUAL);               // 深度测试类型
    // 接着告诉OpenGL我们希望进行最好的透视修正。这会十分轻微的影响性能。但使得透视图看起来好一点。
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    setupWorld();
}

void Lesson9::renderNow()
{
    if (!isExposed())
        return;

    bool needsInitialize = false;

    if (!m_context) {
        m_context = new QOpenGLContext(this);
        m_context->setFormat(requestedFormat());
        m_context->create();

        needsInitialize = true;
    }

    m_context->makeCurrent(this);

    if (needsInitialize) {
        initializeOpenGLFunctions();
        initialize();
    }

    render();

    m_context->swapBuffers(this);
}

//载入文件
//在程序内部直接存储数据会让程序显得太过死板和无趣。从磁盘上载入世界资料,会给我们带来更多的弹性,可以让我们体验不同的世界,
//而不用被迫重新编译程序。另一个好处就是用户可以切换世界资料并修改它们而无需知道程序如何读入输出这些资料的。
//数据文件的类型我们准备使用文本格式。这样编辑起来更容易,写的代码也更少。等将来我们也许会使用二进制文件。
//问题是,怎样才能从文件中取得数据资料呢?首先,创建一个叫做SetupWorld()的新函数。把这个文件定义为file,并且使用只读方式打开文件。
//我们必须在使用完毕之后关闭文件。大家一起来看看现在的代码:
void Lesson9::setupWorld()
{
    QFile file(":/world/World.txt");
    if(!file.open(QIODevice::ReadOnly))
    {
        qDebug()<<"Can't open world file.";
        return;
    }

    QTextStream stream(&file);
    //我们对区段进行初始化,并读入部分数据
    QString oneline;		// 存储数据的字符串
    int numtriangles;		// 区段的三角形数量
    float x, y, z, u, v;	// 3D 和 纹理坐标

    readStr(&stream, oneline); // 读入一行数据
    sscanf(oneline.toLatin1().data(), "NUMPOLLIES %dn", &numtriangles); // 读入三角形数量

    m_sector1.triangle = new TRIANGLE[numtriangles];		 // 为numtriangles个三角形分配内存并设定指针
    m_sector1.numtriangles = numtriangles;					 // 定义区段1中的三角形数量
    // 遍历区段中的每个三角形
    for (int triloop = 0; triloop < numtriangles; triloop++) // 遍历所有的三角形
    {
        // 遍历三角形的每个顶点
        for (int vertloop = 0; vertloop < 3; vertloop++)	 // 遍历所有的顶点
        {
            readStr(&stream, oneline);				         // 读入一行数据
            // 读入各自的顶点数据
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 将顶点数据存入各自的顶点
            m_sector1.triangle[triloop].vertex[vertloop].x = x;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 x=x
            m_sector1.triangle[triloop].vertex[vertloop].y = y;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 y=y
            m_sector1.triangle[triloop].vertex[vertloop].z = z;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 z=z
            m_sector1.triangle[triloop].vertex[vertloop].u = u;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 u=u
            m_sector1.triangle[triloop].vertex[vertloop].v = v;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 v=v
        }
    }
    //数据文件中每个三角形都以如下形式声明:
    //X1 Y1 Z1 U1 V1
    //X2 Y2 Z2 U2 V2
    //X3 Y3 Z3 U3 V3
    file.close();
}

//将每个单独的文本行读入变量。这有很多办法可以做到。一个问题是文件中并不是所有的行都包含有意义的信息。
//空行和注释不应该被读入。我们创建了一个叫做readstr()的函数。这个函数会从数据文件中读入一个有意义的行
//至一个已经初始化过的字符串。下面就是代码:
void Lesson9::readStr(QTextStream *stream, QString &string)
{
    do
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == 'n' || string.isEmpty());
}

void Lesson9::loadGLTexture()
{
    QImage image(":/image/Crate.bmp");
    image = image.convertToFormat(QImage::Format_RGB888);
    image = image.mirrored();
    glGenTextures(3, &m_texture[0]);// 创建纹理
    // 创建近邻滤波纹理
    glBindTexture(GL_TEXTURE_2D, m_texture[0]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_NEAREST);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image.width(), image.height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());

    // 创建线性滤波纹理
    glBindTexture(GL_TEXTURE_2D, m_texture[1]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, 3, image.width(), image.height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, image.bits());

    // 创建MipMapped滤波纹理
    glBindTexture(GL_TEXTURE_2D, m_texture[2]);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST);
    gluBuild2DMipmaps(GL_TEXTURE_2D, 3, image.width(), image.height(),
                      GL_RGB, GL_UNSIGNED_BYTE, image.bits());
}

void Lesson9::exposeEvent(QExposeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
    {
        renderNow();
    }
}

void Lesson9::resizeEvent(QResizeEvent *event)
{
    Q_UNUSED(event);

    if (isExposed())
    {
        renderNow();
    }
}
//当左右方向键按下后,旋转变量yrot。
//当前后方向键按下后,我们使用sine和cosine函数重新生成镜头位置(您需要些许三角函数学的知识)。
//Piover180是一个很简单的折算因子用来折算度和弧度。
//接着您可能会问:walkbias是什么意思?这是NeHe的发明的单词。基本上就是当人行走时头部产生上下摆动的幅度。
//我们使用简单的sine正弦波来调节镜头的Y轴位置。如果不添加这个而只是前后移动的话,程序看起来就没这么棒了。
void Lesson9::keyPressEvent(QKeyEvent *event)
{
    int key=event->key();
    switch(key)
    {
    case Qt::Key_PageUp:     // 向上旋转场景
    {
        m_lookupdown-=1.0f;
        break;
    }
    case Qt::Key_PageDown:   // 向下旋转场景
    {
        m_lookupdown+=1.0f;
        break;
    }
    case Qt::Key_Right:
    {
        m_heading -=1.0f;
        m_yrot = m_heading;	 // 向左旋转场景
        break;
    }
    case Qt::Key_Left:
    {
        m_heading += 1.0f;
        m_yrot = m_heading;	// 向右侧旋转场景
        break;
    }
    case Qt::Key_Up:
    {
        m_xpos -= (float)sin(m_heading*piover180) * 0.05f;	// 沿游戏者所在的X平面移动
        m_zpos -= (float)cos(m_heading*piover180) * 0.05f;	// 沿游戏者所在的Z平面移动
        if (m_walkbiasangle >= 359.0f)					    // 如果walkbiasangle大于359度
        {
            m_walkbiasangle = 0.0f;					        // 将walkbiasangle设为0
        }
        else
        {
            m_walkbiasangle+= 10;					        // 如果walkbiasangle < 359,则增加10
        }
        m_walkbias = (float)sin(m_walkbiasangle * piover180)/20.0f; // 使游戏者产生跳跃感
        break;
    }
    case Qt::Key_Down:
    {
        m_xpos += (float)sin(m_heading*piover180) * 0.05f;	// 沿游戏者所在的X平面移动
        m_zpos += (float)cos(m_heading*piover180) * 0.05f;	// 沿游戏者所在的Z平面移动
        if (m_walkbiasangle  1,减去10
        }
        m_walkbias = (float)sin(m_walkbiasangle * piover180)/20.0f;	 // 使游戏者产生跳跃感
        break;
    }
    case Qt::Key_F:
    {
        m_filter+=1;
        if(m_filter > 2)
        {
            m_filter = 0;
        }
    }
    }
    if(key==Qt::Key_F||key==Qt::Key_PageUp||key==Qt::Key_PageDown||key==Qt::Key_Up||key==Qt::Key_Down
            ||key==Qt::Key_Right||key==Qt::Key_Left)
    {
        renderNow();
    }
}

main.cpp

#include#includeint main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QSurfaceFormat format;
    format.setSamples(16);

    Lesson9 window;
    window.setFormat(format);
    window.resize(640, 480);
    window.show();

    return app.exec();
}

运行效果


按键控制

F键:切换三种滤波方式

PageUp和PageDown:控制场景的上下角度

方向键Up和Down:控制场景的前进和后退

方向键Left和Right:控制场景的作用角度



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭