OPenGL笔记--创建一个3D场景

news2024/9/25 19:20:31

文章目录

  • 一、前言
  • 二、效果展示
  • 三、详细流程
    • 3.1、World.txt文件规则
    • 3.2、加载World.txt
    • 3.3、绘制场景
    • 3.4、交互
  • 四、详细代码
  • 五、举一反三

一、前言

通过前面的学习,基本掌握了怎么绘制图形,使用纹理,接下来就来创建一个3D场景。

基本原理
一个复杂的场景肯定是由一些简单的图形,通过某种组合方式构建起来的,在OPenGL中也不例外;例如:在绘制立方体的时候,立方体也是由6个正方形围起来的;

基本图形
由于显卡在渲染三角形时效率较高,所以我们采用三角形来构建复杂的3D场景;

数据结构

  • 当您想要使用一系列的数字来完美的表达3D环境时,随着环境复杂度的上升,这个工作的难度也会随之上升;
  • 出于这个原因,我们必须将数据归类,使其具有更多的可操作性风格,在程序中添加sector结构体(区段)的定义;
  • 每个3D世界基本上可以看作是sector(区段)的集合,一个sector(区段)可以是一个房间、一个立方体、或者任意一个闭合的区间;
typedef struct tagVERTEX	//三角形的顶点
 {
	float x, y, z;					    // 3D 坐标
    float u, v;							// 纹理坐标
} VERTEX;								

typedef struct tagTRIANGLE	//三角形
{
    VERTEX vertex[3];						// VERTEX矢量数组,大小为3
}TRIANGLE;

typedef struct tagSECTOR	//Sector区段结构(三角形集合)
{
    int numtriangles;						// Sector中的三角形个数
    TRIANGLE* triangle;						// 指向三角数组的指针
} SECTOR;								

数据加载
在程序内部直接存储数据会让程序显得太过死板和无趣。从磁盘上载入世界资料,会给我们带来更多的弹性,可以让我们体验不同的世界,而不用被迫重新编译程序。另一个好处就是用户可以切换世界资料并修改它们而无需知道程序如何读入输出这些资料的。数据文件的类型我们准备使用文本格式(txt)。这样编辑起来更容易,写的代码也更少。
在这里插入图片描述


二、效果展示

在这里插入图片描述
在这里插入图片描述


三、详细流程

首先,不懂怎么创建OPenGL窗口的,可以参考:OPenGL笔记–创建一个OPenGL窗口

在窗口的基础上,我们在函数paintGL()中绘制我们的3D世界(其实就是一个贴了纹理的盒子);

之前的教程里我们都是直接在函数paintGL()中绘制,这里我们通过加载World.txt中的数据来绘制图形;

这个World.txt是一个描述一堆三角形信息的文本文件,众所周知,一个三角形由三个点构成,纹理有两个坐标,所以:

在World.txt中,采用(x, y, z, u, v)的形式来描述一个三角形顶点,例如:

NUMPOLLIES 12

// Floor 1
-3.0  0.0 -3.0 0.0 6.0
3.0  0.0  -3.0 0.0 0.0
-3.0  0.0  3.0 6.0 0.0
3.0  0.0 3.0 0.0 6.0
3.0  0.0 -3.0 6.0 6.0
-3.0  0.0  3.0 6.0 0.0

在World.txt中,最上面一行NUMPOLLIES 12记录的是此文本文件中描述三角形的个数,每3个点(对应文本文件中的三行)组成一个三角形;

3.1、World.txt文件规则

World.txt中第一行表示文本文件中描述的三角形个数:NUMPOLLIES 12

接下来,例如,我们搭建的是一个盒子,我们可以将其拆分为6个面:上、下、左、右、前、后

基本格式如下

NUMPOLLIES 12

//上
一个三角形
一个三角形

//下
一个三角形
一个三角形

//左
一个三角形
一个三角形

//右
一个三角形
一个三角形

//前
一个三角形
一个三角形

//后
一个三角形
一个三角形

例如,我们准备搭建一个661的盒子,以x-z平面为底,以原点为底部中心,如下图所示:
请添加图片描述

以底面为例进行讲解,我们将底面正方形划分为两个三角形(怎么划分不重要),分别是上三角和下三角

上三角:

(-3, 0, -3)
(3, 0, -3)
(-3, 0, 3)

下三角:

(3, 0, 3)
(3, 0, -3)
(-3, 0, 3)

现在确定了三角形的坐标,那纹理坐标怎么确定呢?纹理坐标通常在0~1之间(但是纹理坐标如果超过1就会复制)

以上三角为例:以直角为纹理坐标系原点,直角边为纹理坐标系坐标轴,纹理坐标X、Y轴需要垂直世界坐标系轴X、Y轴,如下图示:
在这里插入图片描述
所以可以得出上三角的纹理坐标为:

(0, 0)
(0, 6)
(6, 0)

三角形坐标和纹理坐标合起来就得到了World.txt中的三角形信息:

NUMPOLLIES 1

// Floor 1
-3.0  0.0  -3.0  0.0  0.0
3.0  0.0  -3.0  0.0  6.0
-3.0  0.0  3.0  6.0  0.0

3.2、加载World.txt

World.txt文件编写完成之后,通过setipWorld()函数来加载World.txt;

  • 将读取的三角形个数存储到区段结构体数组m_sector1中;
  • 将三角形信息存储到区段结构体数组m_sector1中;
void GLWidget::setupWorld()
{
    QFile file(":/world/World.txt");
    if(!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::warning(this, tr("Warning"), tr("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 %d\n", &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();
}

//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循环开始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要进行处理
}

3.3、绘制场景

在加载玩World.txt到区段结构体数组m_sector1中之后,在paintGL()中绘制;

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    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[0]);			// 选择的纹理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_TRIANGLES);					// 开始绘制三角形
            glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线

            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();						// 三角形绘制结束
    }

}

3.4、交互

绘制完成之后,我们的3D世界基本搭建完成了,然后需要添加键盘交互;

void GLWidget::keyPressEvent(QKeyEvent* e)
{
    switch (e->key()) {
        case Qt::Key_Q: {
            fullscreen = !fullscreen;
            if(fullscreen) {
                showFullScreen();
            }else {
                showNormal();
                setGeometry(500,500,640,480);
            }
            updateGL();
            break;
        }//case Qt::Key_Q

        case Qt::Key_Escape: {
            close();
        }//Qt::Key_Escape

        case Qt::Key_PageUp:
        {
            m_lookupdown-=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_PageDown:
        {
            m_lookupdown+=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_Right:
        {
            m_heading -=1.0f;
            m_yrot = m_heading;							// 向左旋转场景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右侧旋转场景
            updateGL();
            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平面移动
            updateGL();
            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平面移动
            updateGL();
            break;
        }

    }//switch (e->key())
}

四、详细代码

素材
在这里插入图片描述

World.txt

NUMPOLLIES 12

// Floor 1
-3.0  0.0 -3.0 0.0 6.0
-3.0  0.0  3.0 0.0 0.0
 3.0  0.0  3.0 6.0 0.0
-3.0  0.0 -3.0 0.0 6.0
 3.0  0.0 -3.0 6.0 6.0
 3.0  0.0  3.0 6.0 0.0

// Ceiling 1
-3.0  1.0 -3.0 0.0 6.0
-3.0  1.0  3.0 0.0 0.0
 3.0  1.0  3.0 6.0 0.0
-3.0  1.0 -3.0 0.0 6.0
 3.0  1.0 -3.0 6.0 6.0
 3.0  1.0  3.0 6.0 0.0

// Left
-3.0  1.0  -3.0 0.0 6.0
-3.0  1.0  3.0 0.0 0.0
-3.0  0.0  3.0 1.0 0.0
-3.0  0.0  3.0 0.0 6.0
-3.0  0.0  -3.0 0.0 0.0
-3.0  1.0  -3.0 1.0 0.0

// right
3.0  1.0  3.0 0.0 6.0
3.0  1.0  -3.0 0.0 0.0
3.0  0.0  -3.0 1.0 0.0
3.0  0.0  3.0 0.0 0.0
3.0  0.0  -3.0 0.0 6.0
3.0  1.0  3.0 1.0 0.0

// front
-3.0  1.0  -3.0 0.0 6.0
3.0  1.0  -3.0 0.0 0.0
3.0  0.0  -3.0 1.0 0.0
-3.0  0.0  -3.0 0.0 0.0
3.0  0.0  -3.0 0.0 6.0
-3.0  1.0  -3.0 1.0 0.0

// behind
-3.0  1.0  3.0 0.0 6.0
3.0  1.0  3.0 0.0 0.0
3.0  0.0  3.0 1.0 0.0
-3.0  0.0  3.0 0.0 0.0
3.0  0.0  3.0 0.0 6.0
-3.0  1.0  3.0 1.0 0.0
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>
#include <QKeyEvent>
#include <GL/glu.h>
#include <QMessageBox>
#include <QApplication>
#include <QTextStream>
#include <math.h>

#include <QDebug>

//继承QGLWidget得到OPenGL窗口部件类
class GLWidget : public QGLWidget
{
    Q_OBJECT

public:
    //场景描述结构体
    //==================================================================
    typedef struct tagVERTEX    // 创建顶点结构
    {
        float x, y, z;						// 3D 坐标
        float u, v;							// 纹理坐标
    } VERTEX;

    typedef struct tagTRIANGLE  // 创建三角形结构
    {
        VERTEX vertex[3];				    // VERTEX矢量数组,大小为3
    }TRIANGLE;// 命名为 TRIANGLE

    typedef struct tagSECTO     // 创建Sector区段结构
    {
        int numtriangles;					// Sector中的三角形个数
        TRIANGLE* triangle;					// 指向三角数组的指针
    } SECTOR;
    //==================================================================

public:
    GLWidget(QWidget* parent = 0, bool fs = false);
    ~GLWidget();

protected:
    /*************************************************************************************************
    QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这三个函数实现
    *************************************************************************************************/
    void initializeGL() override;           //用来初始化OPenGL窗口,可以在里面设定一些有关选项
    void paintGL() override;                //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
    void resizeGL(int w, int h) override;   //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕

    void keyPressEvent(QKeyEvent* e) override;  //Qt键盘事件处理函数

private:
    void setupWorld();  //初始化场景
    void readStr(QTextStream *stream, QString &string); //读取顶点信息
    void loadTexture(); //加载纹理

private:
    bool fullscreen;    //用来保存窗口是否处于全屏状态的变量

    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_texture[3];

};

#endif // GLWIDGET_H
#include "GLWidget.h"

const float piover180 = 0.0174532925f;

GLWidget::GLWidget(QWidget* parent, bool fs)
    : QGLWidget(parent)
{
    fullscreen = fs;

    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;

    setMinimumSize(1000,1000);               //设置窗口大小
    setWindowTitle("The first OpenGL Window");  //设置窗口标题

    if(fullscreen) {
        showFullScreen();
    }
}

GLWidget::~GLWidget()
{

}

void GLWidget::initializeGL()
{
    loadTexture();  //加载纹理

    glEnable(GL_TEXTURE_2D);    //使能纹理

    glClearColor(0.0, 0.0, 0.0, 0.0);   //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】

    glClearDepth(1.0);  //设置深度缓存

    glDepthFunc(GL_LESS); //所作深度测试的类型

    glEnable(GL_DEPTH_TEST);    //启动深度测试

    glShadeModel(GL_SMOOTH);    //启用smooth shading(阴影平滑)

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点

    setupWorld();
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    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[0]);			// 选择的纹理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_TRIANGLES);					// 开始绘制三角形
            glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线

            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 GLWidget::resizeGL(int w, int h)
{
    if(h == 0) {    //防止h为0
        h = 1;
    }

    glViewport(0, 0, (GLint)w, (GLint)h);   //重置当前的视口(Viewport)

    glMatrixMode(GL_PROJECTION);    //选择投影矩阵

    glLoadIdentity();   //重置投影矩阵

    gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.001, 1000.0 );  //建立透视投影矩阵

    glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵

    glLoadIdentity();   //重置模型观察矩阵
}

void GLWidget::keyPressEvent(QKeyEvent* e)
{
    switch (e->key()) {
        case Qt::Key_Q: {
            fullscreen = !fullscreen;
            if(fullscreen) {
                showFullScreen();
            }else {
                showNormal();
                setGeometry(500,500,640,480);
            }
            updateGL();
            break;
        }//case Qt::Key_Q

        case Qt::Key_Escape: {
            close();
        }//Qt::Key_Escape

        case Qt::Key_PageUp:
        {
            m_lookupdown-=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_PageDown:
        {
            m_lookupdown+=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_Right:
        {
            m_heading -=1.0f;
            m_yrot = m_heading;							// 向左旋转场景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右侧旋转场景
            updateGL();
            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平面移动
            updateGL();
            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平面移动
            updateGL();
            break;
        }

    }//switch (e->key())
}

void GLWidget::setupWorld()
{
    QFile file(":/world/World.txt");
    if(!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::warning(this, tr("Warning"), tr("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 %d\n", &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();
}

//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循环开始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要进行处理
}

//加载纹理
void GLWidget::loadTexture()
{
    QImage image(":/Images/Crate.bmp");
    image = image.convertToFormat(QImage::Format_RGB888);
    image = image.mirrored();
    glGenTextures(1, &m_texture[0]);// 创建纹理

    // Create Nearest Filtered Texture
    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());
}

五、举一反三

通过上面的流程,我们创建了一个小房间,它使用的纹理都是一样的,接下来我们通过使用不同的纹理,来创建一个感官更加丰富的场景;

效果展示

在这里插入图片描述

上面使用的是三角形,我们也可以使用矩形来构建:
素材

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

World1.txt

grass 1

// grass
-3.0  0.0 -3.0 0.0 6.0
3.0  0.0  -3.0 6.0 6.0
 3.0  0.0  3.0 6.0 0.0
-3.0  0.0 3.0 0.0 0.0

sky 1 

// sky
-3.0  1.0 -3.0 0.0 6.0
3.0  1.0  -3.0 6.0 6.0
 3.0  1.0  3.0 6.0 0.0
-3.0  1.0 3.0 0.0 0.0

floor 4

//floor-left
-3.0  1.0 3.0 0.0 6.0
-3.0  1.0  -3.0 6.0 6.0
 -3.0  0.0  -3.0 6.0 0.0
-3.0  0.0 3.0 0.0 0.0

//floor-right
3.0  1.0 -3.0 0.0 6.0
3.0  1.0  3.0 6.0 6.0
3.0  0.0  3.0 6.0 0.0
3.0  0.0 -3.0 0.0 0.0

//floor-front
-3.0  1.0 -3.0 0.0 6.0
3.0  1.0  -3.0 6.0 6.0
 3.0  0.0  -3.0 6.0 0.0
-3.0  0.0 -3.0 0.0 0.0

//floor-behind
-3.0  1.0 3.0 0.0 6.0
3.0  1.0  3.0 6.0 6.0
3.0  0.0  3.0 6.0 0.0
-3.0  0.0 3.0 0.0 0.0
#ifndef GLWIDGET_H
#define GLWIDGET_H

#include <QGLWidget>
#include <QKeyEvent>
#include <GL/glu.h>
#include <QMessageBox>
#include <QApplication>
#include <QTextStream>
#include <math.h>

#include <QDebug>

//继承QGLWidget得到OPenGL窗口部件类
class GLWidget : public QGLWidget
{
    Q_OBJECT

public:
    //场景描述结构体
    //==================================================================
    typedef struct tagVERTEX    // 创建顶点结构
    {
        float x, y, z;						// 3D 坐标
        float u, v;							// 纹理坐标
    } VERTEX;

    typedef struct tagTRIANGLE  // 创建三角形结构
    {
        VERTEX vertex[3];				    // VERTEX矢量数组,大小为3
    }TRIANGLE;// 命名为 TRIANGLE

    typedef struct tagRECT  // 创建四边形结构
    {
        VERTEX vertex[4];				    // VERTEX矢量数组,大小为4
    }RECT;// 命名为 RECT

    typedef struct tagSECTO     // 创建Sector区段结构
    {
        int numtriangles;					// Sector中的三角形个数
        TRIANGLE* triangle;					// 指向三角数组的指针
        RECT* rect;
    } SECTOR;
    //==================================================================

public:
    GLWidget(QWidget* parent = 0, bool fs = false);
    ~GLWidget();

protected:
    /*************************************************************************************************
    QGLWidget 类已经内置了对 OpenGL 的处理,就是通过对 initializeGL()、 paintGL()和 resizeGL()这三个函数实现
    *************************************************************************************************/
    void initializeGL() override;           //用来初始化OPenGL窗口,可以在里面设定一些有关选项
    void paintGL() override;                //用来绘制OPenGL的窗口,只要有更新发生,这个函数就会被调用
    void resizeGL(int w, int h) override;   //用来处理窗口大小变换这一事件,resizeGL()在处理完后会自动刷新屏幕

    void keyPressEvent(QKeyEvent* e) override;  //Qt键盘事件处理函数

private:
    void setupWorld();  //初始化场景
    void readStr(QTextStream *stream, QString &string); //读取顶点信息
    void loadTexture(); //加载纹理

private:
    bool fullscreen;    //用来保存窗口是否处于全屏状态的变量

    SECTOR* m_sector;
    SECTOR m_sector1;   //草地
    SECTOR m_sector2;   //天空
    SECTOR m_sector3;   //砖墙

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

    GLuint	m_texture[3];

};

#endif // GLWIDGET_H
#include "GLWidget.h"

const float piover180 = 0.0174532925f;

GLWidget::GLWidget(QWidget* parent, bool fs)
    : QGLWidget(parent)
{
    fullscreen = fs;

    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;

    setMinimumSize(1000,1000);               //设置窗口大小
    setWindowTitle("The first OpenGL Window");  //设置窗口标题

    if(fullscreen) {
        showFullScreen();
    }
}

GLWidget::~GLWidget()
{

}

void GLWidget::initializeGL()
{
    loadTexture();  //加载纹理

    glEnable(GL_TEXTURE_2D);    //使能纹理

    glClearColor(0.0, 0.0, 0.0, 0.0);   //清除屏幕时所用的颜色,rgba【0.0(最黑)~1.0(最亮)】

    glClearDepth(1.0);  //设置深度缓存

    glDepthFunc(GL_LESS); //所作深度测试的类型

    glEnable(GL_DEPTH_TEST);    //启动深度测试

    glShadeModel(GL_SMOOTH);    //启用smooth shading(阴影平滑)

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //真正精细的透视修正,告诉OPenGL我们希望进行最好的透视修正,这会十分轻微的影响性能,但使得透视图看起来好一点

    setupWorld();
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度缓存
    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[0]);			// 选择的纹理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_QUADS);					// 开始绘制三角形
            glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线

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

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

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

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

        glEnd();						// 三角形绘制结束
    }
    //------------------------------------------------------------------------------------------------
    glBindTexture(GL_TEXTURE_2D, m_texture[1]);			// 选择的纹理
    numtriangles = m_sector2.numtriangles;				// 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_QUADS);					// 开始绘制三角形
            glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线

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

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

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

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

        glEnd();						// 三角形绘制结束
    }
    //------------------------------------------------------------------------------------------------
    glBindTexture(GL_TEXTURE_2D, m_texture[2]);			// 选择的纹理
    numtriangles = m_sector3.numtriangles;				// 取得Sector1的三角形数量
    for (int loop_m = 0; loop_m < numtriangles; loop_m++)		// 遍历所有的三角形
    {
        glBegin(GL_QUADS);					// 开始绘制三角形
            glNormal3f( 0.0f, 0.0f, 1.0f);			// 指向前面的法线

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

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

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

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

        glEnd();						// 三角形绘制结束
    }
}

void GLWidget::resizeGL(int w, int h)
{
    if(h == 0) {    //防止h为0
        h = 1;
    }

    glViewport(0, 0, (GLint)w, (GLint)h);   //重置当前的视口(Viewport)

    glMatrixMode(GL_PROJECTION);    //选择投影矩阵

    glLoadIdentity();   //重置投影矩阵

    gluPerspective( 45.0, (GLfloat)w/(GLfloat)h, 0.001, 1000.0 );  //建立透视投影矩阵

    glMatrixMode(GL_MODELVIEW); //选择模型观察矩阵

    glLoadIdentity();   //重置模型观察矩阵
}

void GLWidget::keyPressEvent(QKeyEvent* e)
{
    switch (e->key()) {
        case Qt::Key_Q: {
            fullscreen = !fullscreen;
            if(fullscreen) {
                showFullScreen();
            }else {
                showNormal();
                setGeometry(500,500,640,480);
            }
            updateGL();
            break;
        }//case Qt::Key_Q

        case Qt::Key_Escape: {
            close();
        }//Qt::Key_Escape

        case Qt::Key_PageUp:
        {
            m_lookupdown-=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_PageDown:
        {
            m_lookupdown+=1.0f;
            updateGL();
            break;
        }
        case Qt::Key_Right:
        {
            m_heading -=1.0f;
            m_yrot = m_heading;							// 向左旋转场景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右侧旋转场景
            updateGL();
            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平面移动
            updateGL();
            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平面移动
            updateGL();
            break;
        }

    }//switch (e->key())
}

void GLWidget::setupWorld()
{
    QFile file(":/world/World1.txt");
    if(!file.open(QIODevice::ReadOnly))
    {
        QMessageBox::warning(this, tr("Warning"), tr("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(), "grass %d\n", &numtriangles); // 读入三角形数量

    m_sector1.rect = new RECT[numtriangles];				// 为numtriangles个三角形分配内存并设定指针
    m_sector1.numtriangles = numtriangles;					// 定义区段1中的三角形数量
    // 遍历区段中的每个三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍历所有的三角形
    {
        // 遍历三角形的每个顶点
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍历所有的顶点
        {
            readStr(&stream, oneline);				// 读入一行数据
            // 读入各自的顶点数据
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 将顶点数据存入各自的顶点
            m_sector1.rect[triloop].vertex[vertloop].x = x;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 x=x
            m_sector1.rect[triloop].vertex[vertloop].y = y;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 y=y
            m_sector1.rect[triloop].vertex[vertloop].z = z;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 z=z
            m_sector1.rect[triloop].vertex[vertloop].u = u;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 u=u
            m_sector1.rect[triloop].vertex[vertloop].v = v;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 v=v
        }
    }

    //------------------------------------------------------------------------------------------------
    readStr(&stream, oneline); // 读入一行数据
    sscanf(oneline.toLatin1().data(), "sky %d\n", &numtriangles); // 读入三角形数量

    m_sector2.rect = new RECT[numtriangles];				// 为numtriangles个三角形分配内存并设定指针
    m_sector2.numtriangles = numtriangles;					// 定义区段1中的三角形数量
    // 遍历区段中的每个三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍历所有的三角形
    {
        // 遍历三角形的每个顶点
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍历所有的顶点
        {
            readStr(&stream, oneline);				// 读入一行数据
            // 读入各自的顶点数据
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 将顶点数据存入各自的顶点
            m_sector2.rect[triloop].vertex[vertloop].x = x;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 x=x
            m_sector2.rect[triloop].vertex[vertloop].y = y;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 y=y
            m_sector2.rect[triloop].vertex[vertloop].z = z;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 z=z
            m_sector2.rect[triloop].vertex[vertloop].u = u;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 u=u
            m_sector2.rect[triloop].vertex[vertloop].v = v;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 v=v
        }
    }

    //------------------------------------------------------------------------------------------------
    readStr(&stream, oneline); // 读入一行数据
    sscanf(oneline.toLatin1().data(), "floor %d\n", &numtriangles); // 读入三角形数量

    m_sector3.rect = new RECT[numtriangles];				// 为numtriangles个三角形分配内存并设定指针
    m_sector3.numtriangles = numtriangles;					// 定义区段1中的三角形数量
    // 遍历区段中的每个三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍历所有的三角形
    {
        // 遍历三角形的每个顶点
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍历所有的顶点
        {
            readStr(&stream, oneline);				// 读入一行数据
            // 读入各自的顶点数据
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 将顶点数据存入各自的顶点
            m_sector3.rect[triloop].vertex[vertloop].x = x;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 x=x
            m_sector3.rect[triloop].vertex[vertloop].y = y;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 y=y
            m_sector3.rect[triloop].vertex[vertloop].z = z;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 z=z
            m_sector3.rect[triloop].vertex[vertloop].u = u;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 u=u
            m_sector3.rect[triloop].vertex[vertloop].v = v;	// 区段 1,  第 triloop 个三角形, 第  vertloop 个顶点, 值 v=v
        }
    }
    //------------------------------------------------------------------------------------------------

    file.close();
}

//读取World.txt中的有效数据行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循环开始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要进行处理
}

//加载纹理
void GLWidget::loadTexture()
{
    glGenTextures(1, &m_texture[0]);// 创建纹理

    QImage image1(":/Images/grass.bmp");
    image1 = image1.convertToFormat(QImage::Format_RGB888);
    image1 = image1.mirrored();
    // Create Nearest Filtered Texture
    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, image1.width(), image1.height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, image1.bits());

    QImage image2(":/Images/sky.bmp");
    image2 = image2.convertToFormat(QImage::Format_RGB888);
    image2 = image2.mirrored();
    // Create Nearest Filtered Texture
    glBindTexture(GL_TEXTURE_2D, m_texture[1]);
    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, image2.width(), image2.height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, image2.bits());

    QImage image3(":/Images/floor.bmp");
    image3 = image3.convertToFormat(QImage::Format_RGB888);
    image3 = image3.mirrored();
    // Create Nearest Filtered Texture
    glBindTexture(GL_TEXTURE_2D, m_texture[2]);
    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, image3.width(), image3.height(),
                 0, GL_RGB, GL_UNSIGNED_BYTE, image3.bits());


}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/495319.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity 软性管的实现

概述 因近期项目有要求使用到水管这种软性管的模拟&#xff0c;该篇主要说明软管的实现和应用&#xff0c;参考自&#xff1a;unity3D---实现柔软水管&#xff08;蛇的移动&#xff09;效果一&#xff08;无重力&#xff09;_unity 软管_ayouayouwei的博客-CSDN博客 效果 实现…

B/S医院手术麻醉管理系统源码:麻醉知情同意书模板

麻醉知情同意书模板 姓名:​ 性别:​ 年龄:​ 科别:​ 床号:​ 住院号:​ 疾病介绍和治疗建议: 医生已告知我因​手术&#xff0c;而接受麻醉。 1.麻醉作用的产生主要是利用麻醉药使中枢神经系统或神经中某些部位受到抑制的结果&#xff0c;临床麻醉的主要任务是: 2.为…

webpack 的打包流程

1.webpack 的打包流程 从以上5个方面来分析Webpack的打包流程&#xff1a; 初始化参数&#xff1a;这一步会从我们配置的webpack.config.js中读取到对应的配置参数和shell命令中传入的参数进行合并得到最终打包配置参数。 开始编译&#xff1a;这一步我们会通过调用webpack()方…

计算机网络基础知识(二)—— 什么是Ip地址、Mac地址、网关、子网掩码、DNS

文章目录 01 | Ip地址02 | Mac地址03 | 网关04 | 子网掩码05 | DNS06 | 总结 初次接触网络时&#xff0c;只知道电脑连接网线&#xff0c;就可以打开4399玩小游戏&#xff0c;可以登录QQ和朋友聊天&#xff1b; 再次接触网络时&#xff0c;知道了怎么查看自己电脑的网络情况&am…

06 - 5 生产者消费者模式

架构演进 介绍 同步调用变成异步调用生产数据与消费数据分离协调不同处理速度 生产者 系统运转的动力为下一个环节产生待处理的工作/数据与消费者的关系 重点在如何将数据发送到容器对消费者无依赖不关注消费者的how/when 发送顺序 消费者 容器 平衡 与EDA对比 消费策略 优点…

我发布了自己第一个由ChatGPT辅助开发的开源项目goattribute

需求产生 前两天在工作过程中又遇到了一直以来困惑我的一个问题&#xff0c;就是Go配置项的管理问题。 在开发一个新项目的时候&#xff0c;往往涉及到配置项的管理。个人小项目可能会通过配置文件来传入、环境变量来传入&#xff0c;也可能通过命令行参数来传入&#xff0c;公…

代码随想录 LeetCode数组篇 长度最小的子数组

文章目录 &#xff08;中等&#xff09;209. 长度最小的子数组&#xff08;中等&#xff09;904. 水果成篮&#xff08;困难&#xff09;76. 最小夫覆盖子串 &#xff08;中等&#xff09;209. 长度最小的子数组 我的思路&#xff1a;双指针p和q&#xff0c;滑动窗口的思想 每…

牛客练习赛111 D青蛙兔子的约会

题目链接 示例1 输入 3 3 4 10 1 2 2 4 5 1 1 3 5 11 1 1 输出 YES NO NO 说明 第一问&#xff0c;青蛙晚上向右跳1次&#xff0c;白天无法与兔子相遇。青蛙向右跳2次&#xff0c;也就是2a6的距离&#xff0c;白天兔子向左跳1次&#xff0c;可以相遇。所以在跳[1,2]次中&#…

app持续交付实战

app持续交付实战 一、学习目标二、优势三、子任务拆分四、环境依赖1、安卓 SDK2、安卓设备&#xff08;真机 or 模拟器&#xff09;3、Appium 自动化测试4、JDK5、Python3环境6、allure-commandline工具7、allure插件 五、实战任务&#xff1a;串行执行 Jenkins Pipeline 项目1…

Python学习之批量转换图片格式和统一图片尺寸

前言 大家在工作的时候是不是都会接触到很多的图片&#xff0c;为了满足不同的需求&#xff1a; 兼容性&#xff1a;不同设备和应用程序可能支持不同的图片格式。通过转换图片格式&#xff0c;可以确保在各种设备和应用程序中都能够正确地显示图片。 文件大小&#xff1a;不…

基于matlab 从接收脉冲中提取波形参数

一、前言 现代飞机通常随身携带雷达警告接收器 &#xff08;RWR&#xff09;。RWR检测到雷达发射&#xff0c;并在雷达信号照射到飞机上时警告飞行员。RWR不仅可以检测雷达发射&#xff0c;还可以分析截获的信号并编目信号来自哪种雷达。此示例显示了 RWR 如何估计截获脉冲的参…

10倍速度开发贪吃蛇游戏之AI辅助

今天就来聊聊AI代码辅助神器&#xff0c;即便是零基础也能上手&#xff0c;因为实在是太强了&#xff0c;这以后叫程序员们怎么活啊&#xff01;话不多说&#xff0c;直接上神器 我用的是cursor,其实目前AI辅助代码不止cursor&#xff0c;还有微软家的copilot X这个根植于gith…

JVM-01-JVM知识

1-JVM内存模型 Java开发人员一般情况下&#xff0c;使用过程中&#xff0c;不用关注内存的申请和释放&#xff0c;得益于JVM自动内存分配机制&#xff0c;但是其实是个双刃剑&#xff0c;这可以提升Java开发的效率&#xff0c;但是弱化了开发人员内存管理意识&#xff0c;系统容…

四元数快速入门【Quaternion】

四元数&#xff08;Quaternion&#xff09;是用于旋转和拉伸向量的数学运算符。 本文提供了一个概述&#xff0c;以帮助理解在空间导航等应用程序中对四元数的需求。 推荐&#xff1a;用 NSDT场景设计器 快速搭建3D场景。 可以通过多种方式在空间中准确定位、移动和旋转物体。 …

U盘在电脑上读不出来怎么办?详细解决方法在这!

案例&#xff1a;u盘在电脑上读不出来 【不知道为什么&#xff0c;我把u盘插入电脑后电脑完全读不出。我也不知道到底哪里出现了问题&#xff0c;有人可以帮我解答一下吗&#xff1f;】 U盘作为我们生活中经常使用的存储工具&#xff0c;其能帮我们存储大量的文件&#xff0c…

BM54-三数之和

题目 给出一个有n个元素的数组S&#xff0c;S中是否有元素a,b,c满足abc0&#xff1f;找出数组S中所有满足条件的三元组。 数据范围&#xff1a;0≤n≤1000&#xff0c;数组中各个元素值满足 ∣val∣≤100。 空间复杂度&#xff1a;O(n^2)&#xff0c;时间复杂度 O(n^2)。 注…

React 第三方插件 —— Cron 表达式生成器(qnn-react-cron)

qnn-react-cron 可以看做 react-cron-antd 的升级版&#xff08;具体“渊源”可见文档&#xff09;&#xff0c;现有功能如下&#xff1a; &#x1f389; 全面支持 cron&#xff1a;秒、分、时、日、月、周、年 &#x1f389; 日及周条件互斥&#xff0c;自动改变响应值 &…

8年测试开发,写给1-3年功能测试的几点建议,满满硬货指导

从15年毕业到现在也从业八年了&#xff0c;普通本科毕业&#xff0c;现在一家互联网公司担任测试部门总监&#xff0c;摸爬打滚&#xff0c;坑坑洼洼也经历了不少。思绪很久决定还是写下这篇&#xff0c;希望对后进的小伙子少走一点弯路。 很多人把职场想得太美好&#xff0c;其…

学node写接口!!!

fs 可以读取文档 fs.readFild() 用于读取文件 第一个参数 路径 第二个参数 "utf8"(默认值) 第三个参数 函数 function(err , dataStr ){ 第一个参数是错误&#xff0c; 第二个参数是正确的可以拿到读取文件里面的值 } fs.writeFile() 用于创建文件添加内容 …

【LeetCode中等】1419.数青蛙

给你一个字符串 croakOfFrogs&#xff0c;它表示不同青蛙发出的蛙鸣声&#xff08;字符串 “croak” &#xff09;的组合。由于同一时间可以有多只青蛙呱呱作响&#xff0c;所以 croakOfFrogs 中会混合多个 “croak” 。 请你返回模拟字符串中所有蛙鸣所需不同青蛙的最少数目。…