OpenGL從入門到放棄 #03-PR


??本節(jié)將對上節(jié)學(xué)習(xí)的內(nèi)容稍作補充戒洼,然后開始復(fù)習(xí)及拓展訓(xùn)練燕垃,以提高熟練度枢劝。對于習(xí)題的答案并不一定是最優(yōu)解,練習(xí)為主卜壕,并不做過多文字上的解釋您旁。

索引緩沖對象(Element Buffer Object,EBO)


??如果我們現(xiàn)在的需求不是一個三角形轴捎,而是一個矩形鹤盒。我們可以通過繪制兩個三角形的辦法來組成一個矩形(OpenGL主要處理三角形)。那么就要改變我們的輸入頂點數(shù)據(jù):

float vertices[] = {
    // 第一個三角形
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, 0.5f, 0.0f,  // 左上角
    // 第二個三角形
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

??可以看到這兩個三角形有兩個頂點重復(fù)了侦副,第一個是右下角0.5f, -0.5f, 0.0f侦锯,第二個是左上角-0.5f, 0.5f, 0.0f。目前看來重復(fù)的頂點數(shù)據(jù)并不會帶來什么嚴(yán)重的后秦驯,但是如果是面對一個非常精致的模型尺碰,里面包含了海量的三角形,這種情況下的重復(fù)所帶來的性能開銷是不可忽視的译隘。不過考慮周詳?shù)腛penGL顯然早已經(jīng)知道了這個問題并提供了很好的解決辦法亲桥,索引緩沖對象(Element Buffer Object,EBO)為此而生固耘。EBO與VBO一樣题篷,也是一個緩沖,它專門存儲索引厅目,存儲的是頂點數(shù)據(jù)的索引番枚,用索引的重復(fù)代替頂點的重復(fù)法严,那么如何設(shè)置頂點數(shù)據(jù)的索引呢?首先葫笼,我們要定義一個數(shù)組存儲不重復(fù)的頂點深啤,像列出一個矩形的所有頂點一樣:

float vertices[] = {
    0.5f, 0.5f, 0.0f,   // 右上角
    0.5f, -0.5f, 0.0f,  // 右下角
    -0.5f, -0.5f, 0.0f, // 左下角
    -0.5f, 0.5f, 0.0f   // 左上角
};

??這是符合我們數(shù)學(xué)認(rèn)知的一個矩形的頂點表示。然后再定義一個數(shù)組存儲繪制出矩形所需的的索引渔欢,當(dāng)然是按繪制三角形的頂點順序存放索引:

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

??接下來要做的就是創(chuàng)建一個EBO對象墓塌,然后再把索引數(shù)組送進EBO里。

    unsigned int EBO;
    glGenBuffers(1, &EBO);

??當(dāng)然要想對這個EBO進行操作還是要先綁定這個EBO奥额。

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

??一切都與討論VBO時相似苫幢,唯一不同的就是緩沖的類型為GL_ELEMENT_ARRAY_BUFFER
??現(xiàn)在才是需要稍加注意的地方垫挨,如果我們使用了EBO韩肝,那么繪制的函數(shù)就不再是glDrawArrays函數(shù),而是glDrawElements函數(shù)九榔,指明是從索引緩沖渲染哀峻。

    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

??第一個參數(shù)指定了我們繪制的模式,這個和glDrawArrays的一樣哲泊;第二個參數(shù)是我們打算繪制的頂點個數(shù)剩蟀,這里是6;第三個是參數(shù)是索引的類型切威;最后一個參數(shù)指定EBO中的偏移量育特,由于這里并沒有偏移量,所以是0先朦。
??glDrawElements函數(shù)從當(dāng)前綁定的EBO中獲取索引缰冤,這意味著我們每次調(diào)用glDrawElements繪制不同的物體都要綁定不同的EBO,這雖然不是特別麻煩喳魏,但如果有人樂意幫我做這件事那自然最好棉浸。我們的善心人士VAO果真樂于助人,在VAO綁定時去綁定EBO刺彩,這個步驟就會被VAO所保存迷郑,以后在渲染循環(huán)中綁定VAO時就會自動把EBO也綁定了。

??當(dāng)目標(biāo)是GL_ELEMENT_ARRAY_BUFFER的時候创倔,VAO會儲存glBindBuffer的函數(shù)調(diào)用三热。這也意味著它也會儲存解綁調(diào)用,所以確保你沒有在解綁VAO之前解綁索引數(shù)組緩沖三幻,否則它就沒有這個EBO配置了。

??上述的提示我親身去體驗了一下呐能,在我解綁VAO前就把EBO給解綁了確實報錯:



??最后的代碼如下:

// ..:: 初始化代碼 :: ..
// 1. 綁定頂點數(shù)組對象
    glBindVertexArray(VAO);
// 2. 把我們的頂點數(shù)組復(fù)制到一個頂點緩沖中念搬,供OpenGL使用
    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 復(fù)制我們的索引數(shù)組到一個索引緩沖中抑堡,供OpenGL使用
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 4. 設(shè)定頂點屬性指針
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

[...]

// ..:: 繪制代碼(渲染循環(huán)中) :: ..
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0)
    glBindVertexArray(0);

??接下來就可以運行程序了:



??補充的內(nèi)容到此結(jié)束,接下來是習(xí)題時間朗徊。

1. 添加更多頂點到數(shù)據(jù)中首妖,使用glDrawArrays,嘗試?yán)L制兩個彼此相連的三角形

??我打算繪制一個正三角爷恳,一個倒三角有缆,兩者公用其中一條邊,形成一個棱形:
頂點數(shù)據(jù):

float vertices[] = {
    0.0f, 0.5f, 0.0f,
    0.5f, 0.0f, 0.0f,
    0.0f, -0.5f, 0.0f,
    -0.5f, 0.0f, 0.0f
};

索引數(shù)組:

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

??其余代碼不做改變温亲,得到結(jié)果:



??這道題稍顯簡單棚壁。

2. 創(chuàng)建相同的兩個三角形,但對它們的數(shù)據(jù)使用不同的VAO和VBO

??那么當(dāng)前圖案的頂點數(shù)據(jù)和其所屬的VBO及VAO不再另行改變栈虚,而是再新造一組頂點數(shù)據(jù)袖外、VAO及VBO。我造一個漏斗吧:

float vertices2[] = {
    -0.5f, 0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 2, // 第一個三角形
    2, 3, 4  // 第二個三角形
};

??雖說不再改變魂务,但是要做一些修改曼验,譬如原本的三角形函數(shù)的某些參數(shù)要改為VAO[0]、VBO[0]和EBO[0]粘姜。這里是對原來作出的修改:

    //創(chuàng)建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //創(chuàng)建EBO且與索引數(shù)據(jù)綁定
    unsigned int EBO[2];
    glGenBuffers(2, EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

??這里是新增的VBO鬓照、VAO和EBO的綁定:

    glBindVertexArray(VAO[1]);

    glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);

??在渲染循環(huán)中,原本glBindVertexArray函數(shù)的參數(shù)是VAO孤紧,現(xiàn)在為了看第二個圖案的效果豺裆,所以參數(shù)是VAO[1],用我們新創(chuàng)建好的VAO坛芽。

    glBindVertexArray(VAO[1]);
3.創(chuàng)建兩個著色器程序留储,第二個程序使用一個不同的片段著色器,輸出黃色咙轩;再次繪制這兩個三角形获讳,讓其中一個輸出為黃色

??那我們就直接讓第二個圖案(漏斗)輸出黃色吧。
源碼:

const char * fragmentShaderSource2 =
"   #version 330 core                           \n   "
"   out vec4 FragColor;                         \n   "
"   void main()                                 \n   "
"   {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n   ";

片段著色器:

    unsigned int fragmentShader2;
    fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
    glCompileShader(fragmentShader2);

鏈接著色器程序?qū)ο螅?/p>

    unsigned int shaderProgram2;
    shaderProgram2 = glCreateProgram();

    glAttachShader(shaderProgram2, vertexShader);      //用的是原來的頂點著色器
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);

刪除著色器:

    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(fragmentShader2);

在渲染循環(huán)中活喊,使用第二個著色器程序?qū)ο螅?/p>

    while (!glfwWindowShouldClose(window))
    {
        //處理輸入
        processInput(window);

        //渲染指令
        glClearColor(1.0f, 0, 0, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram2);
        glBindVertexArray(VAO[1]);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        

        //接收輸入丐膝,交換緩沖
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

??包含3道題的所有代碼在此:

#define GLEW_STATIC
#include<iostream>
#include<GL/glew.h>
#include<GLFW/glfw3.h>

void processInput(GLFWwindow*);

float vertices[] = {    //棱形頂點數(shù)據(jù)
    0.0f, 0.5f, 0.0f,
    0.5f, 0.0f, 0.0f,
    0.0f, -0.5f, 0.0f,
    -0.5f, 0.0f, 0.0f
};

unsigned int indices[] = { // 注意索引從0開始! 
    0, 1, 3, // 第一個三角形
    1, 2, 3  // 第二個三角形
};

float vertices2[] = {    //漏斗頂點數(shù)據(jù)
    -0.5f, 0.5f, 0.0f,
    0.5f, 0.5f, 0.0f,
    0.0f, 0.0f, 0.0f,
    0.5f, -0.5f, 0.0f,
    -0.5f, -0.5f, 0.0f,
};

unsigned int indices2[] = { // 注意索引從0開始! 
    0, 1, 2, // 第一個三角形
    2, 3, 4  // 第二個三角形
};

const char * vertexShaderSource =
"#version 330 core                                  \n"
"layout (location = 0) in vec3 aPos;                \n"      
"void main()                                        \n"      
"{gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);} \n";    

const char * fragmentShaderSource =
"   #version 330 core                           \n   "    //輸出橘色
"   out vec4 FragColor;                         \n   "
"   void main()                                 \n   "
"   {FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);} \n   ";

const char * fragmentShaderSource2 =
"   #version 330 core                           \n   "    //輸出黃色
"   out vec4 FragColor;                         \n   "
"   void main()                                 \n   "
"   {FragColor = vec4(1.0f, 1.0f, 0.0f, 1.0f);} \n   ";

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);  //使用的OpenGL版本號3.3
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);      //使用流水線配置模式

    GLFWwindow* window = glfwCreateWindow(800, 600, "Learn OpenGL 2019-07-24 17:25:23", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);

    //初始化GLEW
    glewExperimental = true;
    if (glewInit() != GLEW_OK)
    {
        std::cout << "Failed to initial GLEW." << std::endl;
        glfwTerminate();
        return -1;
    }

    glViewport(0, 0, 800, 600);//渲染窗口大小
    //創(chuàng)建VAO
    unsigned int VAO[2];
    glGenVertexArrays(2, VAO);
    glBindVertexArray(VAO[0]);

    //創(chuàng)建VBO且與頂點數(shù)據(jù)綁定
    unsigned int VBO[2];
    glGenBuffers(2, VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO[0]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);



    //創(chuàng)建EBO且與索引數(shù)據(jù)綁定
    unsigned int EBO[2];
    glGenBuffers(2, EBO);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[0]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);

    //頂點著色器
    unsigned int vertexShader;
    vertexShader = glCreateShader(GL_VERTEX_SHADER);

    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);

    int  success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);

    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }

    //片段著色器
    unsigned int fragmentShader;
    fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

    unsigned int fragmentShader2;
    fragmentShader2 = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);

    glShaderSource(fragmentShader2, 1, &fragmentShaderSource2, NULL);
    glCompileShader(fragmentShader2);
    int  success1;
    char infoLog1[512];
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success1);

    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog1);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog1 << std::endl;
    }

    //著色器程序?qū)ο?    unsigned int shaderProgram;
    shaderProgram = glCreateProgram();

    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);

    unsigned int shaderProgram2;
    shaderProgram2 = glCreateProgram();

    glAttachShader(shaderProgram2, vertexShader);
    glAttachShader(shaderProgram2, fragmentShader2);
    glLinkProgram(shaderProgram2);

    //刪除著色器對象
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    glDeleteShader(fragmentShader2);

    //鏈接頂點屬性
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    //glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
    glBindVertexArray(VAO[1]);

    glBindBuffer(GL_ARRAY_BUFFER,VBO[1]);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices2), vertices2, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO[1]);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices2), indices2, GL_STATIC_DRAW);

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 3, (void*)0);
    glEnableVertexAttribArray(0);

    glBindVertexArray(0);


    while (!glfwWindowShouldClose(window))
    {
        //處理輸入
        processInput(window);

        //渲染指令
        glClearColor(1.0f, 0, 0, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);
        glUseProgram(shaderProgram2);
        glBindVertexArray(VAO[1]);
        //glDrawArrays(GL_TRIANGLES, 0, 3);
        glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
        glBindVertexArray(0);
        

        //接收輸入,交換緩沖
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwTerminate();
    return 0;
}

void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window,GLFW_KEY_ESCAPE) == GLFW_PRESS)
    {
        glfwSetWindowShouldClose(window, true);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末钾菊,一起剝皮案震驚了整個濱河市帅矗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌煞烫,老刑警劉巖浑此,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異滞详,居然都是意外死亡凛俱,警方通過查閱死者的電腦和手機紊馏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蒲犬,“玉大人朱监,你說我怎么就攤上這事≡#” “怎么了赫编?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長奋隶。 經(jīng)常有香客問我擂送,道長,這世上最難降的妖魔是什么达布? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任团甲,我火速辦了婚禮,結(jié)果婚禮上黍聂,老公的妹妹穿的比我還像新娘躺苦。我一直安慰自己,他們只是感情好产还,可當(dāng)我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布匹厘。 她就那樣靜靜地躺著,像睡著了一般脐区。 火紅的嫁衣襯著肌膚如雪愈诚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天牛隅,我揣著相機與錄音炕柔,去河邊找鬼。 笑死媒佣,一個胖子當(dāng)著我的面吹牛匕累,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播默伍,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼欢嘿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了也糊?” 一聲冷哼從身側(cè)響起炼蹦,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎狸剃,沒想到半個月后掐隐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡钞馁,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年瑟枫,在試婚紗的時候發(fā)現(xiàn)自己被綠了斗搞。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡慷妙,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出允悦,到底是詐尸還是另有隱情膝擂,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布隙弛,位于F島的核電站架馋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏全闷。R本人自食惡果不足惜叉寂,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望总珠。 院中可真熱鬧屏鳍,春花似錦、人聲如沸局服。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽淫奔。三九已至山涡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間唆迁,已是汗流浹背鸭丛。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留唐责,地道東北人鳞溉。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像妒蔚,于是被迫代替她去往敵國和親穿挨。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,941評論 2 355