computeshader实现全局光照

全局光照介绍

        全局光照技术本身是一个很复杂的技术,有非常多方式实现。从管线来说有光栅化实现的全局光照,有光线追踪实现的更逼真的行为树。而我们当前主流的或者说更多人讨论的是光栅化下的全局光照。

        而全局光照又包括了更多的表现,比如天气,环境等都会影响到全局光照,而每一块在引擎中可能都需要单独处理。

        当前流行的引擎比如unity用的光照贴图、光照探针,反射平面等方式来模拟间接光照。而ue5用的ssgi+体素+有向距离场来做的间接光照。当前也有一些自己实现的间接光照效果。

        最近对这块也挺感兴趣,参考了很多文章和论文,觉得可以用computeshader的gpu运算能力来实现一次全局实时光照效果。(当前还有一些限制,之后再优化)

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

实现原理:

        要得到全局光照,首先直接光照我们直接用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);

这样再外面就能根据自己需要把物体渲染到对象上了。

像这里我定义passtag就是他的rt名。这样shader中就可以直接用了。

2.渲染屏幕空间的法线信息

既然有了renderobjectstort的方式,就可以添加一个feature,这个feature的materail用我们normal的mat

然后shader中深度需要写入,并且blend是OneZero

然后我们需要写入他的世界空间法线信息

这样得到的结果是

这就是我们需要的法线信息。

3.渲染屏幕空间的光亮颜色信息

上面有提到如果在场景比较暗的情况下我们要获取场景的颜色信息就不能靠直接光照的颜色信息来组成颜色了,因为直接光照已经在场景中没有颜色信息了,是一篇死黑。这时候就需要我们提前拿到一个明亮的信息来给点光源照亮环境的机会。所以我们需要这样的图片。最终的效果在比较黑暗的情况下是这样的:

可以看到他还是把点光源周围的环境照亮了。

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 i = 1; i <= sCount; i++)
	{
#if defined(SHADER_API_D3D11)
		// This 'floor(1.0001 * s)' operation is needed to avoid a DX11 NVidia shader issue.
		i = floor(1.0001 * i);
#endif
		float3 v_s1 = PickSamplePoint(curPos.xy, randAddon, i);


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

		float3 newPosAOW = curPos.xyz + v_s1;
		float4 newScreenAOPos = mul(_VPMatrix, float4(newPosAOW,1));
		newScreenAOPos /= newScreenAOPos.w;
		newScreenAOPos = (newScreenAOPos + float4(1.0, 1.0, 1.0, 1.0)) / 2.0;
		newScreenAOPos = float4(newScreenAOPos.x * _RTResolution.x, newScreenAOPos.y * _RTResolution.y, newScreenAOPos.x * _RTResolution.z, newScreenAOPos.y * _RTResolution.w);

		float4 newColorAOPos = ClipToWorld(uint2(newScreenAOPos.zw), newScreenAOPos.zw, 1);//_RenderObjectsToRTFeature[uint2(newScreenAOPos.xy)] * 2000 - 1000;
		float3 v_s2_AO = newColorAOPos.xyz - curPos.xyz;

		// Estimate the obscurance value
		float a1_AO = max(dot(v_s2_AO, normal), 0.0);// *distance(_CameraPosition.xyz, posW) / 1000;
		float a2_AO = sqrt(dot(v_s2_AO, v_s2_AO)) + EPSILON;
		aoNum += a1_AO * rcp(a2_AO);//(_LevelID == 2 ? 2 : 2);

		v_s1 = PickSamplePoint(curPos.xy, randAddon, i);
		//------------ao-----------------



		// Make it distributed between [0, _Radius]
		v_s1 *= sqrt((i + 1.0) * rcpSampleCount) * RADIUS;
		v_s1 = faceforward(v_s1, -normal, v_s1);
		v_s1 = normalize(v_s1);

		/*uint isNextStep = 1;
		for (int j = 0; j < sCount; j++)
		{
			if (dot(vS1floats[j], v_s1) > 0.95)
			{
				isNextStep = 0;
				break;
			}
		}

		if (isNextStep == 0)
		{
			continue;
		}
		vS1floats[i] = v_s1;*/

		for (int j = 1; j <= colNum; j+= 1)
		{
			
			float3 newPosW = curPos.xyz + v_s1 * j;
			float4 newScreenPos = mul(_VPMatrix, float4(newPosW,1));
			newScreenPos /= newScreenPos.w;
			newScreenPos = (newScreenPos + float4(1, 1, 1, 1)) / 2;
			newScreenPos = float4(newScreenPos.x * _RTResolution.x, newScreenPos.y * _RTResolution.y, newScreenPos.z, newScreenPos.w);

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

			float3 v_s2 = newColorPos.xyz - curPos.xyz;
			/*float vDistance = 0.1;
			if (abs(v_s1.y) < vDistance || abs(v_s1.x) < vDistance || abs(v_s1.z) < vDistance)
			{
				continue;
			}*/
			float a2 = dot(v_s2, v_s2);
			if (a2 > 1)
			{
				realAddNum++;
				float4 cameraColor = _CameraColorTexture[newUV];

				float3 colorValue = cameraColor.xyz - curColor.xyz;

				float colorLen = (colorValue.x * colorValue.x + colorValue.y * colorValue.y + colorValue.z * colorValue.z);
				//颜色相近就不加了
				if (colorLen < 0.5)
				{
					continue;
				}

				float4 newColorNum = cameraColor;// *rcp(pow(2, _LevelID));
				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(colNum);
				//color = color / sqrt(a2);//rcp(a2 * (_LevelID == 0 ? 0.3 : (_LevelID == 1 ? 0.3 : 1.7)));// *((1 + materialProp) * 1);
			}
		}
	}

	color *= (realAddNum == 0) ? 1 : rcp(realAddNum);
	color *= 1 - color.a;
	color *= RADIUS;
	//color.rgb *= 20;

	aoNum *= aoRADIUS;
	// Apply contrast
	aoNum = PositivePow(aoNum * INTENSITY * rcpSampleCount, kContrast);
	color.a = 1 - aoNum;
	// Apply contrast
	//aoNum = PositivePow(aoNum * INTENSITY * rcpSampleCount, kContrast);
	IndirectLightTexture[uv] = color;

最后得到的效果是

这个就是间接光照的效果,可以看到他会产生很多锯齿,如果就这样展示整个界面还是有点问题的

可以看到锯齿很明显,那么我们就得抗锯齿,抗锯齿得方式很多,比如fxaa,taa等。我选择的是直接模糊,因为他的消耗还能接受而且效果也不错。

4.3.blit结合间接光照信息

最后我们肯定要把间接光照结合直接光一起展示,所以我再blit上做,当然如果有后处理,再那边也要做一遍。

总结:

整体就是上面的思路。最后说一下我是建立了一个叫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);
}

猜你喜欢

转载自blog.csdn.net/llsansun/article/details/119895565