国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景

這篇具有很好參考價(jià)值的文章主要介紹了OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

一、前言

通過前面的學(xué)習(xí),基本掌握了怎么繪制圖形,使用紋理,接下來就來創(chuàng)建一個(gè)3D場(chǎng)景。

基本原理
一個(gè)復(fù)雜的場(chǎng)景肯定是由一些簡(jiǎn)單的圖形,通過某種組合方式構(gòu)建起來的,在OPenGL中也不例外;例如:在繪制立方體的時(shí)候,立方體也是由6個(gè)正方形圍起來的;

基本圖形
由于顯卡在渲染三角形時(shí)效率較高,所以我們采用三角形來構(gòu)建復(fù)雜的3D場(chǎng)景;

數(shù)據(jù)結(jié)構(gòu)

  • 當(dāng)您想要使用一系列的數(shù)字來完美的表達(dá)3D環(huán)境時(shí),隨著環(huán)境復(fù)雜度的上升,這個(gè)工作的難度也會(huì)隨之上升;
  • 出于這個(gè)原因,我們必須將數(shù)據(jù)歸類,使其具有更多的可操作性風(fēng)格,在程序中添加sector結(jié)構(gòu)體(區(qū)段)的定義;
  • 每個(gè)3D世界基本上可以看作是sector(區(qū)段)的集合,一個(gè)sector(區(qū)段)可以是一個(gè)房間、一個(gè)立方體、或者任意一個(gè)閉合的區(qū)間;
typedef struct tagVERTEX	//三角形的頂點(diǎn)
 {
	float x, y, z;					    // 3D 坐標(biāo)
    float u, v;							// 紋理坐標(biāo)
} VERTEX;								

typedef struct tagTRIANGLE	//三角形
{
    VERTEX vertex[3];						// VERTEX矢量數(shù)組,大小為3
}TRIANGLE;

typedef struct tagSECTOR	//Sector區(qū)段結(jié)構(gòu)(三角形集合)
{
    int numtriangles;						// Sector中的三角形個(gè)數(shù)
    TRIANGLE* triangle;						// 指向三角數(shù)組的指針
} SECTOR;								

數(shù)據(jù)加載
在程序內(nèi)部直接存儲(chǔ)數(shù)據(jù)會(huì)讓程序顯得太過死板和無趣。從磁盤上載入世界資料,會(huì)給我們帶來更多的彈性,可以讓我們體驗(yàn)不同的世界,而不用被迫重新編譯程序。另一個(gè)好處就是用戶可以切換世界資料并修改它們而無需知道程序如何讀入輸出這些資料的。數(shù)據(jù)文件的類型我們準(zhǔn)備使用文本格式(txt)。這樣編輯起來更容易,寫的代碼也更少。
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景


二、效果展示

OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景


三、詳細(xì)流程

首先,不懂怎么創(chuàng)建OPenGL窗口的,可以參考:OPenGL筆記–創(chuàng)建一個(gè)OPenGL窗口

在窗口的基礎(chǔ)上,我們?cè)诤瘮?shù)paintGL()中繪制我們的3D世界(其實(shí)就是一個(gè)貼了紋理的盒子);

之前的教程里我們都是直接在函數(shù)paintGL()中繪制,這里我們通過加載World.txt中的數(shù)據(jù)來繪制圖形;

這個(gè)World.txt是一個(gè)描述一堆三角形信息的文本文件,眾所周知,一個(gè)三角形由三個(gè)點(diǎn)構(gòu)成,紋理有兩個(gè)坐標(biāo),所以:

在World.txt中,采用(x, y, z, u, v)的形式來描述一個(gè)三角形頂點(diǎn),例如:

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記錄的是此文本文件中描述三角形的個(gè)數(shù),每3個(gè)點(diǎn)(對(duì)應(yīng)文本文件中的三行)組成一個(gè)三角形;

3.1、World.txt文件規(guī)則

World.txt中第一行表示文本文件中描述的三角形個(gè)數(shù):NUMPOLLIES 12

接下來,例如,我們搭建的是一個(gè)盒子,我們可以將其拆分為6個(gè)面:上、下、左、右、前、后

基本格式如下

NUMPOLLIES 12

//上
一個(gè)三角形
一個(gè)三角形

//下
一個(gè)三角形
一個(gè)三角形

//左
一個(gè)三角形
一個(gè)三角形

//右
一個(gè)三角形
一個(gè)三角形

//前
一個(gè)三角形
一個(gè)三角形

//后
一個(gè)三角形
一個(gè)三角形

例如,我們準(zhǔn)備搭建一個(gè)661的盒子,以x-z平面為底,以原點(diǎn)為底部中心,如下圖所示:
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景

以底面為例進(jìn)行講解,我們將底面正方形劃分為兩個(gè)三角形(怎么劃分不重要),分別是上三角和下三角

上三角:

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

下三角:

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

現(xiàn)在確定了三角形的坐標(biāo),那紋理坐標(biāo)怎么確定呢?紋理坐標(biāo)通常在0~1之間(但是紋理坐標(biāo)如果超過1就會(huì)復(fù)制)

以上三角為例:以直角為紋理坐標(biāo)系原點(diǎn),直角邊為紋理坐標(biāo)系坐標(biāo)軸,紋理坐標(biāo)X、Y軸需要垂直世界坐標(biāo)系軸X、Y軸,如下圖示:
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景
所以可以得出上三角的紋理坐標(biāo)為:

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

三角形坐標(biāo)和紋理坐標(biāo)合起來就得到了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()函數(shù)來加載World.txt;

  • 將讀取的三角形個(gè)數(shù)存儲(chǔ)到區(qū)段結(jié)構(gòu)體數(shù)組m_sector1中;
  • 將三角形信息存儲(chǔ)到區(qū)段結(jié)構(gòu)體數(shù)組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);
    //我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
    QString oneline;							// 存儲(chǔ)數(shù)據(jù)的字符串
    int numtriangles;							// 區(qū)段的三角形數(shù)量
    float x, y, z, u, v;							// 3D 和 紋理坐標(biāo)

    readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
    sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 讀入三角形數(shù)量

    m_sector1.triangle = new TRIANGLE[numtriangles];				// 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
    m_sector1.numtriangles = numtriangles;					// 定義區(qū)段1中的三角形數(shù)量
    // 遍歷區(qū)段中的每個(gè)三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍歷所有的三角形
    {
        // 遍歷三角形的每個(gè)頂點(diǎn)
        for (int vertloop = 0; vertloop < 3; vertloop++)		// 遍歷所有的頂點(diǎn)
        {
            readStr(&stream, oneline);				// 讀入一行數(shù)據(jù)
            // 讀入各自的頂點(diǎn)數(shù)據(jù)
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
            m_sector1.triangle[triloop].vertex[vertloop].x = x;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 x=x
            m_sector1.triangle[triloop].vertex[vertloop].y = y;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 y=y
            m_sector1.triangle[triloop].vertex[vertloop].z = z;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 z=z
            m_sector1.triangle[triloop].vertex[vertloop].u = u;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 u=u
            m_sector1.triangle[triloop].vertex[vertloop].v = v;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 v=v
        }
    }
    //數(shù)據(jù)文件中每個(gè)三角形都以如下形式聲明:
    //X1 Y1 Z1 U1 V1
    //X2 Y2 Z2 U2 V2
    //X3 Y3 Z3 U3 V3
    file.close();
}

//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循環(huán)開始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要進(jìn)行處理
}

3.3、繪制場(chǎng)景

在加載玩World.txt到區(qū)段結(jié)構(gòu)體數(shù)組m_sector1中之后,在paintGL()中繪制;

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
    glLoadIdentity();   //重置當(dāng)前的模型觀察矩陣

    GLfloat x_m, y_m, z_m, u_m, v_m;				// 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
    GLfloat xtrans = -m_xpos;						// 用于游戲者沿X軸平移時(shí)的大小
    GLfloat ztrans = -m_zpos;						// 用于游戲者沿Z軸平移時(shí)的大小
    GLfloat ytrans = -m_walkbias-0.25f;				// 用于頭部的上下擺動(dòng)
    GLfloat sceneroty = 360.0f - m_yrot;				// 位于游戲者方向的360度角
    int numtriangles;						// 保有三角形數(shù)量的整數(shù)
    glRotatef(m_lookupdown, 1.0f, 0,0);					// 上下旋轉(zhuǎn)
    glRotatef(sceneroty, 0, 1.0f, 0);					// 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
    glTranslatef(xtrans, ytrans, ztrans);				// 以游戲者為中心的平移場(chǎng)景
    glBindTexture(GL_TEXTURE_2D, m_texture[0]);			// 選擇的紋理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形數(shù)量
    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;	// 第一點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[0].y;	// 第一點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[0].z;	// 第一點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[0].u;	// 第一點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[0].v;	// 第一點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.triangle[loop_m].vertex[1].x;	// 第二點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[1].y;	// 第二點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[1].z;	// 第二點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[1].u;	// 第二點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[1].v;	// 第二點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.triangle[loop_m].vertex[2].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[2].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[2].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[2].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[2].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

        glEnd();						// 三角形繪制結(jié)束
    }

}

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;							// 向左旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右側(cè)旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Up:
        {
            m_xpos -= (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos -= (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            updateGL();
            break;
        }
        case Qt::Key_Down:
        {
            m_xpos += (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos += (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            updateGL();
            break;
        }

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

四、詳細(xì)代碼

素材
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景

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:
    //場(chǎng)景描述結(jié)構(gòu)體
    //==================================================================
    typedef struct tagVERTEX    // 創(chuàng)建頂點(diǎn)結(jié)構(gòu)
    {
        float x, y, z;						// 3D 坐標(biāo)
        float u, v;							// 紋理坐標(biāo)
    } VERTEX;

    typedef struct tagTRIANGLE  // 創(chuàng)建三角形結(jié)構(gòu)
    {
        VERTEX vertex[3];				    // VERTEX矢量數(shù)組,大小為3
    }TRIANGLE;// 命名為 TRIANGLE

    typedef struct tagSECTO     // 創(chuàng)建Sector區(qū)段結(jié)構(gòu)
    {
        int numtriangles;					// Sector中的三角形個(gè)數(shù)
        TRIANGLE* triangle;					// 指向三角數(shù)組的指針
    } SECTOR;
    //==================================================================

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

protected:
    /*************************************************************************************************
    QGLWidget 類已經(jīng)內(nèi)置了對(duì) OpenGL 的處理,就是通過對(duì) initializeGL()、 paintGL()和 resizeGL()這三個(gè)函數(shù)實(shí)現(xiàn)
    *************************************************************************************************/
    void initializeGL() override;           //用來初始化OPenGL窗口,可以在里面設(shè)定一些有關(guān)選項(xiàng)
    void paintGL() override;                //用來繪制OPenGL的窗口,只要有更新發(fā)生,這個(gè)函數(shù)就會(huì)被調(diào)用
    void resizeGL(int w, int h) override;   //用來處理窗口大小變換這一事件,resizeGL()在處理完后會(huì)自動(dòng)刷新屏幕

    void keyPressEvent(QKeyEvent* e) override;  //Qt鍵盤事件處理函數(shù)

private:
    void setupWorld();  //初始化場(chǎng)景
    void readStr(QTextStream *stream, QString &string); //讀取頂點(diǎn)信息
    void loadTexture(); //加載紋理

private:
    bool fullscreen;    //用來保存窗口是否處于全屏狀態(tài)的變量

    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);               //設(shè)置窗口大小
    setWindowTitle("The first OpenGL Window");  //設(shè)置窗口標(biāo)題

    if(fullscreen) {
        showFullScreen();
    }
}

GLWidget::~GLWidget()
{

}

void GLWidget::initializeGL()
{
    loadTexture();  //加載紋理

    glEnable(GL_TEXTURE_2D);    //使能紋理

    glClearColor(0.0, 0.0, 0.0, 0.0);   //清除屏幕時(shí)所用的顏色,rgba【0.0(最黑)~1.0(最亮)】

    glClearDepth(1.0);  //設(shè)置深度緩存

    glDepthFunc(GL_LESS); //所作深度測(cè)試的類型

    glEnable(GL_DEPTH_TEST);    //啟動(dòng)深度測(cè)試

    glShadeModel(GL_SMOOTH);    //啟用smooth shading(陰影平滑)

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //真正精細(xì)的透視修正,告訴OPenGL我們希望進(jìn)行最好的透視修正,這會(huì)十分輕微的影響性能,但使得透視圖看起來好一點(diǎn)

    setupWorld();
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
    glLoadIdentity();   //重置當(dāng)前的模型觀察矩陣

    GLfloat x_m, y_m, z_m, u_m, v_m;				// 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
    GLfloat xtrans = -m_xpos;						// 用于游戲者沿X軸平移時(shí)的大小
    GLfloat ztrans = -m_zpos;						// 用于游戲者沿Z軸平移時(shí)的大小
    GLfloat ytrans = -m_walkbias-0.25f;				// 用于頭部的上下擺動(dòng)
    GLfloat sceneroty = 360.0f - m_yrot;				// 位于游戲者方向的360度角
    int numtriangles;						// 保有三角形數(shù)量的整數(shù)
    glRotatef(m_lookupdown, 1.0f, 0,0);					// 上下旋轉(zhuǎn)
    glRotatef(sceneroty, 0, 1.0f, 0);					// 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
    glTranslatef(xtrans, ytrans, ztrans);				// 以游戲者為中心的平移場(chǎng)景
    glBindTexture(GL_TEXTURE_2D, m_texture[0]);			// 選擇的紋理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形數(shù)量
    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;	// 第一點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[0].y;	// 第一點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[0].z;	// 第一點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[0].u;	// 第一點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[0].v;	// 第一點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.triangle[loop_m].vertex[1].x;	// 第二點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[1].y;	// 第二點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[1].z;	// 第二點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[1].u;	// 第二點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[1].v;	// 第二點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.triangle[loop_m].vertex[2].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector1.triangle[loop_m].vertex[2].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector1.triangle[loop_m].vertex[2].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector1.triangle[loop_m].vertex[2].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.triangle[loop_m].vertex[2].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

        glEnd();						// 三角形繪制結(jié)束
    }

}

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

    glViewport(0, 0, (GLint)w, (GLint)h);   //重置當(dāng)前的視口(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;							// 向左旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右側(cè)旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Up:
        {
            m_xpos -= (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos -= (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            updateGL();
            break;
        }
        case Qt::Key_Down:
        {
            m_xpos += (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos += (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            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);
    //我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
    QString oneline;							// 存儲(chǔ)數(shù)據(jù)的字符串
    int numtriangles;							// 區(qū)段的三角形數(shù)量
    float x, y, z, u, v;							// 3D 和 紋理坐標(biāo)

    readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
    sscanf(oneline.toLatin1().data(), "NUMPOLLIES %d\n", &numtriangles); // 讀入三角形數(shù)量

    m_sector1.triangle = new TRIANGLE[numtriangles];				// 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
    m_sector1.numtriangles = numtriangles;					// 定義區(qū)段1中的三角形數(shù)量
    // 遍歷區(qū)段中的每個(gè)三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍歷所有的三角形
    {
        // 遍歷三角形的每個(gè)頂點(diǎn)
        for (int vertloop = 0; vertloop < 3; vertloop++)		// 遍歷所有的頂點(diǎn)
        {
            readStr(&stream, oneline);				// 讀入一行數(shù)據(jù)
            // 讀入各自的頂點(diǎn)數(shù)據(jù)
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
            m_sector1.triangle[triloop].vertex[vertloop].x = x;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 x=x
            m_sector1.triangle[triloop].vertex[vertloop].y = y;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 y=y
            m_sector1.triangle[triloop].vertex[vertloop].z = z;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 z=z
            m_sector1.triangle[triloop].vertex[vertloop].u = u;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 u=u
            m_sector1.triangle[triloop].vertex[vertloop].v = v;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 v=v
        }
    }
    //數(shù)據(jù)文件中每個(gè)三角形都以如下形式聲明:
    //X1 Y1 Z1 U1 V1
    //X2 Y2 Z2 U2 V2
    //X3 Y3 Z3 U3 V3
    file.close();
}

//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循環(huán)開始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要進(jìn)行處理
}

//加載紋理
void GLWidget::loadTexture()
{
    QImage image(":/Images/Crate.bmp");
    image = image.convertToFormat(QImage::Format_RGB888);
    image = image.mirrored();
    glGenTextures(1, &m_texture[0]);// 創(chuàng)建紋理

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

五、舉一反三

通過上面的流程,我們創(chuàng)建了一個(gè)小房間,它使用的紋理都是一樣的,接下來我們通過使用不同的紋理,來創(chuàng)建一個(gè)感官更加豐富的場(chǎng)景;

效果展示

OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景

上面使用的是三角形,我們也可以使用矩形來構(gòu)建:
素材

OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景
OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景

World1.txt文章來源地址http://www.zghlxwxcb.cn/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:
    //場(chǎng)景描述結(jié)構(gòu)體
    //==================================================================
    typedef struct tagVERTEX    // 創(chuàng)建頂點(diǎn)結(jié)構(gòu)
    {
        float x, y, z;						// 3D 坐標(biāo)
        float u, v;							// 紋理坐標(biāo)
    } VERTEX;

    typedef struct tagTRIANGLE  // 創(chuàng)建三角形結(jié)構(gòu)
    {
        VERTEX vertex[3];				    // VERTEX矢量數(shù)組,大小為3
    }TRIANGLE;// 命名為 TRIANGLE

    typedef struct tagRECT  // 創(chuàng)建四邊形結(jié)構(gòu)
    {
        VERTEX vertex[4];				    // VERTEX矢量數(shù)組,大小為4
    }RECT;// 命名為 RECT

    typedef struct tagSECTO     // 創(chuàng)建Sector區(qū)段結(jié)構(gòu)
    {
        int numtriangles;					// Sector中的三角形個(gè)數(shù)
        TRIANGLE* triangle;					// 指向三角數(shù)組的指針
        RECT* rect;
    } SECTOR;
    //==================================================================

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

protected:
    /*************************************************************************************************
    QGLWidget 類已經(jīng)內(nèi)置了對(duì) OpenGL 的處理,就是通過對(duì) initializeGL()、 paintGL()和 resizeGL()這三個(gè)函數(shù)實(shí)現(xiàn)
    *************************************************************************************************/
    void initializeGL() override;           //用來初始化OPenGL窗口,可以在里面設(shè)定一些有關(guān)選項(xiàng)
    void paintGL() override;                //用來繪制OPenGL的窗口,只要有更新發(fā)生,這個(gè)函數(shù)就會(huì)被調(diào)用
    void resizeGL(int w, int h) override;   //用來處理窗口大小變換這一事件,resizeGL()在處理完后會(huì)自動(dòng)刷新屏幕

    void keyPressEvent(QKeyEvent* e) override;  //Qt鍵盤事件處理函數(shù)

private:
    void setupWorld();  //初始化場(chǎng)景
    void readStr(QTextStream *stream, QString &string); //讀取頂點(diǎn)信息
    void loadTexture(); //加載紋理

private:
    bool fullscreen;    //用來保存窗口是否處于全屏狀態(tài)的變量

    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);               //設(shè)置窗口大小
    setWindowTitle("The first OpenGL Window");  //設(shè)置窗口標(biāo)題

    if(fullscreen) {
        showFullScreen();
    }
}

GLWidget::~GLWidget()
{

}

void GLWidget::initializeGL()
{
    loadTexture();  //加載紋理

    glEnable(GL_TEXTURE_2D);    //使能紋理

    glClearColor(0.0, 0.0, 0.0, 0.0);   //清除屏幕時(shí)所用的顏色,rgba【0.0(最黑)~1.0(最亮)】

    glClearDepth(1.0);  //設(shè)置深度緩存

    glDepthFunc(GL_LESS); //所作深度測(cè)試的類型

    glEnable(GL_DEPTH_TEST);    //啟動(dòng)深度測(cè)試

    glShadeModel(GL_SMOOTH);    //啟用smooth shading(陰影平滑)

    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);  //真正精細(xì)的透視修正,告訴OPenGL我們希望進(jìn)行最好的透視修正,這會(huì)十分輕微的影響性能,但使得透視圖看起來好一點(diǎn)

    setupWorld();
}

void GLWidget::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //清除屏幕和深度緩存
    glLoadIdentity();   //重置當(dāng)前的模型觀察矩陣

    GLfloat x_m, y_m, z_m, u_m, v_m;				// 頂點(diǎn)的臨時(shí) X, Y, Z, U 和 V 的數(shù)值
    GLfloat xtrans = -m_xpos;						// 用于游戲者沿X軸平移時(shí)的大小
    GLfloat ztrans = -m_zpos;						// 用于游戲者沿Z軸平移時(shí)的大小
    GLfloat ytrans = -m_walkbias-0.25f;				// 用于頭部的上下擺動(dòng)
    GLfloat sceneroty = 360.0f - m_yrot;				// 位于游戲者方向的360度角
    int numtriangles;						// 保有三角形數(shù)量的整數(shù)
    glRotatef(m_lookupdown, 1.0f, 0,0);					// 上下旋轉(zhuǎn)
    glRotatef(sceneroty, 0, 1.0f, 0);					// 根據(jù)游戲者正面所對(duì)方向所作的旋轉(zhuǎn)
    glTranslatef(xtrans, ytrans, ztrans);				// 以游戲者為中心的平移場(chǎng)景

    glBindTexture(GL_TEXTURE_2D, m_texture[0]);			// 選擇的紋理
    numtriangles = m_sector1.numtriangles;				// 取得Sector1的三角形數(shù)量
    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;	// 第一點(diǎn)的 X 分量
            y_m = m_sector1.rect[loop_m].vertex[0].y;	// 第一點(diǎn)的 Y 分量
            z_m = m_sector1.rect[loop_m].vertex[0].z;	// 第一點(diǎn)的 Z 分量
            u_m = m_sector1.rect[loop_m].vertex[0].u;	// 第一點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.rect[loop_m].vertex[0].v;	// 第一點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.rect[loop_m].vertex[1].x;	// 第二點(diǎn)的 X 分量
            y_m = m_sector1.rect[loop_m].vertex[1].y;	// 第二點(diǎn)的 Y 分量
            z_m = m_sector1.rect[loop_m].vertex[1].z;	// 第二點(diǎn)的 Z 分量
            u_m = m_sector1.rect[loop_m].vertex[1].u;	// 第二點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.rect[loop_m].vertex[1].v;	// 第二點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.rect[loop_m].vertex[2].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector1.rect[loop_m].vertex[2].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector1.rect[loop_m].vertex[2].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector1.rect[loop_m].vertex[2].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.rect[loop_m].vertex[2].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector1.rect[loop_m].vertex[3].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector1.rect[loop_m].vertex[3].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector1.rect[loop_m].vertex[3].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector1.rect[loop_m].vertex[3].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector1.rect[loop_m].vertex[3].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

        glEnd();						// 三角形繪制結(jié)束
    }
    //------------------------------------------------------------------------------------------------
    glBindTexture(GL_TEXTURE_2D, m_texture[1]);			// 選擇的紋理
    numtriangles = m_sector2.numtriangles;				// 取得Sector1的三角形數(shù)量
    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;	// 第一點(diǎn)的 X 分量
            y_m = m_sector2.rect[loop_m].vertex[0].y;	// 第一點(diǎn)的 Y 分量
            z_m = m_sector2.rect[loop_m].vertex[0].z;	// 第一點(diǎn)的 Z 分量
            u_m = m_sector2.rect[loop_m].vertex[0].u;	// 第一點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector2.rect[loop_m].vertex[0].v;	// 第一點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector2.rect[loop_m].vertex[1].x;	// 第二點(diǎn)的 X 分量
            y_m = m_sector2.rect[loop_m].vertex[1].y;	// 第二點(diǎn)的 Y 分量
            z_m = m_sector2.rect[loop_m].vertex[1].z;	// 第二點(diǎn)的 Z 分量
            u_m = m_sector2.rect[loop_m].vertex[1].u;	// 第二點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector2.rect[loop_m].vertex[1].v;	// 第二點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector2.rect[loop_m].vertex[2].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector2.rect[loop_m].vertex[2].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector2.rect[loop_m].vertex[2].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector2.rect[loop_m].vertex[2].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector2.rect[loop_m].vertex[2].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector2.rect[loop_m].vertex[3].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector2.rect[loop_m].vertex[3].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector2.rect[loop_m].vertex[3].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector2.rect[loop_m].vertex[3].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector2.rect[loop_m].vertex[3].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

        glEnd();						// 三角形繪制結(jié)束
    }
    //------------------------------------------------------------------------------------------------
    glBindTexture(GL_TEXTURE_2D, m_texture[2]);			// 選擇的紋理
    numtriangles = m_sector3.numtriangles;				// 取得Sector1的三角形數(shù)量
    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;	// 第一點(diǎn)的 X 分量
            y_m = m_sector3.rect[loop_m].vertex[0].y;	// 第一點(diǎn)的 Y 分量
            z_m = m_sector3.rect[loop_m].vertex[0].z;	// 第一點(diǎn)的 Z 分量
            u_m = m_sector3.rect[loop_m].vertex[0].u;	// 第一點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector3.rect[loop_m].vertex[0].v;	// 第一點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector3.rect[loop_m].vertex[1].x;	// 第二點(diǎn)的 X 分量
            y_m = m_sector3.rect[loop_m].vertex[1].y;	// 第二點(diǎn)的 Y 分量
            z_m = m_sector3.rect[loop_m].vertex[1].z;	// 第二點(diǎn)的 Z 分量
            u_m = m_sector3.rect[loop_m].vertex[1].u;	// 第二點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector3.rect[loop_m].vertex[1].v;	// 第二點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector3.rect[loop_m].vertex[2].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector3.rect[loop_m].vertex[2].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector3.rect[loop_m].vertex[2].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector3.rect[loop_m].vertex[2].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector3.rect[loop_m].vertex[2].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

            x_m = m_sector3.rect[loop_m].vertex[3].x;	// 第三點(diǎn)的 X 分量
            y_m = m_sector3.rect[loop_m].vertex[3].y;	// 第三點(diǎn)的 Y 分量
            z_m = m_sector3.rect[loop_m].vertex[3].z;	// 第三點(diǎn)的 Z 分量
            u_m = m_sector3.rect[loop_m].vertex[3].u;	// 第三點(diǎn)的 U  紋理坐標(biāo)
            v_m = m_sector3.rect[loop_m].vertex[3].v;	// 第三點(diǎn)的 V  紋理坐標(biāo)
            glTexCoord2f(u_m,v_m); glVertex3f(x_m,y_m,z_m);	// 設(shè)置紋理坐標(biāo)和頂點(diǎn)

        glEnd();						// 三角形繪制結(jié)束
    }
}

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

    glViewport(0, 0, (GLint)w, (GLint)h);   //重置當(dāng)前的視口(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;							// 向左旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Left:
        {
            m_heading += 1.0f;
            m_yrot = m_heading;							// 向右側(cè)旋轉(zhuǎn)場(chǎng)景
            updateGL();
            break;
        }
        case Qt::Key_Up:
        {
            m_xpos -= (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos -= (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            updateGL();
            break;
        }
        case Qt::Key_Down:
        {
            m_xpos += (float)sin(m_heading*piover180) * 0.05f;			// 沿游戲者所在的X平面移動(dòng)
            m_zpos += (float)cos(m_heading*piover180) * 0.05f;			// 沿游戲者所在的Z平面移動(dòng)
            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);
    //我們對(duì)區(qū)段進(jìn)行初始化,并讀入部分?jǐn)?shù)據(jù)
    QString oneline;							// 存儲(chǔ)數(shù)據(jù)的字符串
    int numtriangles;							// 區(qū)段的三角形數(shù)量
    float x, y, z, u, v;							// 3D 和 紋理坐標(biāo)

    //------------------------------------------------------------------------------------------------
    readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
    sscanf(oneline.toLatin1().data(), "grass %d\n", &numtriangles); // 讀入三角形數(shù)量

    m_sector1.rect = new RECT[numtriangles];				// 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
    m_sector1.numtriangles = numtriangles;					// 定義區(qū)段1中的三角形數(shù)量
    // 遍歷區(qū)段中的每個(gè)三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍歷所有的三角形
    {
        // 遍歷三角形的每個(gè)頂點(diǎn)
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍歷所有的頂點(diǎn)
        {
            readStr(&stream, oneline);				// 讀入一行數(shù)據(jù)
            // 讀入各自的頂點(diǎn)數(shù)據(jù)
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
            m_sector1.rect[triloop].vertex[vertloop].x = x;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 x=x
            m_sector1.rect[triloop].vertex[vertloop].y = y;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 y=y
            m_sector1.rect[triloop].vertex[vertloop].z = z;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 z=z
            m_sector1.rect[triloop].vertex[vertloop].u = u;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 u=u
            m_sector1.rect[triloop].vertex[vertloop].v = v;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 v=v
        }
    }

    //------------------------------------------------------------------------------------------------
    readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
    sscanf(oneline.toLatin1().data(), "sky %d\n", &numtriangles); // 讀入三角形數(shù)量

    m_sector2.rect = new RECT[numtriangles];				// 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
    m_sector2.numtriangles = numtriangles;					// 定義區(qū)段1中的三角形數(shù)量
    // 遍歷區(qū)段中的每個(gè)三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍歷所有的三角形
    {
        // 遍歷三角形的每個(gè)頂點(diǎn)
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍歷所有的頂點(diǎn)
        {
            readStr(&stream, oneline);				// 讀入一行數(shù)據(jù)
            // 讀入各自的頂點(diǎn)數(shù)據(jù)
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
            m_sector2.rect[triloop].vertex[vertloop].x = x;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 x=x
            m_sector2.rect[triloop].vertex[vertloop].y = y;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 y=y
            m_sector2.rect[triloop].vertex[vertloop].z = z;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 z=z
            m_sector2.rect[triloop].vertex[vertloop].u = u;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 u=u
            m_sector2.rect[triloop].vertex[vertloop].v = v;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 v=v
        }
    }

    //------------------------------------------------------------------------------------------------
    readStr(&stream, oneline); // 讀入一行數(shù)據(jù)
    sscanf(oneline.toLatin1().data(), "floor %d\n", &numtriangles); // 讀入三角形數(shù)量

    m_sector3.rect = new RECT[numtriangles];				// 為numtriangles個(gè)三角形分配內(nèi)存并設(shè)定指針
    m_sector3.numtriangles = numtriangles;					// 定義區(qū)段1中的三角形數(shù)量
    // 遍歷區(qū)段中的每個(gè)三角形
    for (int triloop = 0; triloop < numtriangles; triloop++)		// 遍歷所有的三角形
    {
        // 遍歷三角形的每個(gè)頂點(diǎn)
        for (int vertloop = 0; vertloop < 4; vertloop++)		// 遍歷所有的頂點(diǎn)
        {
            readStr(&stream, oneline);				// 讀入一行數(shù)據(jù)
            // 讀入各自的頂點(diǎn)數(shù)據(jù)
            sscanf(oneline.toLatin1().data(), "%f %f %f %f %f", &x, &y, &z, &u, &v);
            // 將頂點(diǎn)數(shù)據(jù)存入各自的頂點(diǎn)
            m_sector3.rect[triloop].vertex[vertloop].x = x;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 x=x
            m_sector3.rect[triloop].vertex[vertloop].y = y;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 y=y
            m_sector3.rect[triloop].vertex[vertloop].z = z;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 z=z
            m_sector3.rect[triloop].vertex[vertloop].u = u;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 u=u
            m_sector3.rect[triloop].vertex[vertloop].v = v;	// 區(qū)段 1,  第 triloop 個(gè)三角形, 第  vertloop 個(gè)頂點(diǎn), 值 v=v
        }
    }
    //------------------------------------------------------------------------------------------------

    file.close();
}

//讀取World.txt中的有效數(shù)據(jù)行
void GLWidget::readStr(QTextStream *stream, QString &string)
{
    do								// 循環(huán)開始
    {
        string = stream->readLine();
    } while (string[0] == '/' || string[0] == '\n' || string.isEmpty());		// 考察是否有必要進(jìn)行處理
}

//加載紋理
void GLWidget::loadTexture()
{
    glGenTextures(1, &m_texture[0]);// 創(chuàng)建紋理

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


}

到了這里,關(guān)于OPenGL筆記--創(chuàng)建一個(gè)3D場(chǎng)景的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • Three.js之創(chuàng)建3D場(chǎng)景

    【G】Three.js官方文檔:https://threejs.org/docs/ Three.js是一個(gè)流行的WebGL庫(kù),官方文檔提供了詳細(xì)的API參考和示例,適合學(xué)習(xí)和參考。 【G】Three.js GitHub鏈接:https://github.com/mrdoob/three.js 這是一個(gè)流行的基于WebGL的3D圖形庫(kù),提供了豐富的功能和工具,用于創(chuàng)建交互式的3D場(chǎng)景和應(yīng)用。

    2024年02月14日
    瀏覽(160)
  • ThreeJS-3D教學(xué)一:基礎(chǔ)場(chǎng)景創(chuàng)建

    ThreeJS-3D教學(xué)一:基礎(chǔ)場(chǎng)景創(chuàng)建

    Three.js 是一個(gè)開源的 JS 3D 圖形庫(kù),用于創(chuàng)建和展示高性能、交互式的 3D 圖形場(chǎng)景。它建立在 WebGL 技術(shù)之上,并提供了豐富的功能和工具,使開發(fā)者可以輕松地構(gòu)建令人驚嘆的 3D 可視化效果。 Three.js 提供了一套完整的工具和 API,用于創(chuàng)建和管理 3D 場(chǎng)景、幾何體、紋理、光照

    2024年02月07日
    瀏覽(24)
  • 五、3d場(chǎng)景的卡片展示的創(chuàng)建

    五、3d場(chǎng)景的卡片展示的創(chuàng)建

    ????????在我們3d的開發(fā)中,對(duì)某一些建筑和物體進(jìn)行解釋說明是非常常見的現(xiàn)象,那么就不得不說卡片的展示了,卡片展示很友好的說明了當(dāng)前物體的狀態(tài),一目了然,下面就是效果圖。 它主要有兩個(gè)方法來實(shí)現(xiàn),大量的圖片建議使用canvas來實(shí)現(xiàn),少量的可以使用標(biāo)簽實(shí)

    2024年02月03日
    瀏覽(17)
  • U3D通過按鈕點(diǎn)擊實(shí)現(xiàn)場(chǎng)景切換

    U3D通過按鈕點(diǎn)擊實(shí)現(xiàn)場(chǎng)景切換

    1.新建UI,選擇button選項(xiàng),新建button; ? 3.新建一個(gè)空對(duì)象,掛載一個(gè)scenechange c#腳本; 4.編寫腳本,1頭文件using UnityEngine.SceneMangement ? ? ? ? ? ? ? ? ? ?2public void change() { ? ? ? ? ? ? ? ? ? ? scenemanager.loadscene (1)? }//括號(hào)中的數(shù)字為第2步中場(chǎng)景后面的數(shù)字 ? ? ? ? ?

    2024年02月07日
    瀏覽(35)
  • 使用cannon.js創(chuàng)建3D物理仿真場(chǎng)景

    本文將詳細(xì)介紹使用cannon.js創(chuàng)建3D物理仿真場(chǎng)景的步驟和技巧。 cannon.js是一個(gè)開源的JavaScript物理庫(kù),用于實(shí)現(xiàn)3D物理仿真。它可以被用于游戲開發(fā)、機(jī)器人控制、交互式的3D應(yīng)用以及其他需要物理交互的場(chǎng)景。 與其他物理庫(kù)不同的是,cannon.js是一個(gè)非常輕量級(jí)的庫(kù),它的代碼

    2024年02月06日
    瀏覽(15)
  • Three.js教程:第一個(gè)3D場(chǎng)景

    Three.js教程:第一個(gè)3D場(chǎng)景

    推薦:將 NSDT場(chǎng)景編輯器加入你3D工具鏈 其他工具系列: NSDT簡(jiǎn)石數(shù)字孿生 下面的代碼完整展示了通過three.js引擎創(chuàng)建的一個(gè)三維場(chǎng)景,在場(chǎng)景中繪制并渲染了一個(gè)立方體的效果,為了大家更好的宏觀了解three.js引擎, 盡量使用了一段短小但完整的代碼實(shí)現(xiàn)一個(gè)實(shí)際的三維效果

    2023年04月12日
    瀏覽(164)
  • Unity 3D開發(fā)--SceneManager場(chǎng)景管理(異步使用同一個(gè)過渡場(chǎng)景)

    在U3D開發(fā)過程中經(jīng)常使用到多場(chǎng)景的切換,有同步SceneManager.LoadScene()和異步SceneManager.LoadSceneAsync()兩種方法,同步的話一般就會(huì)卡住界面直到加載完成,使用異步的話一般都做一個(gè)加載的進(jìn)度條,每次切換的時(shí)候都需要一個(gè)加載動(dòng)畫,所以需要建一個(gè)專門的過渡加載場(chǎng)景來進(jìn)

    2024年02月14日
    瀏覽(18)
  • 【Unity3D】資源文件 ② ( Unity 中場(chǎng)景文件簡(jiǎn)介 | 查看場(chǎng)景文件內(nèi)容 | 場(chǎng)景文件相關(guān)操作 | 創(chuàng)建場(chǎng)景 | 打開場(chǎng)景 )

    【Unity3D】資源文件 ② ( Unity 中場(chǎng)景文件簡(jiǎn)介 | 查看場(chǎng)景文件內(nèi)容 | 場(chǎng)景文件相關(guān)操作 | 創(chuàng)建場(chǎng)景 | 打開場(chǎng)景 )

    Unity 編輯器中的 場(chǎng)景文件 是以 \\\" .unity \\\" 為后綴的文件 , 該文件中會(huì)記錄所有 游戲物體 GameObject , 以及游戲物體的相關(guān)數(shù)據(jù) , 如下內(nèi)容都是存儲(chǔ)在 場(chǎng)景文件 中的 : 游戲物體 GameObject 節(jié)點(diǎn) : 在 Hierarchy 層級(jí)窗口 中 場(chǎng)景文件 下的各個(gè)節(jié)點(diǎn) 都是游戲物體 , 如 主攝像機(jī) , 光源 , 立

    2024年02月09日
    瀏覽(93)
  • 計(jì)算機(jī)圖形學(xué):繪制一個(gè)3d交互場(chǎng)景(1)

    計(jì)算機(jī)圖形學(xué):繪制一個(gè)3d交互場(chǎng)景(1)

    OpenGL作為一種圖形與硬件的接口,與其他圖形程序開發(fā)工具相比較,它提供了眾多圖形函數(shù),直觀的編程環(huán)境簡(jiǎn)化了三維圖形的繪制過程,使用OpenGL搭建一個(gè)三維場(chǎng)景,能夠通過輸入設(shè)備與場(chǎng)景內(nèi)物體交互。 豪華單間 配置環(huán)境:vs22+freeglut庫(kù) 1.繪制墻體使其成為封閉空間,在

    2024年02月11日
    瀏覽(78)
  • 51-32 CVPR’24 | 3DSFLabelling,通過偽自動(dòng)標(biāo)注增強(qiáng) 3D 場(chǎng)景流估計(jì)

    24 年 2 月,鑒智機(jī)器人、劍橋大學(xué)和上海交通大學(xué)聯(lián)合發(fā)布CVPR\\\'24工作,3DSFLabelling: Boosting 3D Scene Flow Estimation by Pseudo Auto-labelling。 提出?3D 場(chǎng)景自動(dòng)標(biāo)注新框架,將 3D 點(diǎn)云打包成具有不同運(yùn)動(dòng)屬性的 Boxes,通過優(yōu)化每個(gè) Box 運(yùn)動(dòng)參數(shù)并將源點(diǎn)云 Warp 扭曲到目標(biāo)點(diǎn)云中,創(chuàng)建了

    2024年04月09日
    瀏覽(23)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包