高效VolumeLighting的实现及说明

在这里插入图片描述在这里插入图片描述
VolumeLighting的本质就是模拟丁达尔效应,即光线在空气中的散射效果。对于游戏渲染,远景可以直接使用大气散射实现,而近景则使用RayMatching的VolumeLighting实现。

具体可以看我做的Unity实现 https://gitee.com/alienity/srp-volume-lighting,有兴趣的同学可以看每次的提交记录,基本都记录了我每次提交的改进

实现原理

VolumeLighting原理很简单,就是使用RayMatching对每一小段的光照进行积分,最后把积分的结果加到最终渲染的纹理上。

我们做的完善的点

  1. 做了半分辨率的VolumeLighting纹理的RayMatching积分
  2. 用Dither对RayMatching的起点做偏移,可以达到空间上对阶梯型的纹理做一次滤波
  3. 使用BilateralUpSampling基于Depth做了上采样
  4. 做RayMatching的时候使用3DTexture累计每一步得到的Volume积分强度,最后在绘制Transparent物体的时候直接从这张3DTexture中采样

废话不多说,我们看看代码实现

	float4 RayMarch(float2 screenUV, float3 rayStart, float3 rayDir, float rayLength)
	{
		int stepCount = _SampleCount;

		float stepDifference = 1.0f / stepCount;
		float stepSize = rayLength * stepDifference;
		float3 step = rayDir * stepSize;

		int accVolumeWidth;
		int accVolumeHeight;
		int accVolumeDepth;
		_3DVolumeAccumulateTexture.GetDimensions(accVolumeWidth, accVolumeHeight, accVolumeDepth);

		int2 relativeXY = int2(accVolumeWidth, accVolumeHeight) * screenUV;

		// 因为这里使用了半分辨率,所以计算dither的时候也手动使用半分辨率计算
		uint2 screenPos = uint2(screenUV * _ScreenParams.xy * 0.5f);
		uint2 ditherPos = fmod(screenPos, 4);
		float offset = _dither4x4[ditherPos.x][ditherPos.y] * 0.0625f;
		rayStart += step * offset;


		float3 volColor = float3(0, 0, 0);

		[loop]
		for (int i = 0; i < stepCount; ++i)
		{
			float3 currPositionWS = rayStart + step * i;

			// 转换到主光源空间
			float4 shadowCoord = TransformWorldToShadowCoord(currPositionWS);
			Light mainLight = GetMainLight(shadowCoord);

			volColor += 0.1 * mainLight.color * mainLight.shadowAttenuation * mainLight.shadowAttenuation;

#ifdef _ADDITIONAL_LIGHTS
			// uint pixelLightCount = GetAdditionalLightsCount();
			uint pixelLightCount = _AdditionalLightsCount.x;
			for (uint lightIndex = 0u; lightIndex < pixelLightCount; ++lightIndex)
			{
				Light light = GetAdditionalLight(lightIndex, currPositionWS, half4(1, 1, 1, 1));
				volColor += 0.1 * light.color * light.distanceAttenuation * light.shadowAttenuation * light.shadowAttenuation;
			}
#endif

			// 计算在视椎体中的相对深度位置,使用cell向上取整
			int relativeDepth = round(saturate(stepDifference*(i+1)) * accVolumeDepth);
			_3DVolumeAccumulateTexture[int3(relativeXY, relativeDepth)] = volColor * stepDifference;

		}

		return float4(volColor * stepDifference, 1);
	}

我们使用的是光栅化的方法做了RayMathcing,先把绘制的分辨率减半,

在这里插入图片描述在这里插入图片描述

我们对每个像素都做固定次数的RayMatching,实际效果看起来也还行,毕竟Volume信息都是低频信息,同时为了避免产生阶梯的效果,我们对每个RayMatching的起点都做了Dither的偏移。通过以上两张图的对比,添加了Dither之后的效果会好很多。

half4 frag(Varyings input) : SV_Target
{
	UNITY_SETUP_INSTANCE_ID(input);
	UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
	half2 uv = input.uv;

	uint2 depthUV = uv * _ScreenParams.xy;
	half deviceDepth00 = saturate(LoadSceneDepth(depthUV)+0.00001f);
	half deviceDepth01 = saturate(LoadSceneDepth(depthUV+uint2(0,1))+0.00001f);
	half deviceDepth10 = saturate(LoadSceneDepth(depthUV+uint2(1,0))+0.00001f);
	half deviceDepth11 = saturate(LoadSceneDepth(depthUV+uint2(1,1))+0.00001f);

	half2 _screenSizeFrag = _ScreenSize.zw;
	half4 volumeCol00 = SAMPLE_TEXTURE2D(_HalfVolumeTex, sampler_HalfVolumeTex, uv);
	half4 volumeCol01 = SAMPLE_TEXTURE2D(_HalfVolumeTex, sampler_HalfVolumeTex, uv+float2(_screenSizeFrag.x, 0));
	half4 volumeCol10 = SAMPLE_TEXTURE2D(_HalfVolumeTex, sampler_HalfVolumeTex, uv+float2(0, _screenSizeFrag.y));
	half4 volumeCol11 = SAMPLE_TEXTURE2D(_HalfVolumeTex, sampler_HalfVolumeTex, uv+_screenSizeFrag.xy);

	half4 color = (volumeCol00 * deviceDepth00 
					+ volumeCol10 * deviceDepth01 
					+ volumeCol10 * deviceDepth10 
					+ volumeCol11 * deviceDepth11) 
					/(deviceDepth00 + deviceDepth01 + deviceDepth10 + deviceDepth11);

	return color;
}

因为我们是使用半分辨率做了RayMatching,所以这里使用了BilateralUpSampling做上采样,依赖的额外信息就是Depth,因为depth的值在边界的时候差别会非常的大,有可能为0,所以为了避免上采样之后的volume值也变成0,在每个depth的值上加了一个非常小的bias。

绘制不透明物理的时候,我们只需要把Volume的纹理直接叠加到场景上就好了

#if defined(_VOLUMEFUSE_ON)
    float3 positionNDC = input.positionNDC.xyz / input.positionNDC.w;
	float deviceDepth = SampleSceneDepth(positionNDC.xy);
#if UNITY_REVERSED_Z
    deviceDepth = 1 - deviceDepth;
#endif
	float3 samplePos = float3(positionNDC.xy, deviceDepth);
    float3 accVolumeColor = SAMPLE_TEXTURE3D(_3DVolumeAccumulateTexture, sampler_3DVolumeAccumulateTexture, samplePos);
    color.rgb += accVolumeColor.rgb;
#endif

最后为了让透明物体绘制的时候也受到Volume的影响,我们把Volume的每一步都累积到了一张3DTexture上,最后我们需要在绘制不透明物体的时候根据深度从3DTexture中采样,就可以得到相机到当前位置累计的Volume的强度值。

引用

[1] https://github.com/SlightlyMad/VolumetricLights
[2] https://book.douban.com/subject/25738978/

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这个插件允许您通过生成真正容积的程序光束来大大改善场景的照明。 这是模拟聚光灯和手电筒的密度,深度和音量的完美,简单而便宜的方法。 The simple and efficient volumetric lighting solution compatible with every platforms: Windows PC, Mac OS X, Linux, WebGL, iOS, Android, VR, AR, Consoles, Built-in/Legacy Render Pipeline, SRP (URP & HDRP)! The perfect, easy and cheap way to simulate density, depth and volume for your spotlights and flashlights, even on Mobile! It greatly improves the lighting of your scenes by automatically and efficiently generating truly volumetric procedural beams of light to render high quality light shafts effects. A production ready plugin proven by awesome released games showcasing it: - BONEWORKS released for high-end PC VR - Carly and the Reaperman released for Playstation 4 PSVR and high-end PC VR - Kingspray Graffiti released for high-end PC VR and Oculus Quest - Hexagroove released for Nintendo Switch - Covert released for Playstation 4 PSVR, Oculus Rift and Oculus Go Features: - Truly volumetric: works even if you are INSIDE the beam of light. - Incredibly easy to use and integrate / Import it instantly / Zero setup required. - In addition to the Built-in Legacy Render Pipeline, it fully supports the Universal Render Pipeline (URP) and the High Definition Pipeline (HDRP). - Optimized for VR: tested with high-end headsets (Oculus Rift, HTC Vive, Valve Index...) and standalone hardware (Oculus Go, Oculus Quest...), supports all Stereo Rendering Methods (Multi Pass, Single Pass and Single Pass Instanced or Multiview). - AR Ready: supports both Apple iOS ARKit and Google Android ARCore. - GPU Instancing & SRP Batcher: render and batch thousands of beams in 1 single drawcall. - Super FAST w/ low memory footprint: doesn't require any post-process, command buffers, nor compute shaders: works great even on low-performance platforms such as Mobiles and WebGL. - Procedural generation: everything is dynamically computed under the hood. - Add unlimited light beams everywhere: alternative solutions usually requi
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值