天天看點

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

 全局光照介紹

        全局光照技術本身是一個很複雜的技術,有非常多方式實作。從管線來說有光栅化實作的全局光照,有光線追蹤實作的更逼真的行為樹。而我們目前主流的或者說更多人讨論的是光栅化下的全局光照。

        而全局光照又包括了更多的表現,比如天氣,環境等都會影響到全局光照,而每一塊在引擎中可能都需要單獨處理。

        目前流行的引擎比如unity用的光照貼圖、光照探針,反射平面等方式來模拟間接光照。而ue5用的ssgi+體素+有向距離場來做的間接光照。目前也有一些自己實作的間接光照效果。

        最近對這塊也挺感興趣,參考了很多文章和論文,覺得可以用computeshader的gpu運算能力來實作一次全局實時光照效果。(目前還有一些限制,之後再優化)

        先看看沒有全局光照的效果

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

實作原理:

        要得到全局光照,首先直接光照我們直接用lit算,其次間接光照我們需要考慮兩塊,一個是ao的資訊,這裡我直接用ssao的方式實作,然後就是顔色資訊,我們知道一個像素他會受到旁邊的多個物體或者像素的資訊影響。還有一個就是在比較暗的環境下,要擷取到周圍的顔色資訊來疊加。那麼基于這幾點,我們需要的是全局的法線資訊,明亮環境下的顔色資訊,步進的方式實作顔色擷取和指派來做間接光照。

        我的方法其實還有很多空間,比如我的隻有單次反射,而且我的高光沒有做,也是因為我在聯系全局光照的實作方式,實時全局光照還有其他更高效的方式實作,比如體素,有号距離場等,後續再補上。

我的實作步驟如下:

1.建立可以渲染對象到rt的的renderfeature

2.渲染螢幕空間的法線資訊

3.渲染螢幕空間的光亮顔色資訊

4.全局光照計算

4.1.computeshader清理顔色資訊

4.2.computeshader渲染間接光照

4.3.blit結合間接光照資訊

1.建立可以渲染對象到rt的的renderfeature

        為什麼需要渲染資訊到rt的renderfeature呢?首先是因為我們需要一些全局的資訊,比如提到的全局法線資訊以及光亮顔色資訊。而我們的renderfeature是繼承renderobject的,是多渲染一個pass的object,是直接上色到cameracolortexture的,而我們是需要基于每個object渲染到我們自己希望的rt上的,是以就有了這個步驟。

        這一步其實就是建立了一個繼承ScriptableRendererFeature的RenderObjectsToRT,然他建立一個繼承ScriptableRenderPass的RenderObjectsToRTPass。而這個RenderObjectsToRTPass也是針對每個object執行渲染,但是我們修改了他的渲染目标rt

cmd.GetTemporaryRT(RT.id, RTResolution.x, RTResolution.y, 8, FilterMode.Bilinear, rtf); cmd.SetRenderTarget(RT.Identifier()); cmd.ClearRenderTarget(true, true, Color.black);
           

這樣再外面就能根據自己需要把物體渲染到對象上了。

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

像這裡我定義passtag就是他的rt名。這樣shader中就可以直接用了。

2.渲染螢幕空間的法線資訊

既然有了renderobjectstort的方式,就可以添加一個feature,這個feature的materail用我們normal的mat

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

然後shader中深度需要寫入,并且blend是OneZero

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

然後我們需要寫入他的世界空間法線資訊

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

這樣得到的結果是

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

這就是我們需要的法線資訊。

3.渲染螢幕空間的光亮顔色資訊

上面有提到如果在場景比較暗的情況下我們要擷取場景的顔色資訊就不能靠直接光照的顔色資訊來組成顔色了,因為直接光照已經在場景中沒有顔色資訊了,是一篇死黑。這時候就需要我們提前拿到一個明亮的資訊來給點光源照亮環境的機會。是以我們需要這樣的圖檔。最終的效果在比較黑暗的情況下是這樣的:

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

可以看到他還是把點光源周圍的環境照亮了。

4.全局光照計算

接下來進入computeshader實作全局光照的效果。

4.1.computeshader清理顔色資訊

首先肯定要清理我的rt中的顔色,不然會殘留上次的顔色。比較簡單:

uint2 uv = uint2(id.x, id.y); 
SDFRGBTexture[uv]= float4(0, 0, 0, 0); 
IndirectLightTexture[uv] = float4(0, 0, 0, 0);
           

這裡兩個rt一個是用來現在點光源的光照資訊的,一個是間接光照的資訊。

4.2.computeshader渲染間接光照

這裡應該是我們的渲染的重點。

首先我們會擷取深度坐标,然後跟uv一起去轉換裁剪空間的坐标到世界坐标,這裡做法就比較傳統了,就是拿世界空間到裁剪空間的逆矩陣和我們的uv以及depth計算出來的。

float4 clipPos = float4(((float2(float(uv.x), float(uv.y)) / 1024.0000)) * 2.0 - 1.0, (1 - _CameraDepthAttachment[uv].r), 1);
float4 posW = mul(_VPInvMatrix, clipPos);
posW /= posW.w;
           

然後我們先運算ssao,他就是找目前像素點周圍圓環範圍内的随機點,最後跟他的距離成反比來得到這個ao值,也是疊代越多越精确了。

float3 v_s1 = PickSamplePoint(curPos, randAddon, i);
//------------ao-----------------
v_s1 *= sqrt((i + 1.0) * rcpSampleCount) * aoRADIUS;
v_s1 = faceforward(v_s1, -normal, v_s1);

float3 newPosAOW = posW + v_s1;
float4 newScreenAOPos = mul(_VPMatrix, newPosAOW);
newScreenAOPos /= newScreenAOPos.w;
newScreenAOPos = (newScreenAOPos + float4(1, 1, 1, 1)) / 2;
newScreenAOPos = float4(newScreenAOPos.x * _ScreenRect.x, newScreenAOPos.y * _ScreenRect.y, newScreenAOPos.z, newScreenAOPos.w);
float4 newColorAOPos = ClipToWorld(uint2(newScreenAOPos.xy));//_RenderObjectsToRTFeature[uint2(newScreenAOPos.xy)] * 2000 - 1000;
float3 v_s2_AO = newColorAOPos - posW;


// Estimate the obscurance value
float a1_AO = max(dot(v_s2_AO, normal), 0.0);
float a2_AO = dot(v_s2_AO, v_s2_AO) + EPSILON;
aoNum += a1_AO * rcp(a2_AO) * 2;//(_LevelID == 2 ? 2 : 2);
           

然後就是顔色部分了,也是在世界空間根據目前uv的位置來取方向步進一定的距離,然後拿到顔色再跟他的距離取反比來得到了。目前剛說了還得考慮完全黑的情況,是以會有一些黑暗情況下的顔色處理。

for (int j = 1; j <= (_LevelID == 0 ? 30 : (_LevelID == 1 ? 20 : 10)); j++)
{
float3 newPosW = posW + v_s1 * j * _RayDistance;
float4 newScreenPos = mul(_VPMatrix, newPosW);
newScreenPos /= newScreenPos.w;
newScreenPos = (newScreenPos + float4(1, 1, 1, 1)) / 2;
newScreenPos = float4(newScreenPos.x * _ScreenRect.x, newScreenPos.y * _ScreenRect.y, newScreenPos.z, newScreenPos.w);

uint2 newUV = uint2(newScreenPos.xy);
float4 newColorPos = float4(newPosW,1);//_RenderObjectsToRTFeature[newUV] * 2000 - 1000;

float3 v_s2 = newColorPos - posW;
float a2 = dot(v_s2, v_s2);
if (a2 > 0.1)
{
float4 cameraColor = _CameraColorTexture[newUV];
float4 newColorNum = cameraColor * rcp(5);
if (cameraColor.r > 0.05 || cameraColor.g > 0.05 || cameraColor.b > 0.05)
{
	if (curColor.r < 0.01 && curColor.g < 0.01 && curColor.b < 0.01)
	{
		newColorNum += onlyColor;// *((1 + materialProp) * 2);
	}
}
a2 += 1;
color += newColorNum * rcp(a2 * (_LevelID == 0 || _LevelID == 1 ? 0.3 : 1));// *((1 + materialProp) * 1);
realAddNum++;
}
}
           

最後得到的效果是

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

這個就是間接光照的效果,可以看到他會産生很多鋸齒,如果就這樣展示整個界面還是有點問題的

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

可以看到鋸齒很明顯,那麼我們就得抗鋸齒,抗鋸齒得方式很多,比如fxaa,taa等。我選擇的是直接模糊,因為他的消耗還能接受而且效果也不錯。

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

4.3.blit結合間接光照資訊

最後我們肯定要把間接光照結合直接光一起展示,是以我再blit上做,當然如果有後處理,再那邊也要做一遍。

computeshader實作全局光照 全局光照介紹實作原理:我的實作步驟如下:總結:

總結:

整體就是上面的思路。最後說一下我是建立了一個叫GlobalLight的ScriptableRendererFeature,再裡面通過commandbuffer組織computeshader來實作的。

CommandBuffer cmd = CommandBufferPool.Get(profilerTag);
using (new ProfilingScope(cmd, m_ProfilingSampler))
{
    cmd.Clear();
    
    Camera currentCamera = renderingData.cameraData.camera;
    var vpMatrix = currentCamera.projectionMatrix * currentCamera.worldToCameraMatrix;
    
    giComputeShader.SetMatrix(VPMatrixID, vpMatrix);
    giComputeShader.SetMatrix(VPInvMatrixID, vpMatrix.inverse);
    giComputeShader.SetVector(ScreenRectID, new Vector2(Screen.width, Screen.height));
    Vector3 screenLightPos = currentCamera.WorldToScreenPoint(AreaLight.areaLightPos);
    Vector3 prevScreenLightPos = currentCamera.WorldToScreenPoint(AreaLight.prevLightPos);

    giComputeShader.SetTexture(ClearKernel, IndirectLightTexturert, IndirectRT);
    giComputeShader.SetTexture(ClearKernel, SDFRGBTexturert, outputRGBRT);
    giComputeShader.SetVector(PrevScreenAreaLightPos, prevScreenLightPos);
    cmd.DispatchCompute(giComputeShader, ClearKernel, 32, 32, 1);

    giComputeShader.SetVector(ScreenAreaLightPos, screenLightPos);
    giComputeShader.SetVector(WorldLightPos, AreaLight.areaLightPos);
    giComputeShader.SetTexture(kernel, SDFRGBTexturert, outputRGBRT);
    cmd.SetComputeTextureParam(giComputeShader, kernel, cameraDepthTexture.id, cameraDepthTexture.Identifier());
    cmd.DispatchCompute(giComputeShader, kernel, 32, 32, 1);


    giComputeShader.SetTexture(IndirectLightkernel, IndirectLightTexturert, IndirectRT);
    cmd.SetComputeTextureParam(giComputeShader, IndirectLightkernel, onlyColorRTFeature.id, onlyColorRTFeature.Identifier());
    cmd.SetComputeTextureParam(giComputeShader, IndirectLightkernel, outputNormalRT.id, outputNormalRT.Identifier());
    cmd.SetComputeTextureParam(giComputeShader, IndirectLightkernel, cameraOpaqueTexture.id, cameraOpaqueTexture.Identifier());
    cmd.SetComputeTextureParam(giComputeShader, IndirectLightkernel, cameraDepthTexture.id, cameraDepthTexture.Identifier());
    cmd.SetComputeVectorParam(giComputeShader, cameraPos, currentCamera.transform.position);
    cmd.SetComputeIntParam(giComputeShader, qLevelID, (int)mEQLevel);
    cmd.SetComputeIntParam(giComputeShader, rayDistanceID, mRayDistance); 
    cmd.DispatchCompute(giComputeShader, IndirectLightkernel, 32, 32, 1);
    
    {
        giComputeShader.SetTexture(RGBkernel, IndirectLightTexturert, IndirectRT);
        giComputeShader.SetTexture(RGBkernel, IndirectLightTexturert, IndirectRT);
        cmd.DispatchCompute(giComputeShader, RGBkernel, 32, 32, 1);
    }


    AreaLight.prevLightPos = AreaLight.areaLightPos;

    cmd.EndSample(profilerTag);
}
           

繼續閱讀