使用GLFW與GLAD創(chuàng)建窗口并畫出三角形

本文章內(nèi)容代碼可在這里找到,如果此代碼對(duì)您有幫助,煩請(qǐng)動(dòng)動(dòng)您的手指,點(diǎn)個(gè)Star,謝謝!歡迎訪問(wèn)我的個(gè)人主頁(yè)Orient。

創(chuàng)建窗口

1、首先我們引入必要的頭文件:

#include "glad.h"
#include <GLFW/glfw3.h>

請(qǐng)確保GLAD頭文件的引入在GLFW之前,GLAD的頭文件包含了正確的OpenGL頭文件(例如GL/gl.h),所以需要在其他依賴于OpenGL的頭文件之前引入GLAD

2、實(shí)例化GLFW窗口

int main()
{
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // Mac必須添加此行,Windows忽略
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    return 0;
}

前兩行代碼指定了OpenGL的主版本和次版本號(hào)(4.1),第三行代表著使用核心模式(Core-profile),意味著我們只能使用OpenGL功能的一個(gè)子集(沒(méi)有我們不再需要的向后兼容特性)。

3、接下來(lái)創(chuàng)建一個(gè)窗口對(duì)象,它存放了所有和窗口相關(guān)的數(shù)據(jù),而且會(huì)被GLFW的其他函數(shù)頻繁調(diào)用

GLFWwindow* window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
if(window == NULL)
{
    std::cout << "Failed to create GLFW window" << std::endl;
    glfwTerminate();
    return -1;
}
glfwMakeContexCurrent(window);

glfwCreateWindow函數(shù),前兩個(gè)參數(shù)是窗口的寬高,第三個(gè)參數(shù)是這個(gè)窗口的命名,后兩個(gè)暫時(shí)忽略,返回了一個(gè)GLFWwindow對(duì)象。glfwMakeContexCurrent函數(shù)告訴GLFW將窗口的上下文設(shè)置為當(dāng)前線程的主上下文。

4、GLAD是用來(lái)管理OpenGL的函數(shù)指針的,所以調(diào)用任何OpenGL函數(shù)之前需要初始化GLAD

if(!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
    std::cout << "Failed to initialize GLAD" << std::endl;
    return -1;
}

我們給GLAD傳入了用來(lái)加載系統(tǒng)相關(guān)的OpenGL函數(shù)指針地址的函數(shù)。GLFW給我們的是glfwGetProcAdress,它根據(jù)我們編譯的系統(tǒng)定義了正確的函數(shù)。

5、視口

在開(kāi)始渲染之前必須告訴OpenGL渲染窗口(Viewport)的尺寸大小,這樣OpenGL才能知道怎樣根據(jù)窗口大小顯示數(shù)據(jù)和坐標(biāo)。

// 此函數(shù)設(shè)置窗口的維度(Dimension)
glViewport(0, 0, 800, 600);

前兩個(gè)參數(shù)控制窗口左下角位置,后兩個(gè)控制渲染窗口的寬高(像素)。也可將視口維度設(shè)置比GLFW窗口維度小,這樣子之后所有的OpenGL渲染將會(huì)在一個(gè)更小的窗口中顯示,這樣子的話我們也可以將一些其它元素顯示在OpenGL視口之外。

6、對(duì)窗口注冊(cè)回調(diào)函數(shù)(CallbackFunction)

函數(shù)注冊(cè)后會(huì)在每次窗口大小改變的時(shí)候調(diào)用,視口也會(huì)隨之調(diào)整

函數(shù)原型如下:

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    glViewport(0, 0, width, height);
}

進(jìn)行注冊(cè),告訴GLFW每當(dāng)窗口調(diào)整時(shí)調(diào)用此函數(shù):

glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

7、為了使圖像能夠持續(xù)顯示而不是一閃即逝,我們需要寫一個(gè)渲染循環(huán),使得GLFW在退出之前一直保持運(yùn)行

while(!glfwWindowShouldClose(window))
{
    glfwSwapBuffers(window);
    glfwPollEvents();    
}

glfwWindowShouldClose函數(shù)在每次循環(huán)開(kāi)始前檢查一次GLFW是否被要求退出,是的話返回true,循環(huán)結(jié)束

glfwPollEvents函數(shù)檢查是否有觸發(fā)事件,比如鍵盤、鼠標(biāo)等信號(hào)輸入,然后更新窗口狀態(tài),調(diào)用相應(yīng)的回調(diào)函數(shù)(可通過(guò)回調(diào)方法手動(dòng)設(shè)置)。

glfwSwapBuffers函數(shù)會(huì)交換顏色緩沖

8、渲染結(jié)束后釋放所有資源

glfwTerminate();
return 0;

至此,窗口創(chuàng)建完成

接下來(lái)我們進(jìn)行一些完善工作

9、接下來(lái)我們添加一個(gè)觸發(fā)時(shí)間,當(dāng)用戶按下Esc鍵時(shí)關(guān)閉窗口。

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

glfwGetKey函數(shù)需要一個(gè)窗口以及一個(gè)按鍵作為輸入。這個(gè)函數(shù)將會(huì)返回這個(gè)案件是否正在被按下,我們將其定義在processInput函數(shù)當(dāng)中

接下來(lái)在渲染循環(huán)的每一個(gè)迭代中調(diào)用processInput

while (!glfwWindowShouldClose(window))
{
    processInput(window);

    // 這里是渲染指令
    ...

    glfwSwapBuffers(window);
    glfwPollEvents();
}

10、我們使用一個(gè)自定義的顏色清空屏幕,使得在每個(gè)新的渲染迭代開(kāi)始后清除上一次渲染結(jié)果,并顯示我們自定義的顏色

glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);

渲染一個(gè)三角形

開(kāi)始繪制之前,我們需要給OpenGL輸入一些頂點(diǎn)數(shù)據(jù)(范圍在[-1, 1],需要自行進(jìn)行坐標(biāo)變換)。我們需要渲染一個(gè)三角形,因此我們需要三個(gè)頂點(diǎn)位置,將它定義為一個(gè)float數(shù)組:

// 由于我們繪制的是一個(gè)2D三角形,因此,將其頂點(diǎn)的z坐標(biāo)都設(shè)置為0
float vertices[] = {
    -0.5f, -0.5f, 0.0f,
     0.5f, -0.5f, 0.0f,
     0.0f,  0.5f, 0.0f
};

接下來(lái)使用glGenBuffers函數(shù)和一個(gè)緩沖ID生成一個(gè)VBO對(duì)象,并使用glBindBuffer函數(shù)把新創(chuàng)建的緩沖綁定到GL_ARRAY_BUFFER目標(biāo)上:

unsigned int VBO;
glGenBuffers(1, &VBO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);  

從這一刻起,我們使用的任何(在GL_ARRAY_BUFFER目標(biāo)上的)緩沖調(diào)用都會(huì)用來(lái)配置當(dāng)前綁定的緩沖(VBO)。然后我們可以調(diào)用glBufferData函數(shù),它會(huì)把之前定義的頂點(diǎn)數(shù)據(jù)復(fù)制到緩沖的內(nèi)存中:

glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

第一個(gè)參數(shù)是目標(biāo)緩沖的類型,頂點(diǎn)緩沖對(duì)象當(dāng)前綁定到GL_ARRAY_BUFFER目標(biāo)上。

第二個(gè)參數(shù)指定傳輸數(shù)據(jù)大小。

第三個(gè)參數(shù)是我們實(shí)際發(fā)送的數(shù)據(jù)。

第四個(gè)參數(shù)指定了顯卡管理數(shù)據(jù)的方式,有一下三種形式:
GL_STATIC_DRAW:數(shù)據(jù)不會(huì)或幾乎不改變。
GL_DYNAMIC_DRAW:數(shù)據(jù)會(huì)改變很多。
GL_STREAM_DRAW:數(shù)據(jù)每次繪制都會(huì)改變。

頂點(diǎn)著色器

首先用GLSL(OoenGL Shading Language)編寫頂點(diǎn)著色器,然后編譯這個(gè)著色器。下面給出一個(gè)非常基礎(chǔ)的頂點(diǎn)著色器源代碼:

#version 410 core
layout (location = 0) in vec3 aPos;

void main()
{
    gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
}

首先申明OpenGL版本4.1(對(duì)應(yīng)410)。
接下來(lái)使用in關(guān)鍵字,在頂點(diǎn)著色器中聲明所有的輸入頂點(diǎn)屬性(Input Vertex Attribute)。現(xiàn)在我們只關(guān)心位置(Position)數(shù)據(jù),所以我們只需要一個(gè)頂點(diǎn)屬性。GLSL有一個(gè)向量數(shù)據(jù)類型,它包含1到4個(gè)float分量,包含的數(shù)量可以從它的后綴數(shù)字看出來(lái)。由于每個(gè)頂點(diǎn)都有一個(gè)3D坐標(biāo),我們就創(chuàng)建一個(gè)vec3輸入變量aPos。我們同樣也通過(guò)layout (location = 0)設(shè)定了輸入變量的位置值(Location)你后面會(huì)看到為什么我們會(huì)需要這個(gè)位置值。

編譯著色器

先創(chuàng)建一個(gè)著色器對(duì)象,注意還是用ID來(lái)引用。所以我們儲(chǔ)存這個(gè)頂點(diǎn)著色器為unsigned int,然后用glCreateShader創(chuàng)建這個(gè)著色器:

unsigned int vertexShader;
vertexShader = glCreateShader(GL_VERTEX_SHADER);

我們把需要?jiǎng)?chuàng)建的著色器類型以參數(shù)形式提供給glCreateShader。由于我們正在創(chuàng)建一個(gè)頂點(diǎn)著色器,傳遞的參數(shù)是GL_VERTEX_SHADER

接下來(lái)把著色器源碼附加到著色器對(duì)象上,并編譯它:

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

glShaderSource函數(shù)把要編譯的著色器對(duì)象作為第一個(gè)參數(shù)。第二參數(shù)指定了傳遞的源碼字符串?dāng)?shù)量,這里只有一個(gè)。第三個(gè)參數(shù)是頂點(diǎn)著色器真正的源碼,第四個(gè)參數(shù)我們先設(shè)置為NULL。

片段著色器

先給出片段著色器源碼:

#version 410 core
out vec4 FragColor;

void main()
{
    FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
} 

片段著色器只需要一個(gè)輸出變量,這個(gè)變量是一個(gè)4分量向量,它表示的是最終的輸出顏色,我們應(yīng)該自己將其計(jì)算出來(lái)。我們可以用out關(guān)鍵字聲明輸出變量,這里我們命名為FragColor。下面,我們將一個(gè)alpha值為1.0(1.0代表完全不透明)的橘黃色的vec4賦值給顏色輸出。

編譯片段著色器的過(guò)程與頂點(diǎn)著色器類似,只不過(guò)我們使用GL_FRAGMENT_SHADER常量作為著色器類型:

unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);

著色器程序

著色器程序?qū)ο?Shader Program Object)是多個(gè)著色器合并之后并最終鏈接完成的版本。如果要使用剛才編譯的著色器我們必須把它們鏈接(Link)為一個(gè)著色器程序?qū)ο?,然后在渲染?duì)象的時(shí)候激活這個(gè)著色器程序。已激活著色器程序的著色器將在我們發(fā)送渲染調(diào)用的時(shí)候被使用。

創(chuàng)建程序?qū)ο螅?/p>

unsigned int shaderProgram;
shaderProgram = glCreateProgram();

glCreateProgram函數(shù)創(chuàng)建一個(gè)程序,并返回新創(chuàng)建程序?qū)ο蟮腎D引用?,F(xiàn)在我們需要把之前編譯的著色器附加到程序?qū)ο笊希缓笥?code>glLinkProgram鏈接它們:

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

最后調(diào)用glUseProgram函數(shù),激活程序:

glUseProgram(shaderProgram);

著色器對(duì)象鏈接到程序?qū)ο笠院螅枰獎(jiǎng)h除著色器對(duì)象:

glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);

鏈接頂點(diǎn)屬性

頂點(diǎn)著色器允許我們指定任何以頂點(diǎn)屬性為形式的輸入。這使其具有很強(qiáng)的靈活性的同時(shí),它還的確意味著我們必須手動(dòng)指定輸入數(shù)據(jù)的哪一個(gè)部分對(duì)應(yīng)頂點(diǎn)著色器的哪一個(gè)頂點(diǎn)屬性。所以,我們必須在渲染前指定OpenGL該如何解釋頂點(diǎn)數(shù)據(jù)。

我們的頂點(diǎn)緩沖數(shù)據(jù)會(huì)被解析為下面這樣子:


image

因此使用glVertexAttribPointer函數(shù)告訴OpenGL該如何解析頂點(diǎn)數(shù)據(jù):

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

第一個(gè)參數(shù)指定我們要配置的頂點(diǎn)屬性

第二個(gè)參數(shù)指定頂點(diǎn)屬性的大小。

第三個(gè)參數(shù)指定數(shù)據(jù)的類型

第四個(gè)參數(shù)定義我們是否希望數(shù)據(jù)被標(biāo)準(zhǔn)化(Normalize)。如果我們?cè)O(shè)置為GL_TRUE,所有數(shù)據(jù)都會(huì)被映射到0(對(duì)于有符號(hào)型signed數(shù)據(jù)是-1)到1之間。

第五個(gè)參數(shù)是步長(zhǎng),它告訴我們?cè)谶B續(xù)的頂點(diǎn)屬性組之間的間隔。

最后一個(gè)參數(shù)表示位置數(shù)據(jù)在緩沖中起始位置的偏移量(Offset)。

接下來(lái)使用glEnableVertexAttribArray函數(shù),以頂點(diǎn)屬性位置值作為參數(shù),啟用頂點(diǎn)屬性。

代碼最終大概長(zhǎng)這樣:

// 0. 復(fù)制頂點(diǎn)數(shù)組到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. 當(dāng)我們渲染一個(gè)物體時(shí)要使用著色器程序
glUseProgram(shaderProgram);
// 3. 繪制物體
someOpenGLFunctionThatDrawsOurTriangle();

頂點(diǎn)數(shù)組對(duì)象

Vertex Array Object(VAO)可以像頂點(diǎn)緩沖對(duì)象那樣被綁定,任何隨后的頂點(diǎn)屬性調(diào)用都會(huì)儲(chǔ)存在這個(gè)VAO中。

OpenGL的核心模式要求我們使用VAO,所以它知道該如何處理我們的頂點(diǎn)輸入。如果我們綁定VAO失敗,OpenGL會(huì)拒絕繪制任何東西。

一個(gè)頂點(diǎn)數(shù)組對(duì)象會(huì)儲(chǔ)存以下這些內(nèi)容:

glEnableVertexAttribArrayglDisableVertexAttribArray的調(diào)用。
通過(guò)glVertexAttribPointer設(shè)置的頂點(diǎn)屬性配置。
通過(guò)glVertexAttribPointer調(diào)用與頂點(diǎn)屬性關(guān)聯(lián)的頂點(diǎn)緩沖對(duì)象。

VAO的創(chuàng)建類似VBO:

unsigned int VAO;
glGenVertexArrays(1, &VAO);

要使用VAO,只需使用glBindVertexArray綁定VAO。從綁定之后起,我們應(yīng)該綁定和配置對(duì)應(yīng)的VBO和屬性指針,之后解綁VAO供之后使用。當(dāng)我們打算繪制一個(gè)物體的時(shí)候,我們只要在繪制物體前簡(jiǎn)單地把VAO綁定到希望使用的設(shè)定上就行了。

代碼大概是這樣的:

// ..:: 初始化代碼(只運(yùn)行一次 (除非你的物體頻繁改變)) :: ..
// 1. 綁定VAO
glBindVertexArray(VAO);
// 2. 把頂點(diǎn)數(shù)組復(fù)制到緩沖中供OpenGL使用
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. 設(shè)置頂點(diǎn)屬性指針
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);

[...]

// ..:: 繪制代(渲染循環(huán)中) :: ..
// 4. 繪制物體
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();

繪制三角形

glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);

glDrawArrays函數(shù)第一個(gè)參數(shù)是打算繪制的圖元的類型。第二個(gè)參數(shù)制訂了頂點(diǎn)數(shù)組的起始索引,第三個(gè)參數(shù)指定我們打算繪制的頂點(diǎn)個(gè)數(shù)。

最終三角形是長(zhǎng)這樣的:

triangle.png

你可以在這里找到源碼:三角形矩形

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評(píng)論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評(píng)論 3 428
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事?!?“怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,904評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,633評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,368評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,736評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,919評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,235評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,427評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,656評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,055評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,348評(píng)論 1 294
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,160評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,380評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容