GLSL in Unity 系列文章(八):實時陰影實現(xiàn)——Cascaded Shadow Mapping

Unity實時陰影實現(xiàn)——Shadow Mapping
Unity的實時陰影-ShadowMap實現(xiàn)原理
Unity實時陰影實現(xiàn)——Cascaded Shadow Mapping

用GLSL實現(xiàn)CMS(Cascaded Shadow Mapping)說了好久了,今天抽空搞一下,代碼是參考網(wǎng)上大神的,不過翻譯成GLSL還是遇到了一些坑,先看看效果吧:


CMS

鋸齒還是有點嚴重,提高shadowmap的分辨率好像沒什么效果,軟陰影可能好點吧,不過后面有時間再搞吧。

1.生成深度圖
Shader "GLSL/ShadowMapping/Caster" 
{
    SubShader {
        Tags {          
            "RenderType" = "Opaque"
        }
        Pass {
            Fog { Mode Off }
            Cull front//設(shè)置Cull front可解決面向光源的acne問題
            GLSLPROGRAM
            //gl_Vertex 頂點
            //gl_Position 裁剪空間坐標輸出到片元著色器
            //gl_FragColor 輸出顏色
            #include "UnityCG.glslinc"
            #include "lib/Custom.glslinc"
            
            uniform float _gShadowBias;

            struct v2f {
                vec4 pos;//其實沒用到,為了展示如何使用glsl結(jié)構(gòu)體
                vec2 depth;
            };

            #ifdef VERTEX
            out v2f v;
            void main()
            {            
                gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
                gl_Position.z += _gShadowBias;
                v.depth = gl_Position.zw;
            }
            #endif
            #ifdef FRAGMENT
            in v2f v;
            void main()
            {
                float depth = v.depth.x / v.depth.y;

            #if defined (UNITY_REVERSED_Z)
                depth = 1 - depth;       //(1, 0)-->(0, 1)
            #else
                depth = depth*0.5 + 0.5; //(-1, 1)-->(0, 1)
            #endif

                gl_FragColor = EncodeFloatRGBA(depth);
            }
            #endif
            ENDGLSL  
        }
    }
}
2.生成級聯(lián)相關(guān)數(shù)據(jù)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class CascadedShadowMapping : MonoBehaviour
{
    public Light dirLight;
    Camera dirLightCamera;//燈光空間的相機

    public int shadowResolution = 1;
    public Shader shadowCaster = null;

    private Matrix4x4 biasMatrix = Matrix4x4.identity;

    List<Matrix4x4> world2ShadowMats = new List<Matrix4x4>(4);
    GameObject[] dirLightCameraSplits = new GameObject[4];
    RenderTexture[] depthTextures = new RenderTexture[4];//四級陰影紋理

    void OnDestroy()
    {
        dirLightCamera = null;

        for (int i = 0; i < 4; i++)
        {
            if (depthTextures[i])
            {
                DestroyImmediate(depthTextures[i]);
            }
        }
    }

    void Awake()
    {
        biasMatrix.SetRow(0, new Vector4(0.5f, 0, 0, 0.5f));
        biasMatrix.SetRow(1, new Vector4(0, 0.5f, 0, 0.5f));
        biasMatrix.SetRow(2, new Vector4(0, 0, 0.5f, 0.5f));
        biasMatrix.SetRow(3, new Vector4(0, 0, 0, 1f));

        InitFrustumCorners();
    }

    //初始化rt,4級陰影紋理對應(yīng)4張rt
    private void CreateRenderTexture()
    {
        RenderTextureFormat rtFormat = RenderTextureFormat.Default;
        if (!SystemInfo.SupportsRenderTextureFormat(rtFormat))
            rtFormat = RenderTextureFormat.Default;

        for (int i = 0; i < 4; i++)
        {
            depthTextures[i] = new RenderTexture(1024, 1024, 24, rtFormat);
            Shader.SetGlobalTexture("_gShadowMapTexture" + i, depthTextures[i]);
        }
    }

    //創(chuàng)建燈光攝像機
    public Camera CreateDirLightCamera()
    {
        GameObject goLightCamera = new GameObject("Directional Light Camera");
        Camera LightCamera = goLightCamera.AddComponent<Camera>();

        LightCamera.cullingMask = 1 << LayerMask.NameToLayer("Caster");
        LightCamera.backgroundColor = Color.white;
        LightCamera.clearFlags = CameraClearFlags.SolidColor;
        LightCamera.orthographic = true;
        LightCamera.enabled = false;

        for (int i = 0; i < 4; i++)
        {
            dirLightCameraSplits[i] = new GameObject("dirLightCameraSplits" + i);
        }

        return LightCamera;
    }

    private void Update()
    {
        CalcMainCameraSplitsFrustumCorners();
        CalcLightCameraSplitsFrustum();

        if (dirLight)
        {
            if (!dirLightCamera)
            {
                dirLightCamera = CreateDirLightCamera();
                CreateRenderTexture();
            }

            Shader.SetGlobalFloat("_gShadowBias", 0.005f);
            Shader.SetGlobalFloat("_gShadowStrength", 0.5f);

            world2ShadowMats.Clear();
            //構(gòu)建4級陰影紋理
            for (int i = 0; i < 4; i++)
            {
                ConstructLightCameraSplits(i);

                dirLightCamera.targetTexture = depthTextures[i];
                dirLightCamera.RenderWithShader(shadowCaster, "");

                Matrix4x4 projectionMatrix = GL.GetGPUProjectionMatrix(dirLightCamera.projectionMatrix, false);
                world2ShadowMats.Add(projectionMatrix * dirLightCamera.worldToCameraMatrix);
            }

            Shader.SetGlobalMatrixArray("_gWorld2Shadow", world2ShadowMats);
        }
    }

    float[] _LightSplitsNear;
    float[] _LightSplitsFar;

    struct FrustumCorners
    {
        public Vector3[] nearCorners;
        public Vector3[] farCorners;
    }

    FrustumCorners[] mainCamera_Splits_fcs;
    FrustumCorners[] lightCamera_Splits_fcs;

    //初始化4級遠近裁剪面坐標
    void InitFrustumCorners()
    {
        mainCamera_Splits_fcs = new FrustumCorners[4];
        lightCamera_Splits_fcs = new FrustumCorners[4];
        for (int i = 0; i < 4; i++)
        {
            mainCamera_Splits_fcs[i].nearCorners = new Vector3[4];
            mainCamera_Splits_fcs[i].farCorners = new Vector3[4];

            lightCamera_Splits_fcs[i].nearCorners = new Vector3[4];
            lightCamera_Splits_fcs[i].farCorners = new Vector3[4];
        }
    }

    void CalcMainCameraSplitsFrustumCorners()
    {
        float near = Camera.main.nearClipPlane;
        float far = Camera.main.farClipPlane;

        //分4級:x + x*2 + x*2*2 + x*2*2*2 = 100% ==>15*x = 100% ==> x = 0.066666666≈0.067 = 6.7%
        //得到6.7%、13.3%、26.7%、53.3%等分的4級遠近裁剪坐標
        float[] nears = { near, far * 0.067f + near, far * 0.133f + far * 0.067f + near, far * 0.267f + far * 0.133f + far * 0.067f + near };
        float[] fars = { far * 0.067f + near, far * 0.133f + far * 0.067f + near, far * 0.267f + far * 0.133f + far * 0.067f + near, far };

        _LightSplitsNear = nears;
        _LightSplitsFar = fars;

        Shader.SetGlobalVector("_gLightSplitsNear", new Vector4(_LightSplitsNear[0], _LightSplitsNear[1], _LightSplitsNear[2], _LightSplitsNear[3]));
        Shader.SetGlobalVector("_gLightSplitsFar", new Vector4(_LightSplitsFar[0], _LightSplitsFar[1], _LightSplitsFar[2], _LightSplitsFar[3]));

        //計算主攝像機的4級視錐體
        for (int k = 0; k < 4; k++)
        {
            Camera.main.CalculateFrustumCorners(new Rect(0, 0, 1, 1), _LightSplitsNear[k], Camera.MonoOrStereoscopicEye.Mono, mainCamera_Splits_fcs[k].nearCorners);
            for (int i = 0; i < 4; i++)
            {
                mainCamera_Splits_fcs[k].nearCorners[i] = Camera.main.transform.TransformPoint(mainCamera_Splits_fcs[k].nearCorners[i]);
            }

            Camera.main.CalculateFrustumCorners(new Rect(0, 0, 1, 1), _LightSplitsFar[k], Camera.MonoOrStereoscopicEye.Mono, mainCamera_Splits_fcs[k].farCorners);
            for (int i = 0; i < 4; i++)
            {
                mainCamera_Splits_fcs[k].farCorners[i] = Camera.main.transform.TransformPoint(mainCamera_Splits_fcs[k].farCorners[i]);
            }
        }
    }

    //計算燈光相機包圍盒
    void CalcLightCameraSplitsFrustum()
    {
        if (dirLightCamera == null)
            return;

        for (int k = 0; k < 4; k++)
        {
            for (int i = 0; i < 4; i++)
            {
                lightCamera_Splits_fcs[k].nearCorners[i] = dirLightCameraSplits[k].transform.InverseTransformPoint(mainCamera_Splits_fcs[k].nearCorners[i]);
                lightCamera_Splits_fcs[k].farCorners[i] = dirLightCameraSplits[k].transform.InverseTransformPoint(mainCamera_Splits_fcs[k].farCorners[i]);
            }

            float[] xs = { lightCamera_Splits_fcs[k].nearCorners[0].x, lightCamera_Splits_fcs[k].nearCorners[1].x, lightCamera_Splits_fcs[k].nearCorners[2].x, lightCamera_Splits_fcs[k].nearCorners[3].x,
                       lightCamera_Splits_fcs[k].farCorners[0].x, lightCamera_Splits_fcs[k].farCorners[1].x, lightCamera_Splits_fcs[k].farCorners[2].x, lightCamera_Splits_fcs[k].farCorners[3].x };

            float[] ys = { lightCamera_Splits_fcs[k].nearCorners[0].y, lightCamera_Splits_fcs[k].nearCorners[1].y, lightCamera_Splits_fcs[k].nearCorners[2].y, lightCamera_Splits_fcs[k].nearCorners[3].y,
                       lightCamera_Splits_fcs[k].farCorners[0].y, lightCamera_Splits_fcs[k].farCorners[1].y, lightCamera_Splits_fcs[k].farCorners[2].y, lightCamera_Splits_fcs[k].farCorners[3].y };

            float[] zs = { lightCamera_Splits_fcs[k].nearCorners[0].z, lightCamera_Splits_fcs[k].nearCorners[1].z, lightCamera_Splits_fcs[k].nearCorners[2].z, lightCamera_Splits_fcs[k].nearCorners[3].z,
                       lightCamera_Splits_fcs[k].farCorners[0].z, lightCamera_Splits_fcs[k].farCorners[1].z, lightCamera_Splits_fcs[k].farCorners[2].z, lightCamera_Splits_fcs[k].farCorners[3].z };

            float minX = Mathf.Min(xs);
            float maxX = Mathf.Max(xs);

            float minY = Mathf.Min(ys);
            float maxY = Mathf.Max(ys);

            float minZ = Mathf.Min(zs);
            float maxZ = Mathf.Max(zs);

            lightCamera_Splits_fcs[k].nearCorners[0] = new Vector3(minX, minY, minZ);
            lightCamera_Splits_fcs[k].nearCorners[1] = new Vector3(maxX, minY, minZ);
            lightCamera_Splits_fcs[k].nearCorners[2] = new Vector3(maxX, maxY, minZ);
            lightCamera_Splits_fcs[k].nearCorners[3] = new Vector3(minX, maxY, minZ);

            lightCamera_Splits_fcs[k].farCorners[0] = new Vector3(minX, minY, maxZ);
            lightCamera_Splits_fcs[k].farCorners[1] = new Vector3(maxX, minY, maxZ);
            lightCamera_Splits_fcs[k].farCorners[2] = new Vector3(maxX, maxY, maxZ);
            lightCamera_Splits_fcs[k].farCorners[3] = new Vector3(minX, maxY, maxZ);

            Vector3 pos = lightCamera_Splits_fcs[k].nearCorners[0] + (lightCamera_Splits_fcs[k].nearCorners[2] - lightCamera_Splits_fcs[k].nearCorners[0]) * 0.5f;

            dirLightCameraSplits[k].transform.position = dirLightCameraSplits[k].transform.TransformPoint(pos);
            dirLightCameraSplits[k].transform.rotation = dirLight.transform.rotation;
        }
    }

    void ConstructLightCameraSplits(int k)
    {
        dirLightCamera.transform.position = dirLightCameraSplits[k].transform.position;
        dirLightCamera.transform.rotation = dirLightCameraSplits[k].transform.rotation;

        dirLightCamera.nearClipPlane = lightCamera_Splits_fcs[k].nearCorners[0].z;
        dirLightCamera.farClipPlane = lightCamera_Splits_fcs[k].farCorners[0].z;

        dirLightCamera.aspect = Vector3.Magnitude(lightCamera_Splits_fcs[k].nearCorners[0] - lightCamera_Splits_fcs[k].nearCorners[1]) / Vector3.Magnitude(lightCamera_Splits_fcs[k].nearCorners[1] - lightCamera_Splits_fcs[k].nearCorners[2]);
        dirLightCamera.orthographicSize = Vector3.Magnitude(lightCamera_Splits_fcs[k].nearCorners[1] - lightCamera_Splits_fcs[k].nearCorners[2]) * 0.5f;
    }
}
3.接受陰影
Shader "GLSL/CSMShadowMapping/Receiver" {

    SubShader {
        Tags { "RenderType"="Opaque"  }

        LOD 300 

        Pass {
            Name "FORWARD"
            Tags{ "LightMode" = "ForwardBase" }

            GLSLPROGRAM
            //gl_Vertex 頂點
            //gl_Position 裁剪空間坐標輸出到片元著色器
            //gl_FragColor 輸出顏色
            #include "UnityCG.glslinc"
            #include "lib/Custom.glslinc"
            #pragma fragmentoption ARB_precision_hint_fastest 

            uniform mat4 _gWorldToShadow;
            uniform sampler2D _gShadowMapTexture;
            uniform vec4 _gShadowMapTexture_TexelSize;
            /*{TextureName}_TexelSize - a float4 property contains texture size information :
            x contains 1.0 / width
            y contains 1.0 / height
            z contains width
            w contains height*/

            uniform vec4 _gLightSplitsNear;
            uniform vec4 _gLightSplitsFar;
            uniform mat4 _gWorld2Shadow[4];
            
            uniform sampler2D _gShadowMapTexture0;
            uniform sampler2D _gShadowMapTexture1;
            uniform sampler2D _gShadowMapTexture2;
            uniform sampler2D _gShadowMapTexture3;
            uniform float _gShadowStrength;

            struct v2f
            {
                vec2 uv;
                vec4 shadowCoord;
                float eyeZ;
                vec4 worldPos;
            };
            

            //3x3的PCF Soft Shadow
            float PCFSample(float depth, vec2 uv)
            {
                float shadow = 0.0;
                for (int x = -1; x <= 1; ++x)
                {
                    for (int y = -1; y <= 1; ++y)
                    {
                        vec4 col = texture(_gShadowMapTexture, uv + vec2(x, y) * _gShadowMapTexture_TexelSize.xy);
                        float sampleDepth = DecodeFloatRGBA(col);
                        shadow += sampleDepth < depth ? _gShadowStrength : 1.0;//接受物體片元的深度與深度圖的值比較,大于則表示被擋住燈光,顯示為陰影,否則顯示自己的顏色(這里顯示白色)
                    }
                }
                return shadow /= 9.0;
            }

            vec4 getCascadeWeights(float z)
            {
                vec4 zNear = vec4(z >= _gLightSplitsNear.x?1.0:0.0,z >= _gLightSplitsNear.y?1.0:0.0,z >= _gLightSplitsNear.z?1.0:0.0,z >= _gLightSplitsNear.w?1.0:0.0);
                vec4 zFar = vec4(z < _gLightSplitsFar.x?1.0:0.0,z < _gLightSplitsFar.y?1.0:0.0,z < _gLightSplitsFar.z?1.0:0.0,z < _gLightSplitsFar.w?1.0:0.0);
                vec4 weights = zNear * zFar;
                return weights;
            }

            vec4 getShadowCoord(vec4 wpos, vec4 cascadeWeights)
            {
                vec3 sc0 = (_gWorld2Shadow[0] * wpos).xyz;
                vec3 sc1 = (_gWorld2Shadow[1] * wpos).xyz;
                vec3 sc2 = (_gWorld2Shadow[2] * wpos).xyz;
                vec3 sc3 = (_gWorld2Shadow[3] * wpos).xyz;
                return vec4(sc0 * cascadeWeights[0] + sc1 * cascadeWeights[1] + sc2 * cascadeWeights[2] + sc3 * cascadeWeights[3], 1);
            }

            vec4 SampleShadowTexture(vec4 wPos, vec4 cascadeWeights)
            {
                vec4 shadowCoord0 = (_gWorld2Shadow[0] * wPos);
                vec4 shadowCoord1 = (_gWorld2Shadow[1] * wPos);
                vec4 shadowCoord2 = (_gWorld2Shadow[2] * wPos);
                vec4 shadowCoord3 = (_gWorld2Shadow[3] * wPos);

                shadowCoord0.xy /= shadowCoord0.w;
                shadowCoord1.xy /= shadowCoord1.w;
                shadowCoord2.xy /= shadowCoord2.w;
                shadowCoord3.xy /= shadowCoord3.w;

                shadowCoord0.xy = shadowCoord0.xy*0.5 + 0.5;
                shadowCoord1.xy = shadowCoord1.xy*0.5 + 0.5;
                shadowCoord2.xy = shadowCoord2.xy*0.5 + 0.5;
                shadowCoord3.xy = shadowCoord3.xy*0.5 + 0.5;

                vec4 sampleDepth0 = texture(_gShadowMapTexture0, shadowCoord0.xy);
                vec4 sampleDepth1 = texture(_gShadowMapTexture1, shadowCoord1.xy);
                vec4 sampleDepth2 = texture(_gShadowMapTexture2, shadowCoord2.xy);
                vec4 sampleDepth3 = texture(_gShadowMapTexture3, shadowCoord3.xy);

                float depth0 = shadowCoord0.z / shadowCoord0.w;
                float depth1 = shadowCoord1.z / shadowCoord1.w;
                float depth2 = shadowCoord2.z / shadowCoord2.w;
                float depth3 = shadowCoord3.z / shadowCoord3.w;

                #if defined (UNITY_REVERSED_Z)
                    depth0 = 1 - depth0;       //(1, 0)-->(0, 1)
                    depth1 = 1 - depth1;
                    depth2 = 1 - depth2;
                    depth3 = 1 - depth3;
                #else
                    depth0 = depth0*0.5 + 0.5; //(-1, 1)-->(0, 1)
                    depth1 = depth1*0.5 + 0.5;
                    depth2 = depth2*0.5 + 0.5;
                    depth3 = depth3*0.5 + 0.5;
                #endif

                float shadow0 = sampleDepth0.r < depth0 ? _gShadowStrength : 1.0;
                float shadow1 = sampleDepth1.r < depth1 ? _gShadowStrength : 1.0;
                float shadow2 = sampleDepth2.r < depth2 ? _gShadowStrength : 1.0;
                float shadow3 = sampleDepth3.r < depth3 ? _gShadowStrength : 1.0;

                //return col0;
                float shadow = shadow0 * cascadeWeights[0] + shadow1 * cascadeWeights[1] + shadow2 * cascadeWeights[2] + shadow3 * cascadeWeights[3];
                //return shadow * cascadeWeights;
                return vec4(shadow, shadow, shadow, shadow);
            }

            #ifdef VERTEX
            out v2f o;
            void main()
            {            
                gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
                o.uv = gl_MultiTexCoord0.xy;
                o.worldPos = unity_ObjectToWorld * gl_Vertex;
                o.shadowCoord = _gWorldToShadow * o.worldPos;
                o.eyeZ = gl_Position.w;
            }
            #endif
            #ifdef FRAGMENT
            in v2f o;
            void main()
            {
                vec4 weights = getCascadeWeights(o.eyeZ);
                // sample depth texture
                vec4 col = SampleShadowTexture(o.worldPos, weights);//310以后texture2D過期了,使用texture函數(shù)
                gl_FragColor = col;
            }
            #endif
            ENDGLSL
        }
    }
}

github:https://github.com/eangulee/GLSLInUnity.git

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

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