OpenGL學(xué)習(xí)29——點(diǎn)陰影

點(diǎn)陰影(point shadow)

上一章節(jié)我們了解使用陰影映射創(chuàng)建動(dòng)態(tài)陰影,但是只適合用于定向光源產(chǎn)生的陰影璃诀,因此也稱(chēng)為定向陰影映射(directional shadow mapping)逢并。本章我們討論如何在所有方向上生成動(dòng)態(tài)陰影翠胰,這項(xiàng)技術(shù)特別適合點(diǎn)光源邓尤,因此也稱(chēng)為點(diǎn)陰影(point shadow)畔况,或更正式的名稱(chēng)叫做全向陰影映射(omnidirectional shadow mapping)敦跌。

  • 全向陰影映射與定向陰影映射相似背伴,都是先生成基于光源視角的深度圖,然后基于片元位置從深度圖采樣峰髓,最后通過(guò)比較每個(gè)片元當(dāng)前存儲(chǔ)的深度值來(lái)判斷是否處于陰影中傻寂,兩者的主要區(qū)別就是所使用的深度圖。
  • 全向陰影映射使用立方體貼圖將整個(gè)場(chǎng)景渲染到立方體的各個(gè)面携兵,并從這6個(gè)面中采樣點(diǎn)光源周?chē)h(huán)境的深度值疾掰。見(jiàn)下圖:(圖片取自書(shū)中
    點(diǎn)陰影

1. 生成深度立方體貼圖

  • 創(chuàng)建一個(gè)環(huán)繞光源的深度立方體貼圖的一種方式就是使用6個(gè)視矩陣分別渲染場(chǎng)景6次,每次將立方體貼圖的不同面附加到幀緩沖區(qū)徐紧。代碼看起來(lái)如下:
for (unsigned int i = 0; i < 6; i++)
{
    GLenum face = GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
    glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, face, depthCubemap, 0);
    BindViewMatrix(lightViewMatrices[i]);
    RenderScene();
}
  • 使用上述方法需要很多渲染操作調(diào)用静檬,太過(guò)繁瑣,本章我們采用另外一種方式:在幾何著色器中使用一個(gè)小技巧來(lái)讓我們用一次渲染調(diào)用完成立方體貼圖的構(gòu)建并级。(注意:采用幾何著色器的方式不一定性能更好拂檩,具體哪種方法性能更優(yōu)需根據(jù)渲染的場(chǎng)景,顯卡型號(hào)等進(jìn)行測(cè)試)
    1. 首先生成立方體貼圖嘲碧。
unsigned int depthCubemap;
glGenTextures(1, &depthCubemap);
    1. 為每個(gè)立方體貼圖面指定一個(gè)2D深度值紋理圖像稻励。
const unsigned int SHADOW_WIDTH = 1024, SHADOW_HEIGHT = 1024;
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
for (unsigned int i = 0; i < 6; i++)
{
    glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_DEPTH_COMPONENT, SHADOW_WIDTH, SHADOW_HEIGHT, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
}
    1. 設(shè)置立方體貼圖紋理參數(shù)。
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
    1. 使用glFramebufferTexture函數(shù)將立方體貼圖紋理附加為幀緩沖區(qū)的深度附件。
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glFramebufferTexture(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthCubemap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
  • 與上一章節(jié)相似望抽,陰影映射的兩個(gè)階段的偽代碼如下:
// 第一階段:渲染深度立方體貼圖
glViewport(0, 0, SHADOW_WIDTH, SHADOW_HEIGHT);
glBindFramebuffer(GL_FRAMEBUFFER, depthMapFBO);
glClear(GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
RenderScene();
glBindFramebuffer(GL_FRAMEBUFFER, 0);
// 第二階段:使用深度立方體貼圖渲染場(chǎng)景
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
ConfigureShaderAndMatrices();
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
RenderScene();

1.1 基于光源視角轉(zhuǎn)換

  • 設(shè)置好幀緩沖區(qū)和立方體貼圖后加矛,我們需要一種方法將場(chǎng)景的所有幾何基元轉(zhuǎn)換到光源6個(gè)方向上的光源空間。與陰影映射章節(jié)一樣煤篙,我們需要一個(gè)光源空間的轉(zhuǎn)換矩陣T斟览,但是這一次立方體的每個(gè)面都需要一個(gè)。
  • 光源空間轉(zhuǎn)換矩陣包含一個(gè)投影矩陣和一個(gè)視矩陣辑奈,對(duì)于每個(gè)轉(zhuǎn)換矩陣我們使用相同的投影矩陣苛茂。
float aspect = (float)SHADOW_WIDTH / (float)SHADOW_HEIGHT;
float near = 1.0f;
float far = 25.0f;
glm::mat4 shadowProj = glm::perspective(glm::radians(90.0f), apsect, near, far);
  • 對(duì)于投影矩陣需要注意的一點(diǎn)是我們將視角角度設(shè)置為90.0f。這是為了保證視場(chǎng)正好大到足夠填充立方體貼圖的一個(gè)面鸠窗,這樣所有的面就能夠沿著邊緣對(duì)齊味悄。
  • 每個(gè)方向我們使用相同的投影矩陣,但是對(duì)于視矩陣塌鸯,我們需要使用glm::lookAt函數(shù)創(chuàng)建面向立方體貼圖6個(gè)面的6個(gè)視矩陣侍瑟。方向按如下順序:右,左丙猬,上涨颜,下,近和遠(yuǎn)茧球。
std::vector<glm::mat4> shadowTransforms;
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(1.0, 0.0, 0.0),
                    glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(-1.0, 0.0, 0.0),
                    glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(0.0, 1.0, 0.0),
                    glm::vec3(0.0, 0.0, 1.0)));
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(0.0, -1.0, 0.0),
                    glm::vec3(0.0, 0.0, -1.0)));
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(0.0, 0.0, 1.0),
                    glm::vec3(0.0, -1.0, 0.0)));
shadowTransforms.push_back(shadowProj * 
                    glm::lookAt(lightPos, lightPos + glm::vec3(0.0, 0.0, -1.0),
                    glm::vec3(0.0, -1.0, 0.0)));

1.2 深度著色器

  • 要渲染深度值到立方體貼圖庭瑰,我們需要完整使用三種著色器。其中幾何著色器負(fù)責(zé)將頂點(diǎn)坐標(biāo)從世界空間轉(zhuǎn)換到6個(gè)不同的光源空間抢埋。因此弹灭,頂點(diǎn)著色器只是將頂點(diǎn)坐標(biāo)轉(zhuǎn)換到世界空間并傳遞給幾何著色器。頂點(diǎn)著色器如下:
#version 330 core
layout (location = 0) in vec3 aPos;

uniform mat4 model;

void main()
{
    gl_Position = model * vec4(aPos, 1.0);
}
  • 幾何著色器使用內(nèi)置變量gl_Layer來(lái)指定往立方體貼圖的那個(gè)面輸出基元揪垄。如果不管該變量穷吮,幾何著色器像往常一樣將數(shù)據(jù)傳遞到渲染管道的下一個(gè)階段,但是如果我們更新該變量我們可以控制將每個(gè)基元渲染到立方體貼圖的那個(gè)面饥努。當(dāng)然這需要有一個(gè)立方體貼圖紋理附加到當(dāng)前激活的幀緩沖區(qū)捡鱼。幾何著色器如下:
#version 330 core
layout (triangles) in;
layout (triangle_strip, max_vertices = 18) out;

uniform mat4 shadowMatrices[6];

out vec4 FragPos;

void main()
{
    for (int face = 0; face < 6; ++face)
    {
        gl_Layer = face;  // 指定渲染到那個(gè)面
        for (int i = 0; i < 3; ++i)   // 每個(gè)三角形頂點(diǎn)
        {
            FragPos = gl_in[i].gl_Position;
            gl_Position = shadowMatrices[face] * FragPos;
            EmitVertex();
        }
        EndPrimitive();
    }
}
  • 上一章我們使用一個(gè)空的片元著色器,讓OpenGL自己決定深度圖的深度值酷愧。這次我們自己計(jì)算最近片元位置與光源位置的線性距離作為深度值驾诈。片元著色器如下:
#version 330 core
in vec4 FragPos;

uniform vec3 lightPos;
uniform float far_plane;

void mian()
{
    // 獲取片元與光源的距離
    float lightDiatance = length(FragPos.xyz, lightPos);
    // 除以far_plane,映射到[0;1]范圍
    lightDiatance = lightDiatance / far_plane;
    // 寫(xiě)入深度值
    gl_FragDepth = lightDiatance;
}

2. 全向陰影映射

  • 渲染全向陰影的過(guò)程與定向陰影映射相似溶浴,只是這次我們需要綁定立方體貼圖紋理并且將光源投影的遠(yuǎn)平面變量傳遞給著色器乍迄。偽代碼如下:
glViewport(0, 0, SCR_WIDTH, SCR_HEIGHT);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
shader.use();
// ... 發(fā)送變量值到著色器
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_CUBE_MAP, depthCubemap);
// 綁定其他紋理
RenderScene();
  • 場(chǎng)景的頂點(diǎn)著色器和片元著色器與陰影映射章節(jié)的相似,差別在于我們現(xiàn)在使用方向矢量來(lái)采樣深度值士败,因此不需要光源空間的片元位置闯两。因此我們可以移除頂點(diǎn)著色器的FragPosLightSpace變量。
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;

out VS_OUT
{
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
} vs_out;

uniform mat4 projection;
uniform mat4 view;
uniform mat4 model;
uniform mat4 lightSpaceMatrix;

void main()
{
    vs_out.FragPos = vec3(model * vec4(aPos, 1.0));
    vs_out.Normal = transpose(inverse(mat3(model))) * aNormal;
    vs_out.TexCoords = aTexCoords;
    gl_Position = projection * view * model * vec4(aPos, 1.0);
}
  • 片元著色器主要改變?cè)谟陉幱坝?jì)算函數(shù),因?yàn)楝F(xiàn)在我們需要從立方體貼圖紋理而不是二維紋理采樣深度值生蚁。下面我們逐步討論函數(shù)的內(nèi)容。首先我們需要從立方體貼圖紋理檢索深度值戏自。
float ShadowCaculation(float fragPos)
{
    vec3 fragToLight = fragPos - lightPos;
    float closestDepth = texture(depthMap, fragToLight).r;
}
  • 將深度值從[0, 1]轉(zhuǎn)換到[0, far_plane]邦投。
closestDepth *= far_plane;
  • 檢索當(dāng)前片元的深度值,由前面我們計(jì)算深度值的方式擅笔,我們知道其實(shí)就是片元與光源之間的距離志衣。
float currentDepth = length(fragToLight);
  • 計(jì)算陰影值并應(yīng)用偏移消除陰影粉刺。
float bias = 0.05;
float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;
  • 最后猛们,完整的片元著色器如下:
#version 330 core
out vec4 FragColor;

in VS_OUT
{
    vec3 FragPos;
    vec3 Normal;
    vec2 TexCoords;
} fs_in;

uniform sampler2D diffuseTexture;
uniform samplerCube shadowMap;

uniform vec3 lightPos;
uniform vec3 viewPos;

float ShadowCaculation(vec3 fragPos)
{
    vec3 fragToLight = fragPos - lightPos;
    float closestDepth = texture(shadowMap, fragToLight).r;
    closestDepth *= far_plane;
    float currentDepth = length(fragToLight);
    float bias = 0.05;
    float shadow = currentDepth - bias > closestDepth ? 1.0 : 0.0;

    return shadow;
}

void main()
{
    vec3 color = texture(diffuseTexture, fs_in.TexCoords).rgb;
    vec3 normal = normalize(fs_in.Normal);
    vec3 lightColor = vec3(0.3);
    // ambient
    vec3 ambient = 0.3 * color;
    // diffuse
    vec3 lightDir = normalize(lightPos - fs_in.FragPos);
    float diff = max(dot(lightDir, normal), 0.0);
    vec3 diffuse = diff * lightColor;
    // specular
    vec3 viewDir = normalize(viewPos - fs_in.FragPos);
    float spec = 0.0;
    vec3 halfwayDir = normalize(lightDir + viewDir);
    spec = pow(max(dot(normal, halfwayDir), 0.0), 64.0);
    vec3 specular = spec * lightColor;
    // caculate shadow
    float shadow = ShadowCalculation(fs_in.FragPos);
    vec3 lighting = (ambient + (1.0 -shadow) * (diffuse + specular)) * color;

    FragColor = vec4(lighting, 1.0);
}
  • 渲染效果念脯。


    全向陰影映射
  • 當(dāng)程序渲染異常時(shí),一般我們都會(huì)檢查深度圖是否正常構(gòu)建弯淘÷痰辏可視化深度緩沖區(qū)我們可以采用ShadowCaculation函數(shù)中的closestDepth作為片元輸出。
vec3 fragToLight = fs_in.FragPos - lightPos;
float closestDepth = texture(shadowMap, fragToLight).r;
FragColor = vec4(vec3(closestDepth / far_plane), 1.0);
  • 深度立方體貼圖庐橙。


    深度立方體貼圖

3. PCF

邊緣鋸齒
  • 全向陰影映射與定向陰影映射都基于相同的準(zhǔn)則假勿,因此都存在依賴(lài)于分辨率的偽影(見(jiàn)上圖)。我們可以采取與上一章相同的PCF過(guò)濾器來(lái)平滑邊緣鋸齒态鳖。在上一章PCF的基礎(chǔ)上我們添加第三個(gè)維度转培,如下:
float shadow = 0.0;
float bias = 0.05;
float samples = 4.0;
float offset = 0.1;
for(float x = -offset; x < offset; x += offset / (samples * 0.5))
{
    for(float y = -offset; y < offset; y += offset / (samples * 0.5))
    {
        for(float z = -offset; z < offset; z += offset / (samples * 0.5))
        {
            float closestDepth = texture(depthMap, fragToLight + vec3(x, y, z)).r;
            closestDepth *= far_plane;
            if(currentDepth - bias > closestDepth)
                shadow += 1.0;
        }
    }
}
shadow /= (samples * samples * samples);
  • 渲染效果如下:


    PCF

    PCF拉近效果
  • 上述PCF使用四個(gè)采樣點(diǎn),這樣每個(gè)片元需要進(jìn)行64次采樣浆竭,增加了很多計(jì)算浸须。而且這些采樣很多都是冗余的,因?yàn)檫@里面很多與原來(lái)采樣的方向矢量十分接近邦泄。但是我們也很難區(qū)分哪些子采樣是冗余的删窒,有一個(gè)小技巧就是我們使用一個(gè)偏移數(shù)組來(lái)區(qū)分采樣方向矢量,讓不同子采樣指向不同的方向顺囊。這樣我們就可以降低子采樣的數(shù)量易稠。下面是一個(gè)20個(gè)元素的偏移數(shù)組:
vec3 samplesOffsetDirections[20] = vec3[]
(
    vec3(1, 1,  1), vec3( 1, -1,  1), vec3(-1, -1,  1), vec3(-1, 1,  1), 
    vec3(1, 1, -1), vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1),
    vec3(1, 1,  0), vec3( 1, -1,  0), vec3(-1, -1,  0), vec3(-1, 1,  0),
    vec3(1, 0,  1), vec3(-1,  0,  1), vec3( 1,  0, -1), vec3(-1, 0, -1),
    vec3(0, 1,  1), vec3( 0, -1,  1), vec3( 0, -1, -1), vec3( 0, 1, -1)
);
  • 使用上面的偏移數(shù)組,我們可以調(diào)整PCF算法包蓝,采用固定數(shù)量的子采樣來(lái)對(duì)立方體貼圖進(jìn)行采樣驶社。
float shadow = 0.0;
float bias = 0.05;
int samples = 20.0;
float viewDistance = length(viewPos - fragPos);
float diskRadius = 0.05;
for(int i = 0;i < 20; ++i)
{
    float closestDepth = texture(depthMap, fragToLight + samplesOffsetDirections[i] * diskRadius).r;
    closestDepth *= far_plane;
    if(currentDepth - bias > closestDepth)
        shadow += 1.0;
}
shadow /= float(samples);
  • 另外一個(gè)技巧是我們可以根據(jù)觀察者與片元的距離調(diào)整diskRadius的大小,這樣可以讓視角拉遠(yuǎn)時(shí)陰影更柔和测萎,拉近時(shí)則更銳化亡电。
float diskRadius = (1.0 + (viewDistance / far_plane)) / 25.0;
  • 渲染效果。


    偏移數(shù)組PCF
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末硅瞧,一起剝皮案震驚了整個(gè)濱河市份乒,隨后出現(xiàn)的幾起案子棍苹,更是在濱河造成了極大的恐慌,老刑警劉巖翎朱,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件橙依,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡颂暇,警方通過(guò)查閱死者的電腦和手機(jī)缺谴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)耳鸯,“玉大人湿蛔,你說(shuō)我怎么就攤上這事∠嘏溃” “怎么了阳啥?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)财喳。 經(jīng)常有香客問(wèn)我察迟,道長(zhǎng),這世上最難降的妖魔是什么耳高? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任卷拘,我火速辦了婚禮,結(jié)果婚禮上祝高,老公的妹妹穿的比我還像新娘栗弟。我一直安慰自己,他們只是感情好工闺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布乍赫。 她就那樣靜靜地躺著,像睡著了一般陆蟆。 火紅的嫁衣襯著肌膚如雪雷厂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天叠殷,我揣著相機(jī)與錄音改鲫,去河邊找鬼。 笑死林束,一個(gè)胖子當(dāng)著我的面吹牛像棘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播壶冒,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼缕题,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了胖腾?” 一聲冷哼從身側(cè)響起烟零,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤瘪松,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后锨阿,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體宵睦,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年墅诡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了壳嚎。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡书斜,死狀恐怖诬辈,靈堂內(nèi)的尸體忽然破棺而出酵使,到底是詐尸還是另有隱情荐吉,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布口渔,位于F島的核電站样屠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏缺脉。R本人自食惡果不足惜痪欲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望攻礼。 院中可真熱鬧业踢,春花似錦、人聲如沸礁扮。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)太伊。三九已至雇锡,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間僚焦,已是汗流浹背锰提。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芳悲,地道東北人立肘。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像名扛,于是被迫代替她去往敵國(guó)和親赛不。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

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