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


traceRay函數

在上一篇中,我們有如下簽名的traceRay函數

bool traceRay(float3 start, float3 direction, out float2 hitPixel, out float3 debugCol ) {
}

其中的參數意義都很明了。start和direction是相機空間下的光線起點,以及光線方向。

traceRay的核心代碼並不復雜,如下:

#define RAY_LENGTH 2.0
#define STEP_COUNT 64	//maximum sample count.
				UNITY_LOOP    //強制使用循環結構,不然就會代碼5秒鍾,編譯1小時
				for (int i = 1; i <= STEP_COUNT; i++) {
					float3 p = start + (float)i/STEP_COUNT * RAY_LENGTH * direction ;  //p是當前的光線的空間位置
					float pDepth = p.z / -_ProjectionParams.z;        //_ProjectionParams.z是far clip plane的值。又因為viewspace下正前方z值是負的,所以加個負號。
					float4 screenCoord = mul(_Projection, float4(p,1));    //將光線投影到screen space中。
					screenCoord /= screenCoord.w;
					if (screenCoord.x < -1 || screenCoord.y < -1 || screenCoord.x > 1 || screenCoord.y > 1)
						return false;
					float camDepth = Linear01Depth(tex2Dlod(_CameraDepthTexture, float4(screenCoord.xy / 2 + 0.5,0,0)));    //獲取當前像素的深度。為了使用循環結構,這里必須用tex2Dlod而不是tex2D。
					if (Intersect(pDepth,camDepth) ) {    //相交檢測
						hitPixel = screenCoord.xy / 2 + 0.5;
						debugCol = float3(hitPixel, 0);
						return true;
					}
				}

相交檢測

最簡單的方式

最簡單的,如果該像素的深度大於當前光線的深度(離相機更遠),此時我們認為這是一個命中。

if (pDepth > camDepth) {
    ...
}


該種方法如上圖所示,可以看到物體的下方會有明顯的“拖影”。

加入厚度

為了改進效果,我們加入一個像素厚度的考量。當光線位於像素后面,並且不超出該像素的厚度時,才算命中。我們往往給像素一個固定的厚度。

if (pDepth > camDepth && pDepth < camDepth + 0.001 ) {        //0.001是厚度
...
}


如圖,拖影不見了。

獲取像素實際的厚度

這種方法一般情況下就已經足夠好了。如果要進一步改進的話,我們可以通過backface渲染,得到第二張深度貼圖。通過將兩張深度貼圖的采樣相減,得到一個像素的“厚度”。再按照這個厚度去做相交測試。

后處理腳本:

    private void OnRenderImage(RenderTexture source, RenderTexture destination) {
        RenderBackface();
        mat.SetTexture("_BackfaceTex", GetBackfaceTexture());
        mat.SetMatrix("_WorldToView", GetComponent<Camera>().worldToCameraMatrix);
        Graphics.Blit(source, destination, mat,0);
    }
private void RenderBackface() {
        if (backfaceCamera == null) {
            var t = new GameObject();
            var mainCamera = Camera.main;
            t.transform.SetParent(mainCamera.transform);
            t.hideFlags = HideFlags.HideAndDontSave;
            backfaceCamera = t.AddComponent<Camera>();
            backfaceCamera.CopyFrom(mainCamera);
            backfaceCamera.enabled = false;
            backfaceCamera.clearFlags = CameraClearFlags.SolidColor;
            backfaceCamera.backgroundColor = Color.white;
            backfaceCamera.renderingPath = RenderingPath.Forward;
            backfaceCamera.SetReplacementShader(backfaceShader, "RenderType");
            backfaceCamera.targetTexture = GetBackfaceTexture();
        }
        backfaceCamera.Render();
        
    }

    private RenderTexture backfaceText;
    private RenderTexture GetBackfaceTexture() {
        if (backfaceText == null) { 
            backfaceText = new RenderTexture(Screen.width, Screen.height, 24, RenderTextureFormat.RFloat);
            backfaceText.filterMode = FilterMode.Point;     //VERY IMPORTANT!
        }
        return backfaceText;
    }

渲染背面深度的shader(來自kode80):

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "Unlit/BackfaceShader"
{
	Properties
	{
	}
	SubShader
	{
		Tags { "RenderType"="Opaque" }
		LOD 100
		Cull Front

		Pass
		{
		CGPROGRAM

#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"

		struct v2f {
		float4 position : POSITION;
		float4 linearDepth : TEXCOORD0;
	};

	v2f vert(appdata_base v) {
		v2f output;
		output.position = UnityObjectToClipPos(v.vertex);
		output.linearDepth = float4(0.0, 0.0, COMPUTE_DEPTH_01, 0.0);
		return output;
	}

	float4 frag(v2f input) : COLOR
	{
		return float4(input.linearDepth.z, 0.0, 0.0, 0.0);
	}

		ENDCG

		}
	}
}

					float camDepth = Linear01Depth(tex2Dlod(_CameraDepthTexture, float4(screenCoord.xy / 2 + 0.5, 0, 0)));
					float backZ = tex2Dlod(_BackfaceTex, float4(screenCoord.xy / 2 + 0.5, 0, 0)).r;
					if (pDepth > camDepth && pDepth < backZ) {
						hitPixel = screenCoord.xy / 2 + 0.5;
						debugCol = float3(hitPixel, 0);
						return true;
					}


如圖

注意我在C#腳本中標注的IMPORTANT一行。少了這一行導致了一個非常難debug的bug。具體原因是相機的深度貼圖是Point filter的,而自己創建的rendertexture是默認Bilinear filter的;如果不修改的話,我們用同一個坐標去采樣會導致實際上是不同位置的采樣進行相減。

要注意的是,這種獲取物體厚度的辦法並不萬能。比如一個物體是只有單面的,此時厚度計算就會出問題(可以想想為什么),類似的,如果相機在一個物體內部(其實也相當於單面)也會出問題。

對於這些單面物體,如果是透明物體,可以設置為Transparent,不寫入z緩沖,並且RenderType設置為非Opqaue,此時背面渲染shader就會忽視這個物體。

同時,此時光線有可能和物體的“背面“相交,但是毫無疑問我們只能獲得物體“正面”的顏色信息。此時反射出現的內容依然是物體的正面,對於純色物體這沒什么問題,但是對於其他物體就會顯得很weird了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM