• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            永遠也不完美的程序

            不斷學習,不斷實踐,不斷的重構……

            常用鏈接

            統計

            積分與排名

            好友鏈接

            最新評論

            Cascaded shadow map(轉)

            轉自:http://class.gd/content/shadow-map%E9%98%B4%E5%BD%B1%E8%B4%B4%E5%9B%BE%E6%8A%80%E6%9C%AF%E4%B9%8B%E6%8E%A2%E2%85%A2

            本文來源:http://www.zwqxin.com/archives/opengl

                    上篇里的最后最后,提及了幾種比較有名的Shadow Map的延展技術,Cascaded Shadow Maps是其中比較近期才出現的,而且它引進了Cascade(級聯,層)這個概念,與另一個頗為我們中國人驕傲的名詞PSSM(Parallel- split Shadow Maps)中的Parallel-split指的是同一個概念。事實上兩者的原理是基本一樣的。

                    它先在我們的視錐上動手腳,用幾個與近遠平面平行的截面把視錐分成幾份(Parallel-split);然后針對每一份,通過修改光源投影矩陣,使之后 生成的Shadow Map中只有該份“Splited視錐”里的物體;這樣,在pass1階段就生成了幾張針對不同“Splited視錐”的Shadow Maps,在渲染階段,依據像素深度就可以判斷該位置應用哪張Shadow Map了。

                    這樣做的好處在上篇已經講過了。在距離眼睛近的地方,應用的是分辨率高的陰影圖,距離眼睛遠的地方則是低分辨率。這樣是符合視覺特點的,而且沒有什么浪費的地方。

                    如圖,假設光從視錐正上方射下來(其他方向同理),按CSM的意思,應該把光源視覺下的投影面放在圖示位置(四條短的水平的線)。這里我把視錐分割成四 份,因此需要對應的四張ShadowMap,與人看東西一樣,視像面越靠近陰影(假設位于被投影面,圖中長水平線),看到的陰影越清晰。反映在生成陰影圖 階段,表現為具體caster(被光源直接照射的投射物表面)在光源投影面上占據的范圍大。假設陰影圖尺寸是固定的(譬如1024*1024),在第一個 “Splited視錐”和第四個“Splited視錐”里的投射陰影的物體[投射物]大小也相同(其陰影在實際世界里占地面積必然也相同),則其陰影在陰 影圖里占的像素數會有很大差別(譬如前者占500,000個,后者可能才占5000個),這就是分辨率的差異。最后把ShdowMap帖在場景里(假設在 世界空間下該種投射物的陰影應該占100,000個像素),前者就會比后者效果好很多。(一個是需要進行OverSampling,另一個就得進行 UnderSampling。)所以越靠近眼睛的、越小的Splited視錐里的陰影越高“畫質”,反之則越粗糙(但比起傳統Shadow Map技術也許效果還好一點)——而我們正希望要眼前的事物清晰,遠處的事物模糊甚至不表現出影子也可以——CSM(或者說,PSSM)做到了。

                    重新回頭看看技術實現過程。這里有兩個主要的技術點,一是“怎么分割視錐”,二是“怎么設置每個小視錐的光源投影矩陣”。

                    1. Cascade(Split)的準則

                    從上圖和上分析可以看出,“Splited視錐”沿視線的長度(Zfar - Znear)應該越分越大比較合理,指數增長符合這個規律,但指數增長一般太夸張了,所以配合一個線性增長比較好。在PSSM里,這兩種分法叫 logarithmic split scheme和the uniform split scheme,前者的表達式是經過科學的推導的,這部分也是CSM/PSSM最數學的部分,在GPU GEMS3里有詳細的推導,或者你看PSSM推廣人Fan Zhang [HKUST]那篇"Hardware-Accelerated Parallel-Split Shadow Maps." (IF YOU CAN FIND IT)也該有。它從Shadow-Map Aliasing(dp/ds,單位陰影圖像素單位對應的屏幕像素)的推導開始,找出能滿足使perspective aliasing(由投影縮減效應形成)在各個視錐里均勻分配的分割式。

                    后者只是一個線性式,但它的調和作用避免了“Splited視錐”的過小與過大,通過一個mix因子混合兩式子(我在應用中默認分配logarithmic split scheme的因子是0.75,余者0.25):

            01 // <a href="http://www.ZwqXin.com" title="www.ZwqXin.com">www.ZwqXin.com</a>  Cascaded Shadow Maps
            02 void CCascadingSM::ComputeSplits(float strength, float Dis_Near, float Dis_Far)
            03 {
            04    float distance_scale = Dis_Far / Dis_Near;
            05    
            06    splitfrust[0].ResightNear(Dis_Near); //開始分割
            07    
            08    float partisionFactor = 0.0;
            09    float lerpValue1 = 0.0, lerpValue2 = 0.0;
            10    float SplitsZ = 0.0;
            11    
            12     for(int i = 1; i < NumofSplits; ++i)
            13     {
            14         partisionFactor = i / (float)NumofSplits;
            15    
            16         lerpValue1 = Dis_Near + partisionFactor * (Dis_Far - Dis_Near);
            17    
            18         lerpValue2 = Dis_Near * powf(distance_scale, partisionFactor);
            19    
            20         // 分割面的Z值. 1.005f防止前一個子視錐的遠裁切面與后一個子視錐的近裁切面沖突
            21         SplitsZ =  (1-strength) * lerpValue1  + strength * lerpValue2; 
            22                      
            23         splitfrust[i].ResightNear(SplitsZ * 1.002f);
            24         splitfrust[i-1].ResightFar(SplitsZ);
            25     }
            26    
            27     splitfrust[NumofSplits-1].ResightFar(Dis_Far);//結束分割
            28 }

                    2. Crop  It !

                    針對每個光源投影矩陣進行的調整,在CSM/PSSM里稱為Crop(這么有詩情畫意噶?)。這個過程其實很好理解的,我們在照相的時候,一開始要在 CCD液晶屏的畫面上把焦點確定吧——Cascaded Shadow Maps技術中的光源就是照相者,光源的視像平面就是屏幕,我們是對每個“Splited視錐”都照一張相,因為照的是casters,所以可以說是照人 物相片——把casters所在的“Splited視錐”(對應人物背景)在光源投影空間的中心挪移到視像平面的中心,然后進行光學變焦,使人物背景盡量 充滿屏幕,從而突出人物——casters,噢,不,應說是shadows。

                    恩,這是個具有平移和縮放的線性變換——CROP MATRIX,合適地構造它,然后乘在光源投影矩陣前面(形成新的投影矩陣),就能完成匹配投影矩陣匹配“Splited視錐”的任務。假如目前處理第i 個分割視錐,生成CropMatrix[i],那么對場景坐標系的變換就是:(CropMatrix[i] * LightProjectMatrix) * LightViewMatrix * ModelMatrix * pos。也可認為(CropMatrix[i] * LightProjectMatrix)是二次投影,因為Crop Matrix實質也是個投影矩陣,而且是個名副其實的Otho正交投影矩陣。

            01 // <a href="http://www.ZwqXin.com" title="www.ZwqXin.com">www.ZwqXin.com</a>  Cascaded Shadow Maps 
            02 void CCascadingSM::ApplyCropProjectMatrix(CFrustum &frust) 
            03 {  
            04     CVector3 maxFrustumCoord, minFrustumCoord; 
            05     
            06     CMatrix16 CurrentMatrix;//當前矩陣 
            07     CMatrix16 CropMatrix;//協調光源視野與視錐的Crop Matrix                      
            08     
            09     //光源視圖矩陣 
            10     glGetFloatv(GL_MODELVIEW_MATRIX, CurrentMatrix.mt); 
            11     
            12       //生成視錐的AABB特征向量,視錐先經CurrentMatrix變換到光源視圖空間 
            13     GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMatrix); 
            14     
            15      //計算給Crop Matrix的調整參數 
            16     float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x); 
            17     float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y); 
            18     float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX; 
            19     float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY; 
            20     
            21     CropMatrix = CMatrix16(scaleX,    0.0f,  0.0f, 0.0f, 
            22                              0.0f,  scaleY,  0.0f, 0.0f, 
            23                              0.0f,    0.0f,  1.0f,  0.0f, 
            24                           offsetX, offsetY,  0.0f,  1.0f ); 
            25     
            26    //CropProjectMatrix(光源投影矩陣 = CropMatrix*ProjectZMatrix) 
            27      glLoadIdentity(); 
            28      glLoadMatrixf(CropMatrix.mt); 
            29      //以max_Z和min_Z作為遠近裁切面的正投影矩陣  
            30      glOrtho(-1.0, 1.0, -1.0, 1.0, -maxFrustumCoord.z, -minFrustumCoord.z ); 
            31     
            32 }

                    CropMatrix簡直就跟glOrtho生成的矩陣一模一樣,功用也一樣。只不過這里我沒有對Z坐標進行變換,因為把它交給生成光源投影矩陣的 glOrtho了(反而它只變換Z坐標)。前面不是說把坐標都變換到光源投影CLip空間后再提取AABB嗎,為什么就到光源視圖空間就比較了?因為這里 是平行光的投影,所以用的是正交投影glOrtho,在glOrtho中沒有對X,Y坐標進行變換(看看它的spec就知道了,-1與1為參數是不改變 X,Y數值的),所以兩個空間下的X,Y坐標是一致的,而CropMatrix正是只變換X,Y坐標,所以實在沒必要多此一舉。

                    但有兩種情況是“需要多此一舉”的。一是光源為點光源且需要透視投影;二是在光源與視錐之間還有其他caster。對第二種情況尤其值得注意。看回我在文 章最上面放的自畫示意圖,有個打了X的地方,那里假設有只bird,那么它會否對地面產生陰影呢?——按照CSM基礎理論,不會!因為 CropMATRIX修改后的光源投影平面已經越過它了,已經看不見它了——我們只能看見視錐里(更準確說是視錐的AABB包圍盒里)的物體所留下的陰 影!解決法是把該物件bondingbox在光源視圖空間下的最大Z坐標作為上述算法最后的minFrustumCoord.z,使光源投影平面恰在該位 置而不再下降。這樣做多了些麻煩,而且該“Splited視錐”對應的Shadow Map的分辨率會降低,物體離視錐越遠,分辨率下降越嚴重。所以,如非必要投射那樣的物體(或者部分穿出視錐之外的物體)的陰影,不必這樣做:

                    先計算普適意義下的光源投影矩陣和視圖矩陣(類似傳統SM那樣),用它們的積Light-ProjectView把各個小視錐變換到CLIP投影空間,用 同樣方法得到該空間下的包圍盒(特征向量maxFrustumCoord, minFrustumCoord),這里繼續計算的Crop矩陣就需要用到Z值了,因為我們要修改其中的minFrustumCoord.z。讓它等于 -1——OPENGL在CLIP投影空間的最小坐標值。沒錯,即使該物件在光源正體位置之上,也把它計算入要投影的物件集(casters)里(況且平行 光源本來該是無限遠而不是在那個虛擬位置上的)。最后依然是:CropMatrix[i] *( LightProjectMatrix * LightViewMatrix) * ModelMatrix * pos。

            01 // <a href="http://www.ZwqXin.com" title="www.ZwqXin.com">www.ZwqXin.com</a>  Cascaded Shadow Maps  
            02     CVector3 maxFrustumCoord, minFrustumCoord; 
            03   //.....
            04     GetFrustumAABBCoords(frust, maxFrustumCoord, minFrustumCoord, &CurrentMV);
            05    
            06     minFrustumCoord.z = -1.0f;
            07    
            08      //計算給Crop Matrix的調整參數
            09     float scaleX = 2.0f/(maxFrustumCoord.x - minFrustumCoord.x);
            10     float scaleY = 2.0f/(maxFrustumCoord.y - minFrustumCoord.y);
            11     float scaleZ  = 2.0f / (maxFrustumCoord.z - minFrustumCoord.z);
            12     float offsetX = -0.5f*(maxFrustumCoord.x + minFrustumCoord.x) * scaleX;
            13     float offsetY = -0.5f*(maxFrustumCoord.y + minFrustumCoord.y) * scaleY;
            14     float offsetZ = -0.5f*(maxFrustumCoord.z + minFrustumCoord.z) * scaleZ;
            15    
            16     
            17     CropMatrix = CMatrix16(scaleX,    0.0f,  0.0f, 0.0f, 
            18                              0.0f,  scaleY,  0.0f, 0.0f, 
            19                              0.0f,    0.0f,  scaleZ,  0.0f, 
            20                           offsetX, offsetY,  0.0f,  1.0f ); 
            21 //

                    3. Cast 陰影

                    通過上面矩陣配合(0,1)映射矩陣之類的生成shadow maps后,這就來到第二PASS了,它與傳統Shadow Map(Shadow Map陰影貼圖技術之探Ⅰ)一樣,只是根據像素深度決定用哪張而已。注意,把視錐分割的是近/遠平面,其值是距視點的距離,定義于視圖空間——把它變換到 眼睛的屏幕CLIP空間,就能在shader里“分割”像素深度,把像素都分到SplitNum個區域里(應用中我取了4個)。好了,接下來你知道怎么用 if-else來Cast 陰影圖了吧。

            01 // <a href="http://www.ZwqXin.com" title="www.ZwqXin.com">www.ZwqXin.com</a>  Cascaded Shadow Maps
            02 //fragment shader中獲取當前像素陰影狀態:
            03 //shadow_color [陰影factor], 還是1.0[表明不貢獻陰影之factor]
            04    
            05 const float shadow_color = 0.3;
            06 const float depth_error = 0.005;
            07 //上面提到的那幾個分割值,藏在xyz通道了
            08 uniform vec3 frustum_far; 
            09 uniform sampler2DArray shadowmap;
            10    
            11 vec4 shadeFact()
            12 {
            13    int index = 3;
            14      
            15    //決定cascade,應用的shadowMap index
            16    //gl_FragCoord(當前pixel的x,y窗口坐標,z分量為深度)
            17    
            18    if(gl_FragCoord.z < frustum_far.x) 
            19    {
            20      index = 0;
            21    }
            22    else if(gl_FragCoord.z < frustum_far.y)
            23    {
            24      index = 1;
            25    }
            26    else if(gl_FragCoord.z < frustum_far.z)
            27    {
            28      index = 2;
            29    }
            30      
            31      //轉換像素位置參量pos, 到光源視覺(Croped)-紋理空間
            32      vec4 shadowTexcoord = gl_TextureMatrix[index] * pos;
            33    
            34      //對紋理投影,變換到紋理空間的場景坐標總作為TEXCOORD,這時就得自行為之“透視相除”了
            35      //小聲:對正交投影其實是不必的。。。
            36      if(shadowTexcoord.w != 1.0)
            37      {
            38         shadowTexcoord = shadowTexcoord / shadowTexcoord.w;
            39      }
            40    
            41      //映射到(0~1)以進行紋理檢索
            42      shadowTexcoord = 0.5 * shadowTexcoord + 0.5; 
            43        
            44      //本像素的位置在當前空間(光源視覺(Croped)-紋理空間)的實際深度
            45      float realDepth = shadowTexcoord.z;
            46    
            47      //Texture Array 中以z分量選擇紋理Layer(Shadow Map No.i)
            48      shadowTexcoord.z = float(index); 
            49        
            50     //檢索出Shadow Map中對應位置(x,y)的深度值
            51     float depth =  texture2DArray(shadowmap, shadowTexcoord.xyz).x;
            52    
            53     //當 depth >= realDepth, 該位置所屬caster 或 no-shadow領域, 輸出陰影分量1.0[無陰影]
            54     //當 depth <  realDepth, 該位置所屬shadowed領域           , 輸出陰影分量0.0[有陰影]
            55     float diff = depth - realDepth;
            56    
            57     //為了精度問題,如果差值diff是個很小很小的負量,把該量設定為1.0
            58     //當diff > -0.005(根據應用調節), 認為depth - realDepth >= 0.0[無陰影]
            59     diff = diff / depth_error + 1.0;
            60    
            61     return vec4(diff < 0.0 ? shadow_color : 1.0) ;
            62 }

                    最后是放出演示DEMO了吧

                    在該日志將展示DEMO并淺談一下CSM一些小細節的地方,包括caster-receiver-splitedFrustum組合生成的SCREEN DEPENDENT的crop矩陣。最后是這段時間個人學習Shadow技法的小小總結。



            posted on 2010-11-24 10:20 狂爛球 閱讀(4954) 評論(0)  編輯 收藏 引用 所屬分類: 圖形編程

            久久人爽人人爽人人片AV| 国产精品gz久久久| 午夜视频久久久久一区| 久久综合综合久久狠狠狠97色88| 久久99久久99精品免视看动漫| 一本一本久久a久久精品综合麻豆| 热久久国产精品| 久久久99精品成人片中文字幕| 国产精品99久久久久久宅男| 久久精品国产只有精品66| 国产午夜精品久久久久九九| 精品久久久久久无码免费| 久久久精品人妻无码专区不卡| 久久国产影院| 性做久久久久久久久久久| 国内精品伊人久久久影院| 精品综合久久久久久98| 国内精品久久人妻互换| 久久精品国产免费一区| 丁香久久婷婷国产午夜视频| 久久综合九色综合久99| 久久久久久久女国产乱让韩| 国产成人无码久久久精品一| 国产精品成人99久久久久 | 久久久精品午夜免费不卡| 色综合久久久久| 久久综合视频网| AV狠狠色丁香婷婷综合久久| 久久免费观看视频| 狼狼综合久久久久综合网| 99久久成人18免费网站| 亚洲欧美日韩精品久久亚洲区 | 偷窥少妇久久久久久久久| 久久精品国产亚洲av影院| 欧美亚洲日本久久精品| 精品熟女少妇a∨免费久久| 久久综合九色综合欧美就去吻 | 好久久免费视频高清| 久久天天躁狠狠躁夜夜avapp| 国产69精品久久久久777| 亚洲欧洲久久av|