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

这篇具有很好参考价值的文章主要介绍了OPenGL笔记--创建一个3D场景。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、前言

通过前面的学习,基本掌握了怎么绘制图形,使用纹理,接下来就来创建一个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笔记--创建一个3D场景


二、效果展示

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


三、详细流程

首先,不懂怎么创建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平面为底,以原点为底部中心,如下图所示:
OPenGL笔记--创建一个3D场景

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

上三角:

(-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轴,如下图示:
OPenGL笔记--创建一个3D场景
所以可以得出上三角的纹理坐标为:

(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())
}

四、详细代码

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

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());
}

五、举一反三

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

效果展示

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

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

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

World1.txt文章来源地址https://www.toymoban.com/news/detail-512619.html

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());


}

到了这里,关于OPenGL笔记--创建一个3D场景的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Three.js之创建3D场景

    【G】Three.js官方文档:https://threejs.org/docs/ Three.js是一个流行的WebGL库,官方文档提供了详细的API参考和示例,适合学习和参考。 【G】Three.js GitHub链接:https://github.com/mrdoob/three.js 这是一个流行的基于WebGL的3D图形库,提供了丰富的功能和工具,用于创建交互式的3D场景和应用。

    2024年02月14日
    浏览(81)
  • ThreeJS-3D教学一:基础场景创建

    Three.js 是一个开源的 JS 3D 图形库,用于创建和展示高性能、交互式的 3D 图形场景。它建立在 WebGL 技术之上,并提供了丰富的功能和工具,使开发者可以轻松地构建令人惊叹的 3D 可视化效果。 Three.js 提供了一套完整的工具和 API,用于创建和管理 3D 场景、几何体、纹理、光照

    2024年02月07日
    浏览(56)
  • 五、3d场景的卡片展示的创建

            在我们3d的开发中,对某一些建筑和物体进行解释说明是非常常见的现象,那么就不得不说卡片的展示了,卡片展示很友好的说明了当前物体的状态,一目了然,下面就是效果图。 它主要有两个方法来实现,大量的图片建议使用canvas来实现,少量的可以使用标签实

    2024年02月03日
    浏览(40)
  • U3D通过按钮点击实现场景切换

    1.新建UI,选择button选项,新建button;   3.新建一个空对象,挂载一个scenechange c#脚本; 4.编写脚本,1头文件using UnityEngine.SceneMangement                    2public void change() {                     scenemanager.loadscene (1)  }//括号中的数字为第2步中场景后面的数字          

    2024年02月07日
    浏览(46)
  • 使用cannon.js创建3D物理仿真场景

    本文将详细介绍使用cannon.js创建3D物理仿真场景的步骤和技巧。 cannon.js是一个开源的JavaScript物理库,用于实现3D物理仿真。它可以被用于游戏开发、机器人控制、交互式的3D应用以及其他需要物理交互的场景。 与其他物理库不同的是,cannon.js是一个非常轻量级的库,它的代码

    2024年02月06日
    浏览(35)
  • Three.js教程:第一个3D场景

    推荐:将 NSDT场景编辑器加入你3D工具链 其他工具系列: NSDT简石数字孪生 下面的代码完整展示了通过three.js引擎创建的一个三维场景,在场景中绘制并渲染了一个立方体的效果,为了大家更好的宏观了解three.js引擎, 尽量使用了一段短小但完整的代码实现一个实际的三维效果

    2023年04月12日
    浏览(66)
  • Unity 3D开发--SceneManager场景管理(异步使用同一个过渡场景)

    在U3D开发过程中经常使用到多场景的切换,有同步SceneManager.LoadScene()和异步SceneManager.LoadSceneAsync()两种方法,同步的话一般就会卡住界面直到加载完成,使用异步的话一般都做一个加载的进度条,每次切换的时候都需要一个加载动画,所以需要建一个专门的过渡加载场景来进

    2024年02月14日
    浏览(43)
  • 【Unity3D】资源文件 ② ( Unity 中场景文件简介 | 查看场景文件内容 | 场景文件相关操作 | 创建场景 | 打开场景 )

    Unity 编辑器中的 场景文件 是以 \\\" .unity \\\" 为后缀的文件 , 该文件中会记录所有 游戏物体 GameObject , 以及游戏物体的相关数据 , 如下内容都是存储在 场景文件 中的 : 游戏物体 GameObject 节点 : 在 Hierarchy 层级窗口 中 场景文件 下的各个节点 都是游戏物体 , 如 主摄像机 , 光源 , 立

    2024年02月09日
    浏览(58)
  • 计算机图形学:绘制一个3d交互场景(1)

    OpenGL作为一种图形与硬件的接口,与其他图形程序开发工具相比较,它提供了众多图形函数,直观的编程环境简化了三维图形的绘制过程,使用OpenGL搭建一个三维场景,能够通过输入设备与场景内物体交互。 豪华单间 配置环境:vs22+freeglut库 1.绘制墙体使其成为封闭空间,在

    2024年02月11日
    浏览(108)
  • 51-32 CVPR’24 | 3DSFLabelling,通过伪自动标注增强 3D 场景流估计

    24 年 2 月,鉴智机器人、剑桥大学和上海交通大学联合发布CVPR\\\'24工作,3DSFLabelling: Boosting 3D Scene Flow Estimation by Pseudo Auto-labelling。 提出 3D 场景自动标注新框架,将 3D 点云打包成具有不同运动属性的 Boxes,通过优化每个 Box 运动参数并将源点云 Warp 扭曲到目标点云中,创建了

    2024年04月09日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包