Ray Tracing Shadow + Semi-PCSS

小小處理上一個Work的光追陰影

圖1. 上為效能截圖、左下hard shadow、右下soft shadow

使用光追陰影之後,陰影會異常清晰,原因應該不用解釋了;作為老二,hard是好事,但是作為陰影,還是需要有soft一點(半影區域)的效果的。

(Ray tracing shadow is too clear and sharp, we need to soften them.)

PCSS

實際上包含著PCF的運算,但是引入了Blocker Search的概念,原文可以參考NVIDIA的文章

圖2. Blocker Search公式

一般來說PCF算下去,就是整體陰影都一起柔掉了~但透過這個Blocker Search,讓陰影隨著遮擋物與接收點的距離產生變化,圖1也可以看到,接收點距離遮擋物越遠,半影區域越多,越靠近遮擋物的接收點,則越來越銳利,這就是PCSS的特色了,也很符合現實世界的樣子。

(PCSS is Blocker Search + PCF, received shadow is softer/harder when blocker is far/near.)

從圖2的公式看,我們要取得接收點到燈光的距離&遮擋點到燈光的距離&燈光尺寸,這些資訊在DXR光追中太容易取得了。

payload.distBlockToLight = RayTCurrent();  // get TMax of ray at blocker point

// output shadow
float currAtten = _OutputShadow[DispatchRaysIndex().xy].r;
_OutputShadow[DispatchRaysIndex().xy].r = payload.atten;

float receiverDistToLight = length(lightPos - wpos); // receiver dist to light
_OutputShadow[DispatchRaysIndex().xy].g = receiverDistToLight - payload.distBlockToLight;  // blocker distance to light
_OutputShadow[DispatchRaysIndex().xy].b = receiverDistToLight;
_OutputShadow[DispatchRaysIndex().xy].a = _light.shadowSize;    // use as light size

在closest shader這個流程,就會更新ray打到的位置,代表blocker與receiver的距離,那只要把燈光到接收點的距離減掉它,就是遮擋物到燈光的距離了,另外這種數值一定是超過[0,1]範圍的,記得至少要用Float16來儲存資料。

(It’s easy to get distance from blocker/receiver to light in DXR Shader. DXR Shader provides RayTCurrent() which indicates the distance between blocker and receiver.)

    float2 texelSize = 1.0f / d;
    for (int i = -innerLoop; i <= innerLoop; i++)
    {
        for (int j = -innerLoop; j <= innerLoop; j++)
        {
            // x for blocked, y for dist to blocker
            float4 shadowData = _TexTable[_RayShadowIndex].Sample(_SamplerTable[_CollectShadowSampler], uv + texelSize * float2(i, j)).rgba;

            [branch]
            if (shadowData.x < 1)
            {
                avgBlockDepth += shadowData.y;      // blocker to light distance
                avgReceiverDepth += shadowData.z;   // receiver to light distance
                lightSize += shadowData.w;          // light size
                blockCount++;
            }
        }
    }

    [branch]
    if (blockCount > 0)
    {
        avgBlockDepth /= blockCount;
        avgReceiverDepth /= blockCount;
        lightSize /= blockCount;

        // penumbra formula: (d_receiver - d_blocker) * light_size / d_blocker
        float penumbra = (avgReceiverDepth - avgBlockDepth) * lightSize / avgBlockDepth;
        penumbra = pow(penumbra, 0.5f);
        return penumbra;
    }

Blocker Search的片段,取樣剛才ray tracing後的結果,平均每個點的blocker/receiver到燈光的距離,再套入公式,然後我故意給了平方衰退讓它的衰退變化再快一點。

(Calculate penumbra and give it a square root attenuation.)

最後就是Blur操作了,之所以把標題取為半PCSS是因為這邊根本沒有PCF,PCF需要shadow map的深度值、用shadow matrix換算目前螢幕深度的值,然後呼叫SampleCmp來比較&blend,但是光追陰影是不需要這兩個東西、所以當然也沒有這兩個東西。現在做的事情是Screen-Space Shadow,沒有什麼深度值給你比,所以這邊套上模糊就行了。

(I call this “Semi-PCSS". Because I don’t have shadow map and I can’t use SampleCmp for PCF. There is no PCF here. I use custom box blur to shadow.)

    [loop]
    for (int i = -innerLoop; i <= innerLoop; i++)
    {
        [loop]
        for (int j = -innerLoop; j <= innerLoop; j++)
        {
            float w = (innerLoop - abs(i) + 1) * (innerLoop - abs(j) + 1);
            atten += _TexTable[_RayShadowIndex].Sample(_SamplerTable[_CollectShadowSampler], uv + float2(i, j) * texelSize * penumbra).r * w;
            count += w;
        }
    }

w的權重簡單計算,如果kernel5x5就是12321 24642 36963 24642 12321,想要正式一點的高斯權重也是行,但是就建議算個查找表了因為高斯有指數操作,在取樣陰影的時候,乘上半影區域的衰退,完成柔和。

(Simply calculate blur weight and consider penumbra when sampling screen shadow.)

最後的最後,由於這是screen-space shadow soft,跟以前轉換到shadow map的空間不同,這裡會發生遠處陰影會糊掉的現象,越來越糊。這是很理所當然的因為遠處的資訊會越來越少,但我們還是用一樣的kernel去取樣,所以最後再加一小段。

    float depth = _TexTable[_TransDepthIndex].Sample(_SamplerTable[_CollectShadowSampler], i.uv).r;
    float3 wpos = DepthToWorldPos(depth, i.screenPos);
    float distRatio = 1 - saturate(abs(wpos.z - _CameraPos.z) * 0.05f);

    // reduce penumbra according to distance
    // prevent far object blurring too much

    float penumbra = PenumbraFilter(i.uv, _PCFIndex);
    return BlurFilter(i.uv, _PCFIndex, penumbra * distRatio);

就這樣,用距離去衰退kernel,近處的可能是5×5,遠處的模糊大概就3×3甚至沒有。

(Due to the screen space blur, far object shadow is blurred too much. Calculate distance to camera and reduce blur size on small pixels.)

圖3. 左邊太糊了,右邊隨著距離調整模糊強度

大概就這樣了,下次要開始練習處理point light/spot light。

Ray Tracing Shadow + Semi-PCSS 有 “ 3 則迴響 ”

發表留言

使用 WordPress.com 設計專業網站
立即開始使用