特效中的著色器代碼詳解

前言

圖像和視頻渲染離不開OpenGLES柏副,在可編程的OpenGLES渲染管線中贮懈,頂點著色器和片段著色器使得OpenGLES更加靈活。

通過以下幾種常見的特效烘绽,來認識和了解如何編寫一個片段著色器颈渊。

分屏效果

片段著色器代碼來源于這篇文章遂黍。代碼如下:

// 分六屏特效
precision highp float;
uniform sampler2D inputTexture;
varying highp vec2 textureCoordinate;

void main() {
    highp vec2 uv = textureCoordinate;
    // 左右分三屏
    if (uv.x <= 1.0 / 3.0) {
        uv.x = uv.x + 1.0 / 3.0;
    } else if (uv.x >= 2.0 / 3.0) {
        uv.x = uv.x - 1.0 / 3.0;
    }
    // 上下分兩屏,保留 0.25 ~ 0.75部分
    if (uv.y <= 0.5) {
        uv.y = uv.y + 0.25;
    } else {
        uv.y = uv.y - 0.25;
    }
    gl_FragColor = texture2D(inputTexture, uv);
}

這是一個典型的片段著色器代碼儡炼。
第一行代碼用精度修飾符聲明了精度類型妓湘。在OpenGLES中,有三種精度類型乌询,高(highp)榜贴、中(mediump)、低(lowp),默認是高精度也就是highp的妹田。
第二行代碼聲明了一個采樣器唬党,用于訪問著色器中的紋理圖像。
第三行代碼聲明了一個高精度的2D紋理坐標變量textureCoordinate 鬼佣。
其中precision 代表精度修飾符驶拱;uniform是變量類型限定符,代表統(tǒng)一變量晶衷,統(tǒng)一變量存儲應用程序通過OpenGLESAPI傳入著色器的只讀值蓝纲,對于保存著色器所需的所有數據類型(如變換矩陣阴孟、照明參數和顏色)都很有用。統(tǒng)一變量的命名空間在頂點和片段著色器中是共享的税迷,也就是說永丝,如果頂點和片段著色器一起連接到一個程序對象,他們就會共享同一組統(tǒng)一變量箭养;varying變量是頂點著色器中傳遞給片段著色器的變量值慕嚷,它修飾了一個vec2類型的變量,是一個(x, y)標識的點毕泌。

main函數是程序對象開始調用片段著色器的入口喝检,在該函數中,聲明一個高精度的臨時坐標變量uv撼泛,用于接收頂點著色器傳入的紋理點的坐標挠说。
接下來是具體分屏的邏輯代碼,根據需要填充的原圖像的區(qū)域坎弯,來修改當前的填充區(qū)域纺涤。
根據當前紋理坐標點的x坐標译暂,確定該點是否在整個紋理的三分之一以內以及是否超過了紋理坐標的三分之二驾荣。它分別代表了將紋理的x坐標[0, 1]的紋理區(qū)間分為了3個部分泊窘。紋理讀取的結果和從頂點著色器傳遞的輸入值textureCoordinate用來確定的填充紋理的區(qū)域uv,生成新的紋理。這個裁剪坐標是可以自定義取原圖像的部分區(qū)域谭羔。

上面是一個需要裁剪的示例,下面是一個不需要裁剪的示例
// 四分屏
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;

void main() {
    vec2 uv = TextureCoordsVarying.xy;
    if(uv.x <= 0.5){
        uv.x = uv.x * 2.0;
    }else{
        uv.x = (uv.x - 0.5) * 2.0;
    }
    
    if (uv.y<= 0.5) {
        uv.y = uv.y * 2.0;
    }else{
        uv.y = (uv.y - 0.5) * 2.0;
    }
    
    gl_FragColor = texture2D(Texture, uv);
}

如果原圖像是正方形莉撇,那么鳖眼,4分屏既2x2在不需要對原圖像進行裁剪的情況下,將填充范圍修改為當前值得二倍祭衩。

圖像灰度

這是圖像灰度的片段著色器代碼

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main(){
    // 獲取對應紋理坐標系下色顏色值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    // 將顏色mask與變換因子相乘得到灰度值
    float luminance = dot(mask.rgb, W);
    // 將灰度值轉換為(luminance,luminance,luminance,mask.a)并填充到像素中
    gl_FragColor = vec4(vec3(luminance), 1.0);
}

在不同平臺有多重方法可以實現灰度效果灶体,如GPUImage庫,以及iOS的CoreImage掐暮。但在著色器上可以選擇權值法蝎抽、平均值法、取綠色值法中路克,權值方法是公認的效果最好的樟结,如上,參考這里精算。

浮點算法:Gray = R*0.3 + G*0.59 + B*0.11 (RGB的權重總和為1)
整數方法:Gray = (R*30 + G*59 + B*11)/100(RGB的權重總和為100)
移位方法:Gray = (R*76 + G*151 + B*28)>>8

第一行代碼聲明了高精度的浮點類型瓢宦。
第二行代碼聲明了一個采樣器,用于讀取紋理灰羽。
第三行代碼是頂點著色器中傳入的紋理的坐標變量驮履。
第四行代碼是一個高精度的向量的常量值鱼辙,該值和目標點的RGBA相乘會得到一個灰度值。
main函數中玫镐,第一行代碼使用texture2D函數從采樣器中讀取給定的坐標點的像素的顏色值RGBA(是vec4類型);第二行代碼是將讀取到的紋理的顏色值與上述常量進行點乘座每,調用dot函數即可。
最后一行代碼就是將計算好的新的像素值返回給需要填充的像素摘悴,gl_FragColor也是片段著色器的內置函數峭梳,主要用來設置片元像素的顏色。

漩渦效果

image.png

片段著色器代碼如下

precision mediump float;
// 計算圓周需要用到的π
const float PI = 3.14159265;
// 采樣器
uniform sampler2D image;
// 旋轉的角度蹂喻,Radius是旋轉的半徑
const float uD = 80.0; 
// 設置為0.5葱椭,其實就是為了取旋渦半徑用到的
const float uR = 0.5;
// 頂點著色器傳入的紋理坐標
varying vec2 vTexcoord;
// 主函數
void main() {
    // 聲明一個整形二維向量,x = 512, y = 512口四,其實就是一個512寬高的正方形
    ivec2 ires = ivec2(512, 512);
    // 取出當前正方形的邊長也就是被旋轉區(qū)域的圓的直徑
    float res = float(ires.s);
    // 當前紋理坐標
    vec2 st = vTexcoord;
    // 旋轉半徑由正方形的邊長和Ur相乘得到
    float radius = Res * uR;
    // 通過直徑獲取紋理坐標對應的物體坐標孵运,st是當前的紋理坐標。
    vec2 xy = Res * st;
    // 取出紋理坐標減去半徑之后的具體物體坐標蔓彩,向量相減
    vec2 dxy = xy - vec2(res/2., res/2.);  
    // 當前半徑
    float r = length(dxy);
    // atan函數獲取當前紋理坐標的正切值治笨,與需要旋轉的值相加得到新的旋轉角度
    float beta = atan(dxy.y, dxy.x) + radians(uD) * 2.0 * (-(r/radius)*(r/radius) + 1.0);//(1.0 - r/radius);
    vec2 xy1 = xy;
    // 當前半徑小于旋轉半徑時,將該點坐標旋轉成新的目標角度
    if(r <= radius)  {
        xy1 = res/2.0 + r*vec2(cos(beta), sin(beta));
    }
    // 計算新的紋理坐標
    st = xy1/res;
    // 通過采樣器設置新的紋理坐標赤嚼,并返回給內置函數glFragColor旷赖,設置新的片元。
    vec3 irgb = texture2D(image, st).rgb;
    gl_FragColor = vec4( irgb, 1.0 );
}

前幾行代碼和之前唯一不同的是多了一個常量π的聲明更卒,實現圖像的漩渦效果的原理是在某個半徑范圍里等孵,把當前采樣點旋轉一定角度,旋轉以后當前點的顏色就被旋轉后的點的顏色代替蹂空,因此整個半徑范圍里會有旋轉的效果俯萌。如果旋轉的時候旋轉角度隨著當前點離半徑的距離遞減,整個圖像就會出現漩渦效果上枕。這里使用的了拋物線遞減因子:(1.0-(r/Radius)*(r/Radius) )咐熙,參考這里這里
main函數中辨萍,不同變量的意義如下

PI:我們的計算中的π棋恼,取值3.14159265
uR:設置為0.5,其實就是為了取旋渦半徑用到的
ivec2:整形的二維向量分瘦,這里的ires其實就是一個512寬高的正方形
Res:取出當前正方形的邊長
uD:旋轉的角度蘸泻,Radius是我們旋轉的半徑
xy:通過直徑獲得紋理坐標對應的物體坐標
dxy:取出紋理坐標減去半徑之后的具體物體坐標
r:當前半徑

atan(dxy.y, dxy.x):獲取的當前的夾角,如果不設置其他的值嘲玫,那么當前圖片沒有任何旋轉效果悦施。
radians(uD) * 2.0:在原來夾角的基礎上加上我們設置的旋轉角度80x2 = 160
(-(r/Radius)(r/Radius) + 1.0):拋物線衰減因子,通過距離圓心的距離計算我們旋轉衰減的增益值

縮放效果

縮放效果圖及源碼在這里去团。
縮放效果也是常見的視頻特效的效果抡诞,圖片有一個放大的過程穷蛹,然后再回彈。它可以通過修改頂點坐標和紋理坐標的對應關系來實現昼汗。
修改頂點坐標和紋理坐標肴熏,既可以在頂點著色器實現,也可以在片元著色器上實現顷窒,下面是一個頂點著色器的示例:

// 聲明頂點坐標屬性
attribute vec4 Position; 
// 聲明紋理坐標屬性(attribute修飾符只在頂點著色器中使用)
attribute vec2 TextureCoords; 
// 聲明紋理坐標將修改后的紋理坐標傳遞給片段著色器
varying vec2 TextureCoordsVarying; 
// 統(tǒng)一變量時間戳
uniform float Time;
// PI 
const float PI = 3.1415926; 
// 頂點著色器調用入口
void main (void) {
    // ?次縮放效果時? = 0.6ms 
    float duration = 0.6; 
    // 最?縮放幅度 
    float maxAmplitude = 0.3; 
    // 表示傳?的時間周期.即time的范圍被控制在[0.0~0.6];  mod(a,b),求模運算. 即a%b
    float time = mod(Time, duration); 
    // amplitude 表示振幅蛙吏,引? PI 的?的是為了使? sin 函數,將 amplitude 的范圍控制在 1.0 ~ 1.3 之間鞋吉,并隨著時間變化 
    float amplitude = 1.0 + maxAmplitude * abs(sin(time * (PI / duration))); 
    // 將頂點坐標的 x 和 y 分別乘上?個放?系數鸦做,在紋理坐標不變的情況下,就達到了拉伸的 效果谓着。x,y 放?; z和w保存不變 
    gl_Position = vec4(Position.x * amplitude, Position.y * amplitude, Position.zw);
    // 紋理坐標傳遞給TextureCoordsVarying泼诱,該坐標可以在片段著色器中進行使用
    TextureCoordsVarying = TextureCoords;
}

靈魂出竅效果

示例源地址
效果圖

20200816130455528.gif

由圖中可以發(fā)現此效果有多個圖層,最下面的圖層不動赊锚,上面的圖層隨著時間的變化變大治筒,并且透明度變低直至透明。此效果是由多個圖層構成舷蒲,那么就需要顏色混合耸袜,所以此效果需要在片元著色器中實現,頂點著色器不變阿纤。
片段著色器代碼如下

// 聲明為高精度
recision highp float;
// 聲明全局變量 采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標變量
varying vec2 TextureCoordsVarying;
// 統(tǒng)一變量時間
uniform float Time;

void main (void) {
    // 動畫效果時長
    float duration = 0.7;
    // 最大透明度
    float maxAlpha = 0.4;
    // 放大最大的倍數
    float maxScale = 1.8;
    
    // 當前時間的進度 0-1句灌,mod函數求模夷陋,當前時間%動畫時長
    float progress = mod(Time, duration) / duration; 
    // 當前透明度的進度 0.4 - 0
    float alpha = maxAlpha * (1.0 - progress);
    // 放大倍數的進度 1 - 1.8
    float scale = 1.0 + (maxScale - 1.0) * progress;
    
    // 放大后的x值 0.5是中心點欠拾,中心點是不變的
    float weakX = 0.5 + (TextureCoordsVarying.x - 0.5) / scale;
    // 放大后的x值 0.5是中心點,中心點不變
    float weakY = 0.5 + (TextureCoordsVarying.y - 0.5) / scale;
    // 放大后的紋理坐標
    vec2 weakTextureCoords = vec2(weakX, weakY);
    // 放大后的紋素圖層
    vec4 weakMask = texture2D(Texture, weakTextureCoords);
    // 正常的紋素圖層
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    // 通過矩陣和透明度的乘積再相加實現顏色混合模式
    gl_FragColor = mask * (1.0 - alpha) + weakMask * alpha;
}
顏色混合模式

在上述靈魂出竅示例中骗绕,用到了顏色混合模式藐窄。什么是混合模式?這里是一篇不錯的參考酬土【H蹋混合模式是圖像處理技術中的一個技術名詞,主要功效是可以用不同的方法將對象顏色與底層對象的顏色混合撤缴。將一種混合模式應用于某一對象時刹枉,在此對象的圖層或組下方的任何對象上都可看到混合模式的效果。通過上面索引的文章屈呕,我們已經能夠了解到微宝,顏色混合其實是對RGBA矩陣執(zhí)行加、減虎眨、乘蟋软,以及其他mix操作镶摘。

正片疊底是一種常見的混合方法,它的片段著色器代碼如下:

// 紋理坐標
varying vec2 V_Texcoord;
 // 聲明統(tǒng)一變量基礎紋理
uniform sampler2D U_BaseTexture;
// 聲明統(tǒng)一變量混合紋理
uniform sampler2D U_BlendTexture;

void main() {
    // 從混合紋理采樣器中根據當前紋理坐標獲取RGBA
    vec4 blendColor=texture2D(U_BlendTexture,V_Texcoord);
    // 從混合紋理采樣器中根據當前紋理坐標獲取RGBA
    vec4 baseColor=texture2D(U_BaseTexture,V_Texcoord);
    // 重新賦予新的正片疊底后的紋理RGBA岳守,正片疊底是顏色矩陣的相乘凄敢。
    gl_FragColor=blendColor*baseColor;
}

抖動效果

抖動效果是抖音的經典圖標和效果,其效果如下(示例代碼源同靈魂出竅)湿痢。


20200816130843536.gif

過程:圖層變大涝缝,并且顏色發(fā)生了偏移,然后所以的再變回原來的效果譬重。著色器代碼如下:

// 聲明片段著色器中為高精度浮點型
precision highp float;
// 聲明統(tǒng)一變量紋理采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標
varying vec2 TextureCoordsVarying;
// 統(tǒng)一變量時間
uniform float Time;

void main (void) {
    // 抖動時長
    float duration = 0.7;
    // 放大上限
    float maxScale = 1.1;
    // 顏色偏移步長
    float offset = 0.02;
    // 當前時間的進度 0-1, mod是求模函數俊卤,即當前時間%抖動時長
    float progress = mod(Time, duration) / duration; // 0~1
    // 顏色偏移的進度
    vec2 offsetCoords = vec2(offset, offset) * progress;
    // 縮放的進度
    float scale = 1.0 + (maxScale - 1.0) * progress;
    
    // 放大后的紋理坐標,中心點的紋理坐標+當前坐標減去中心點的坐標的差除以縮放進度害幅,得到放大后的紋理坐標
    vec2 ScaleTextureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale;
    // R偏移的紋素消恍,涉及到紋素的變化都需要從采樣器中讀取
    vec4 maskR = texture2D(Texture, ScaleTextureCoords + offsetCoords);
     // B偏移的紋素
    vec4 maskB = texture2D(Texture, ScaleTextureCoords - offsetCoords);
     // 放大后的紋素
    vec4 mask = texture2D(Texture, ScaleTextureCoords);
    
    gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a);
}

反相(也就是反色效果)

image.png
// 聲明片段著色器中為高精度浮點型
precision highp float;
// 聲明統(tǒng)一變量紋理采樣器
uniform sampler2D Texture;
// 頂點著色器傳入的紋理坐標
varying vec2 TextureCoordsVarying;

void main (void) {
    // 根據紋理坐標點獲取當前紋理的紋素
    vec4 textureColor = texture2D(Texture,TextureCoordsVarying);
    // 將當前紋素的RGB值取反,即可得到圖像的反相以现。
    gl_FragColor = vec4(1.0 - textureColor.r,1.0 -textureColor.g,1.0 -textureColor.b,1)
}

高斯模糊

效果如下:

image.png

參考鏈接
模糊過濾的基本原理是對附近像素進行加權和來混合當前像素顏色狠怨。通常使用的權重隨距離減小(二維屏幕空間距離),距離當前像素較遠的像素貢獻較小邑遏。

頂點著色器

// 聲明一個統(tǒng)一變量的 4x4矩陣
uniform mat4 uMVPMatrix;
// 紋理坐標 給
attribute vec4 aPosition;
// 紋理坐標
attribute vec4 aTextureCoord;

// 高斯算子大小(3 x 3)
const int GAUSSIAN_SAMPLES = 9;
// 統(tǒng)一變量 橫向偏移
uniform float texelWidthOffset;
// 統(tǒng)一變量 縱向偏移
uniform float texelHeightOffset;
// 計算后傳給片段著色器的紋理坐標
varying vec2 textureCoordinate;
// 傳給片段著色器的模糊坐標向量集
varying vec2 blurCoordinates[GAUSSIAN_SAMPLES];

void main() {
    // 通過矩陣變換 獲取新的位置
    gl_Position = uMVPMatrix * aPosition;
    // 紋理坐標(x, y)
    textureCoordinate = aTextureCoord.xy;
    // 用于計算模糊步長
    int multiplier = 0;
    // 模糊步長
    vec2 blurStep;
    // 單個紋理的xy步長偏移量
    vec2 singleStepOffset = vec2(texelHeightOffset, texelWidthOffset);
    // 計算3x3矩陣中的模糊步長與當前紋理坐標的和佣赖,并保存到坐標集合中。
    for (int i = 0; i < GAUSSIAN_SAMPLES; i++) {
        multiplier = (i - ((GAUSSIAN_SAMPLES - 1) / 2));
        blurStep = float(multiplier) * singleStepOffset;
        blurCoordinates[i] = aTextureCoord.xy + blurStep;
    }
}

片段著色器

// 聲明中等精度
precision mediump float;
// 
varying highp vec2 textureCoordinate;
uniform sampler2D inputTexture;
const lowp int GAUSSIAN_SAMPLES = 9;
varying highp vec2 blurCoordinates[GAUSSIAN_SAMPLES];

void main()
{
    lowp vec3 sum = vec3(0.0);
   lowp vec4 fragColor=texture2D(inputTexture,textureCoordinate);

    sum += texture2D(inputTexture, blurCoordinates[0]).rgb * 0.05;
    sum += texture2D(inputTexture, blurCoordinates[1]).rgb * 0.09;
    sum += texture2D(inputTexture, blurCoordinates[2]).rgb * 0.12;
    sum += texture2D(inputTexture, blurCoordinates[3]).rgb * 0.15;
    sum += texture2D(inputTexture, blurCoordinates[4]).rgb * 0.18;
    sum += texture2D(inputTexture, blurCoordinates[5]).rgb * 0.15;
    sum += texture2D(inputTexture, blurCoordinates[6]).rgb * 0.12;
    sum += texture2D(inputTexture, blurCoordinates[7]).rgb * 0.09;
    sum += texture2D(inputTexture, blurCoordinates[8]).rgb * 0.05;

    gl_FragColor = vec4(sum, fragColor.a);
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末记盒,一起剝皮案震驚了整個濱河市憎蛤,隨后出現的幾起案子,更是在濱河造成了極大的恐慌纪吮,老刑警劉巖俩檬,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異碾盟,居然都是意外死亡棚辽,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門冰肴,熙熙樓的掌柜王于貴愁眉苦臉地迎上來屈藐,“玉大人,你說我怎么就攤上這事熙尉×撸” “怎么了?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵检痰,是天一觀的道長包归。 經常有香客問我,道長攀细,這世上最難降的妖魔是什么箫踩? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任爱态,我火速辦了婚禮,結果婚禮上境钟,老公的妹妹穿的比我還像新娘锦担。我一直安慰自己,他們只是感情好慨削,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布洞渔。 她就那樣靜靜地躺著,像睡著了一般缚态。 火紅的嫁衣襯著肌膚如雪磁椒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天玫芦,我揣著相機與錄音浆熔,去河邊找鬼。 笑死桥帆,一個胖子當著我的面吹牛医增,可吹牛的內容都是我干的。 我是一名探鬼主播老虫,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叶骨,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了祈匙?” 一聲冷哼從身側響起忽刽,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎夺欲,沒想到半個月后跪帝,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡洁闰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年歉甚,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扑眉。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖赖钞,靈堂內的尸體忽然破棺而出腰素,到底是詐尸還是另有隱情,我是刑警寧澤雪营,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布弓千,位于F島的核電站,受9級特大地震影響献起,放射性物質發(fā)生泄漏洋访。R本人自食惡果不足惜镣陕,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姻政。 院中可真熱鬧呆抑,春花似錦、人聲如沸汁展。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽食绿。三九已至侈咕,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間器紧,已是汗流浹背耀销。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留铲汪,地道東北人树姨。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像桥状,于是被迫代替她去往敵國和親帽揪。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內容