在Unity中實現屏幕空間反射Screen Space Reflection(1)

本篇文章我會介紹一下我本身在Unity中實現的SSR效果
出發點是理解SSR效果的原理,所以最終效果不是很是完美的(代碼都是夠用就行),可是從學習的角度來講足以學習到SSR中的核心算法。
若是對核心算法沒有興趣,能夠直接使用Unity官方的PostProcessing庫,其中包含了一個SSR效果。(其實現來自於casual effects)html

參考資料:
https://github.com/Unity-Technologies/PostProcessing
http://www.kode80.com/blog/2015/03/11/screen-space-reflections-in-unity-5/
http://casual-effects.blogspot.com/2014/08/screen-space-ray-tracing.htmlgit

完成的工程:
https://github.com/yangrc1234/ScreenSpaceReflection
目前只在2017.一、DirectX下實現,沒有進行其餘測試。除非之後有需求,不然可能不會更新這個repo,畢竟官方已經有解決方案了,不必重複造輪子。這個repo用於學習目的就好了。
一些shader的宏、變量多是2017.1纔有的,若是老版本編譯不過歡迎提issue。
github

第一部分包含屏幕空間反射的定義、以及一個最初步的實現。算法

屏幕空間反射

屏幕空間反射是一個後處理效果。經過對屏幕空間的畫面,按必定方式投射光線,採樣光線路徑上的像素,獲得一個點上的反射顏色。
好比說,對於一個像素A,咱們去計算它的反射。要計算反射,咱們必需要知道視線方向和該點的空間位置以及的法線方向,從而計算出光線的方向。
視線方向好說,空間位置,咱們能夠從深度貼圖中還原出來。法線方向意味着屏幕空間反射只能在Deferred Rendering下進行。在Deferred Rendering下咱們能夠輕鬆的從GBuffer中獲得一個點的法線方向。c#

獲取這些信息後,咱們就能夠開始投射光線了。每次光線步進,咱們都將當前位置的點再投影到屏幕空間上,去採樣屏幕上的像素。若是咱們計算獲得(如何計算等下再說)該像素是光線路徑上的一點,咱們就能夠將該點返回做爲結果了。app

如下是實際代碼:函數

[ImageEffectOpaque]
    private void OnRenderImage(RenderTexture source, RenderTexture destination) {
        mat.SetTexture("_BackfaceTex", GetBackfaceTexture());
        mat.SetMatrix("_WorldToView", GetComponent<Camera>().worldToCameraMatrix);        //emmmmm不知道爲何UNITY_MATRIX_V在這裏變成了一個單位矩陣。須要手動設置world to view的矩陣。
        Graphics.Blit(source, destination, mat,0);    
    }
v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                float4 cameraRay = float4(v.uv * 2.0 - 1.0, 1.0, 1.0);    //做爲一個後期特效,咱們能夠經過uv座標,來得到相機光線方向。注意座標z爲1.0,這裏的cameraRay是從原點到far clip plane的光線
                cameraRay = mul(unity_CameraInvProjection, cameraRay);    //將相機光線從clip space轉移到view space
                o.csRay = cameraRay / cameraRay.w;           
                return o;
            }

                        fixed4 frag (v2f i) : SV_Target
            {
                float decodedDepth = Linear01Depth(tex2D(_CameraDepthTexture, i.uv).r);    
                float3 csRayOrigin = decodedDepth * i.csRay;        //由於i.csRay是指着far clip plane的光線,此時csRayOrigin是view space的光線起點
                float3 wsNormal = tex2D(_CameraGBufferTexture2, i.uv).rgb * 2.0 - 1.0;    //世界座標系下的法線
                float3 csNormal = normalize(mul((float3x3)_WorldToView, wsNormal));    //將轉換到view space

                                float2 hitPixel;
                float3 debugCol;
                                        
                if (traceRay(        //檢測相交
                        csRayOrigin, 
                        normalize(reflect(csRayOrigin, csNormal)),
                        hitPixel,    //out
                        debugCol))    //out
                {
                        reflection = (1 - rayPercent) * tex2D(_MainTex, hitPixel);
                }
                //return float4(debugCol, 1);
                return tex2D(_MainTex, i.uv) + tex2D(_CameraGBufferTexture1,i.uv) * half4(reflection,1);
            }

在traceRay方法中,咱們進行實際的光線投射、相交檢測。
traceRay的簽名中我設置了一個debugCol的參數,當我須要debug這個函數時,我將須要debug的內容放到debugCol中,在main裏輸出debugCol的顏色。這只是我我的的習慣。
對於反射顏色的計算,我只是簡單的獲取了那個像素的顏色,而後加到輸出裏去而已。事實上,由於咱們是在Deferred rendering模式下,咱們能夠獲取到該點的全部的用於着色的信息,用這些信息咱們能夠進行一次完整的基於物理着色。在PostProcessing中的SSR就是這麼作的,能夠參考一下。學習

相關文章
相關標籤/搜索