(十)unity自帶的着色器源碼剖析之——————AutoLight.cginc文件(包含一些陰影和光照相關計算的函數)

AutoLight.cginc文件包含了一系列用來進行照明和陰影計算的函數,本篇重點講述此文件中關於聚光燈和有向平行光的計算原理。

一、DIRECTIONAL宏的定義

#ifndef AUTOLIGHT_INCLUDED
#define AUTOLIGHT_INCLUDED

#include "HLSLSupport.cginc"
#include "UnityShadowLibrary.cginc"


// If none of the keywords are defined, assume directional?
//如果沒有使用點光燈和聚光燈光源,沒有定義一個有向平行光光源,沒有使用點cookie和有向平行光cookie,則默認定義一個有向平行光
#if !defined(POINT) && !defined(SPOT) && !defined(DIRECTIONAL) && !defined(POINT_COOKIE) && !defined(DIRECTIONAL_COOKIE)
    #define DIRECTIONAL
#endif

二、有向平行光產生的基於屏幕空間的陰影的相關函數

2.1 啓用UNITY_NO_SCREENSPACE_SHADOWS宏時TRANSFER_SHADOW宏的定義

// ---- Screen space direction light shadows helpers (any version)
//如果在屏幕空間處理陰影
#if defined (SHADOWS_SCREEN)
//當屏幕陰影空間層疊式陰影(screen space cascaded shadow map)不啓用時,宏UNITY_NO_SCREENSPACE_SHADOWS被啓用,引擎的C#層代碼有BuiltinShaderDefine.UNITY_NO_SCREENSPACE_SHADOWS對應控制設置
    #if defined(UNITY_NO_SCREENSPACE_SHADOWS)
//聲明一個名爲_ShadowMapTexture的陰影紋理貼圖
        UNITY_DECLARE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_WorldToShadow[0], mul( unity_ObjectToWorld, v.vertex ) );

TRANSFER_SHADOW宏定義了一個功能:首先把一個頂點從它的局部座標系轉換到世界座標系下,即mul(unity_ObjectToWorld,v.vertex)語句的功能;接着把該世界座標值變到陰影空間中,得到在陰影空間中的座標值,並賦值給a._ShadowCoord。

從代碼中可以看到,使用TRANSFER_SHADOW宏時,傳遞給它的參數a必然是一個結構體。並且含有一個名爲_ShadowCoord的成員變量,而且該語句包含一塊v.vertex的代碼,所以說這個宏必須搭配其他代碼語句使用,並且其他語句要包含一個變量名爲v的結構體,且這個結構體必須包含一個4D向量類型的、名爲vertex的成員變量。

_ShadowCoord變量再代碼段中有定義,是一個unityShadowCoord4類型的變量,而unityShadowCoord4又在UnityShadowLibrary.cginc文件中有定義,爲fixed4類型,通過宏SHADOW_COORDS可以聲明_ShadowCoord變量綁定一個TEXTCOORD語義。

2.2 啓用UNITY_NO_SCREENSPACE_SHADOWS宏時的unitySampleShadow函數

	//陰影的強度
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            #if defined(SHADOWS_NATIVE)
                fixed shadow = UNITY_SAMPLE_SHADOW(_ShadowMapTexture, shadowCoord.xyz);
                shadow = _LightShadowData.r + shadow * (1-_LightShadowData.r);
                return shadow;
            #else
			//在tegra處理器上,如果直接把_LightShadowData.x傳給Cg庫函數max,會因爲參數類型精度的問題而導致混亂和不精確,所以在此要先把_LightShadowData.x
			//複製給一個unityShadowCoord類型變量lightShadowData.x複製給一個unityShadowCoord類型變量lightShadowDataX,然後傳遞給max函數
                unityShadowCoord dist = SAMPLE_DEPTH_TEXTURE(_ShadowMapTexture, shadowCoord.xy);
                // tegra is confused if we use _LightShadowData.x directly
                // with "ambiguous overloaded function reference max(mediump float, float)"
                unityShadowCoord lightShadowDataX = _LightShadowData.x;
				//比較當前片元的深度值和對應的貼圖紋素中表示的深度值
                unityShadowCoord threshold = shadowCoord.z;
				//如果深度貼圖中的深度值大於當前片元的深度值,表示當前片元在陰影之外,dist>threshold的值爲1,這時max函數返回1
				//如果小於,表示當前片元在陰影之內,dist>threshold的值爲0,這時max函數返回的是lightShadowDataX
                return max(dist > threshold, lightShadowDataX);
            #endif
        }

2.3 未啓用UNITY_NO_SCREENSPACE_SHADOWS宏時TRANSFER_SHADOW宏的定義

當關閉UNITY_NO_SCREENSPACE_SHADOWS宏時,即用基於屏幕空間的陰影時,陰影紋理貼圖和陰影座標的聲明與定義方式如下:
 

//當啓用屏幕空間層疊式陰影
    #else // UNITY_NO_SCREENSPACE_SHADOWS
        UNITY_DECLARE_SCREENSPACE_SHADOWMAP(_ShadowMapTexture);
        #define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
        inline fixed unitySampleShadow (unityShadowCoord4 shadowCoord)
        {
            fixed shadow = UNITY_SAMPLE_SCREEN_SHADOW(_ShadowMapTexture, shadowCoord);
            return shadow;
        }

    #endif

    #define SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
    #define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
#endif

2.4 UNITY_SAMPLE_SCREENSPACE_SHADOWMAP宏、UNITY_DECLARE_SCREENSPACE_SHADOWMAP宏的定義

//如果啓用了和立體渲染相關的宏
#if defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
 #define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) UNITY_SAMPLE_TEX2DARRAY( tex, float3((uv).x/(uv).w, (uv).y/(uv).w, (float)unity_StereoEyeIndex) ).r

    #define UNITY_DECLARE_SCREENSPACE_TEXTURE UNITY_DECLARE_TEX2DARRAY
    #define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) UNITY_SAMPLE_TEX2DARRAY(tex, float3((uv).xy, (float)unity_StereoEyeIndex))
//如果沒有啓用立體渲染,那麼基於屏幕空間的screen space的陰影貼圖實質上就是一個普通的sampler類型,要對該紋理採樣,調用tex2DProj即可
#else

    #define UNITY_DECLARE_SCREENSPACE_SHADOWMAP(tex) sampler2D tex
    #define UNITY_SAMPLE_SCREEN_SHADOW(tex, uv) tex2Dproj( tex, UNITY_PROJ_COORD(uv) ).r
    #define UNITY_DECLARE_SCREENSPACE_TEXTURE(tex) sampler2D tex;
    #define UNITY_SAMPLE_SCREENSPACE_TEXTURE(tex, uv) tex2D(tex, uv)
#endif

三、Unity3D 5.6版本後的陰影和光照計算工具函數

3.1 UnityComputeForwardShadows函數

//此版本的函數根據傳遞進來的某片元在世界空間下的座標、使用的光照貼圖採樣座標,以及它所在的屏幕座標,計算出該片元的陰影值爲多少
half UnityComputeForwardShadows(float2 lightmapUV, float3 worldPos, float4 screenPos)
{
    //fade value 陰影淡化值
	//_WorldSpaceCameraPos是當前攝像機在世界空間中的位置值,用片元位置點和當前攝像機位置點的連線向量與當前攝像機的朝前方向向量做點積,得到的就是連線向量的實際長度值
    float zDist = dot(_WorldSpaceCameraPos - worldPos, UNITY_MATRIX_V[2].xyz);
	
    float fadeDist = UnityComputeShadowFadeDistance(worldPos, zDist);
	//根據淡化距離,求得實時轉烘焙陰影的陰影淡化值

    half  realtimeToBakedShadowFade = UnityComputeShadowFade(fadeDist);

    //baked occlusion if any
	//當使用陰影蒙版時,UnitySampleBakedOcclusion函數用來返回烘焙陰影的衰減值
    half shadowMaskAttenuation = UnitySampleBakedOcclusion(lightmapUV, worldPos);

    half realtimeShadowAttenuation = 1.0f;
    //directional realtime shadow
	//計算主有向平行光產生的實時陰影
    #if defined (SHADOWS_SCREEN)
        #if defined(UNITY_NO_SCREENSPACE_SHADOWS) && !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
	//如果不是基於屏幕空間生成陰影,把片元座標從世界座標系下變換到光源空間下後進行採樣
            realtimeShadowAttenuation = unitySampleShadow(mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1)));
        #else
	//否則把片元從世界座標系下變換到屏幕空間中採樣,下面這段用屏幕座標進行採樣的代碼,只有LIGHTMAP_ON宏未被啓用時會執行到,這時不能使用光照貼圖
            //Only reached when LIGHTMAP_ON is NOT defined (and thus we use interpolator for screenPos rather than lightmap UVs). See HANDLE_SHADOWS_BLENDING_IN_GI below.
            realtimeShadowAttenuation = unitySampleShadow(screenPos);
        #endif
    #endif

    #if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
    //avoid expensive shadows fetches in the distance where coherency will be good
			//使用UNITY_BRANCH分支,明確告知着色器編譯器生成真正的動態分支功能,編碼執行性能消耗較大的陰影fetch操作
    UNITY_BRANCH
    if (realtimeToBakedShadowFade < (1.0f - 1e-2f))
    {
    #endif

        //spot realtime shadow
		//計算聚光燈光源產生的實時陰影,從實時陰影貼圖中取得衰減值
        #if (defined (SHADOWS_DEPTH) && defined (SPOT))
            #if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
                unityShadowCoord4 spotShadowCoord = mul(unity_WorldToShadow[0], unityShadowCoord4(worldPos, 1));
            #else
                unityShadowCoord4 spotShadowCoord = screenPos;
            #endif
            realtimeShadowAttenuation = UnitySampleShadowmap(spotShadowCoord);
        #endif

        //point realtime shadow
			//計算點光源產生的實時陰影,從實時陰影貼圖中取得衰減值
        #if defined (SHADOWS_CUBE)
            realtimeShadowAttenuation = UnitySampleShadowmap(worldPos - _LightPositionRange.xyz);
        #endif

    #if defined(UNITY_FAST_COHERENT_DYNAMIC_BRANCHING) && defined(SHADOWS_SOFT) && !defined(LIGHTMAP_SHADOW_MIXING)
    }
    #endif
	//最後混合實時的、陰影蒙版的、以及實時轉烘焙的陰影值
    return UnityMixRealtimeAndBakedShadows(realtimeShadowAttenuation, shadowMaskAttenuation, realtimeToBakedShadowFade);
}

3.2不同編譯條件下UNITY_SHADOW_COORDS、UNITY_TRANSFER_SHADOW和UNITY_SHADOW_ATTENUATION宏的定義

#if defined(SHADER_API_D3D11) || defined(SHADER_API_D3D12) || defined(SHADER_API_XBOXONE) || defined(SHADER_API_PSSL)
#   define UNITY_SHADOW_W(_w) _w
#else
#   define UNITY_SHADOW_W(_w) (1.0/_w)
#endif

#if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
#    define UNITY_READ_SHADOW_COORDS(input) 0
#else
#    define UNITY_READ_SHADOW_COORDS(input) READ_SHADOW_COORDS(input)
#endif
//如果定義了在全局照明下進行陰影混合的宏,使用這三個宏對應定義一個需要帶座標的版本
#if defined(HANDLE_SHADOWS_BLENDING_IN_GI) // handles shadows in the depths of the GI function for performance reasons
#   define UNITY_SHADOW_COORDS(idx1) SHADOW_COORDS(idx1)
#   define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
#   define UNITY_SHADOW_ATTENUATION(a, worldPos) SHADOW_ATTENUATION(a)
//如果定義了屏幕空間中處理陰影,且不使用貼圖,沒有使用層疊式屏幕空間陰影貼圖
#elif defined(SHADOWS_SCREEN) && !defined(LIGHTMAP_ON) && !defined(UNITY_NO_SCREENSPACE_SHADOWS) // no lightmap uv thus store screenPos instead
    // can happen if we have two directional lights. main light gets handled in GI code, but 2nd dir light can have shadow screen and mask.
    // - Disabled on ES2 because WebGL 1.0 seems to have junk in .w (even though it shouldn't)

//當有兩個有向平行光時,主有向平行光在全局照明的相關代碼中進行處理,第二個有向平行光在屏幕空間中進行陰影計算,如果使用了陰影蒙版,且不是
//在D3D9和OpenGLES平臺下,那就從烘焙出來的光照貼圖中取得陰影數據
#   if defined(SHADOWS_SHADOWMASK) && !defined(SHADER_API_GLES)
#       define UNITY_SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
//因爲陰影是在屏幕空間中進行處理,所以陰影座標的x、y分量就是光照貼圖的u、v貼圖座標換算而來的。對於用coord乘以unity_LightmapST.xy後再加
//上unity_LightmapST.zw的這樣一個計算方式,當LIGHTMAP_ON爲false時才能進入代碼此處
#       define UNITY_TRANSFER_SHADOW(a, coord) {a._ShadowCoord.xy = coord * unity_LightmapST.xy + unity_LightmapST.zw; a._ShadowCoord.zw = ComputeScreenPos(a.pos).xy;}
//計算陰影衰減,轉調用了UnityComputeForwardShadows函數
#       define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, worldPos, float4(a._ShadowCoord.zw, 0.0, UNITY_SHADOW_W(a.pos.w)));
#   else
#       define UNITY_SHADOW_COORDS(idx1) SHADOW_COORDS(idx1)
//如果不從主光照貼圖unity_LightmapST中計算陰影座標,就用TRANSFER_SHADOW計算
#       define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
#       define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, a._ShadowCoord)
#   endif
//其他條件下
#else
#   define UNITY_SHADOW_COORDS(idx1) unityShadowCoord4 _ShadowCoord : TEXCOORD##idx1;
//如果使用陰影蒙版,那麼根據光照貼圖紋理uv座標求出陰影座標
#   if defined(SHADOWS_SHADOWMASK)
#       define UNITY_TRANSFER_SHADOW(a, coord) a._ShadowCoord.xy = coord.xy * unity_LightmapST.xy + unity_LightmapST.zw;
//如果使用立方體陰影,或者光照探針代理體等有體積空間的陰影實現,需要把在世界空間中的座標也傳遞進去
#       if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE) || UNITY_LIGHT_PROBE_PROXY_VOLUME)
#           define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, worldPos, UNITY_READ_SHADOW_COORDS(a))
#       else
//否則給UnityComputeForwardShadows函數傳遞的worldPos參數爲0
#           define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(a._ShadowCoord.xy, 0, 0)
#       endif
#   else   //如果不使用陰影蒙版,就不用實現transfer shadow的操作
#       if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
#           define UNITY_TRANSFER_SHADOW(a, coord)
#       else
#           define UNITY_TRANSFER_SHADOW(a, coord) TRANSFER_SHADOW(a)
#       endif
#       if (defined(SHADOWS_DEPTH) || defined(SHADOWS_SCREEN) || defined(SHADOWS_CUBE))
#           define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, UNITY_READ_SHADOW_COORDS(a))
#       else
#           if UNITY_LIGHT_PROBE_PROXY_VOLUME
#               define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, worldPos, UNITY_READ_SHADOW_COORDS(a))
#           else
#               define UNITY_SHADOW_ATTENUATION(a, worldPos) UnityComputeForwardShadows(0, 0, 0)
#           endif
#       endif
#   endif
#endif

下面這張圖把上面的這段代碼圖表化,更加清楚顯示了在不同多樣體關鍵字開啓條件下各宏的實際定義:

3.3 計算點光源的光亮度衰減的宏

 

#ifdef POINT
//存儲點光源發出的光線在空間中各個位置值的衰減值紋理
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
//首先把世界座標系的座標值worldPos變換到光源空間中,這裏得到的lightCoord是一個採樣值,在對後面的光源衰減紋理和採樣時使用
        unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz; \
			//求出這一點的陰影衰減值
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
			//從光源衰減信息紋理中取出此處的衰減值,然後在UNITY_ATTEN_CHANNEL通道中輸出,再和對應的陰影衰減值相乘,得到最後結果
        fixed destName = tex2D(_LightTexture0, dot(lightCoord, lightCoord).rr).r * shadow;
#endif

_LightTexture0是一張包含光源衰減信息的衰減紋理,lightCoord做一個和自身的點積操作,實際上就是計算出光源空間中位置點lightCoord到光源位置點的距離的平方;然後利用這個距離值重組(swizzle)出一個二維向量,用做衰減紋理的索引座標。

從紋理中取得衰減值後,再乘以該處的陰影值,就可以得到光線在這點worldPos處的光亮度衰減值。

3.4 計算聚光燈光源的光亮度衰減的宏

#ifdef SPOT
sampler2D_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
sampler2D_float _LightTextureB0;
inline fixed UnitySpotCookie(unityShadowCoord4 LightCoord)
{
    return tex2D(_LightTexture0, LightCoord.xy / LightCoord.w + 0.5).w;
}
inline fixed UnitySpotAttenuate(unityShadowCoord3 LightCoord)
{
    return tex2D(_LightTextureB0, dot(LightCoord, LightCoord).xx).r;
}
#if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
#define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord4 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1))
#else
#define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord4 lightCoord = input._LightCoord
#endif
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
        DECLARE_LIGHT_COORD(input, worldPos); \
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
        fixed destName = (lightCoord.z > 0) * UnitySpotCookie(lightCoord) * UnitySpotAttenuate(lightCoord.xyz) * shadow;
#endif

在點光源下,存儲着光亮度隨着距離而衰減的習性的紋理由代碼中的_LightTexture0負責讀取;而在聚光燈下,這張紋理由_LightTextureB0讀取。其採樣座標的計算方法和點光源有相似的地方。在UnitySpotAttenuate函數中,求得位置點中光源位置點的距離的平方,得到衰減值。通常聚光燈用“光亮度隨着距離而衰減的信息”的紋理和點光源是一樣的。

聚光燈光源照射範圍內某一點的光亮度,它的衰減值除了和它與光源點的距離有關外,還和它與光源點的連線和光源照射方向的夾角有關。同樣的到光源點的距離,夾角越大的位置點其衰減值就越大。

3.5 計算有向平行光源的光亮度衰減的宏

#ifdef DIRECTIONAL
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) fixed destName = UNITY_SHADOW_ATTENUATION(input, worldPos);
#endif

有向平行光的光亮度不會隨着光的傳播距離的變化而發生衰減,因此此處所示的UNITY_LIGHT_ATTENUATION宏只需要內部轉調UNITY_SHADOW_ATTENUATION宏,計算陰影產生的衰減結果即可。

3.6 計算帶cookie的點光源的光亮度衰減的宏

在Light組件中,當選擇Type屬性項爲Point時,可以向Cookie屬性項指定一個立方體紋理。Light組件所表徵的點光源使用此立方體紋理產生cookie投影效果。此時點光源的光亮度衰減值除了取決於光源自身發出的光線之外,還與產生cookie效果的立方體紋理中的紋素值有關。

#ifdef POINT_COOKIE
//產生cookie效果的立方體紋理採樣器,在Light組件中的Cookie屬性項中指定
samplerCUBE_float _LightTexture0;
unityShadowCoord4x4 unity_WorldToLight;
sampler2D_float _LightTextureB0;   //點光源光線亮度衰減值紋理圖
#   if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
//把座標轉換從世界座標系下轉換到光源座標系下
#       define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord3 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xyz
#   else
#       define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord3 lightCoord = input._LightCoord
#   endif
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
        DECLARE_LIGHT_COORD(input, worldPos); \
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
//計算光源發出光線的衰減方式,和點光源一樣
        fixed destName = tex2D(_LightTextureB0, dot(lightCoord, lightCoord).rr).r * texCUBE(_LightTexture0, lightCoord).w * shadow;
#endif

3.7 計算帶cookie的有向平行光源的光亮度衰減的宏

#ifdef DIRECTIONAL_COOKIE
sampler2D_float _LightTexture0;   //產生cookie效果的紋理採樣器
unityShadowCoord4x4 unity_WorldToLight;
#   if !defined(UNITY_HALF_PRECISION_FRAGMENT_SHADER_REGISTERS)
#       define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord2 lightCoord = mul(unity_WorldToLight, unityShadowCoord4(worldPos, 1)).xy
#   else
#       define DECLARE_LIGHT_COORD(input, worldPos) unityShadowCoord2 lightCoord = input._LightCoord
#   endif
#   define UNITY_LIGHT_ATTENUATION(destName, input, worldPos) \
        DECLARE_LIGHT_COORD(input, worldPos); \
        fixed shadow = UNITY_SHADOW_ATTENUATION(input, worldPos); \
        fixed destName = tex2D(_LightTexture0, lightCoord).w * shadow;
#endif

和點光源一樣,有向平行光源也可以指定cookie紋理以產生cookie陰影效果,和不帶cookie的類似,帶cookie的有向平行光源的光亮度衰減,其光源本身是不隨距離的變化而產生亮度衰減的,對衰減值有影響的是產生cookie的紋理。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章