2023-06-29 11:55:02 +08:00

11 KiB
Raw Blame History

UE4 渲染功能探究

New: Planar Reflections New: High Quality Reflections

UE4.26 SingleLayerWater笔记

官方论坛讨论

https://forums.unrealengine.com/development-discussion/rendering/1746626-actually-realistic-water-shader#post1789028

SingleLayerCommon.ush

计算光照强度、透明度。 struct WaterVolumeLightingOutput { float3 Luminance; float3 WaterToSceneTransmittance; float3 WaterToSceneToLightTransmittance; };

Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * BehindWaterSceneLuminance); Output.WaterToSceneTransmittance = Transmittance; Output.WaterToSceneToLightTransmittance;

目前没有开启RayMarching,所以核心代码为:

const float3 OpticalDepth = ExtinctionCoeff * BehindWaterDeltaDepth;
float3 Transmittance = exp(-OpticalDepth);
float3 ScatteredLuminance = ScatteringCoeff * (AmbScattLuminance + SunScattLuminance * DirectionalLightShadow);
ScatteredLuminance = (ScatteredLuminance - ScatteredLuminance * Transmittance) / ExtinctionCoeffSafe;

// Apply Fresnel effect to out-scattering towards the view
ScatteredLuminance *= CameraIsUnderWater ? 1.0 : (1.0 - EnvBrdf);	// Under water is less visible due to Fresnel effect
Transmittance *= CameraIsUnderWater ? (1.0 - EnvBrdf) : 1.0;		// Above	"		"		"		"		"

// Add single in-scattering apply colored transmittance to scene color
Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * (BehindWaterSceneLuminance* ColorScaleBehindWater));
Output.WaterToSceneTransmittance = Transmittance;
Output.WaterToSceneToLightTransmittance = Transmittance * MeanTransmittanceToLightSources;
	const float BehindWaterDeltaDepth = CameraIsUnderWater ? WaterDepth : max(0.0f, SceneDepth - WaterDepth);

		const float3 ScatteringCoeff = max(0.0f, GetSingleLayerWaterMaterialOutput0(MaterialParameters));
		const float3 AbsorptionCoeff = max(0.0f, GetSingleLayerWaterMaterialOutput1(MaterialParameters));
		const float PhaseG = clamp(GetSingleLayerWaterMaterialOutput2(MaterialParameters), -1.0f, 1.0f);
		//Sample the optional Material Input ColorScaleBehindWater and fade it out at shorelines to avoid hard edge intersections
		float3 ColorScaleBehindWater = lerp(1.0f, max(0.0f, GetSingleLayerWaterMaterialOutput3(MaterialParameters)), saturate(BehindWaterDeltaDepth * 0.02f));
		
		const float3 ExtinctionCoeff = ScatteringCoeff + AbsorptionCoeff; 
		// Max to avoid division by 0 with the analytical integral below.
		// 1e-5 is high enough to avoid denorms on mobile
		const float3 ExtinctionCoeffSafe = max(ScatteringCoeff + AbsorptionCoeff, 1e-5); 
		
		float DirLightPhaseValue = 0.0f; // Default when Total Internal Reflection happens.
		{
#if SIMPLE_SINGLE_LAYER_WATER
			DirLightPhaseValue = IsotropicPhase();
#else
			float IorFrom = 1.0f; // assumes we come from air
			float IorTo   = DielectricF0ToIor(DielectricSpecularToF0(Specular)); // Wrong if metal is set to >1. But we still keep refraction on the water surface nonetheless.
			const float relativeIOR = IorFrom / IorTo;
			float3 UnderWaterRayDir = 0.0f;
			if (WaterRefract(MaterialParameters.CameraVector, MaterialParameters.WorldNormal, relativeIOR, UnderWaterRayDir))
			{
				DirLightPhaseValue = SchlickPhase(PhaseG, dot(-ResolvedView.DirectionalLightDirection.xyz, UnderWaterRayDir));
			}
#endif
		}

		// We also apply transmittance from light to under water surface. However, the scene has been lit by many sources already.
		// So the transmittance toabove surface is simply approximated using the travel distance from the scene pixel to the water top, assuming a flat water surface.
		// We cannot combine this transmittance with the transmittance from view because this would change the behavior of the analytical integration of light scattering integration.
		const float3 BehindWaterSceneWorldPos = SvPositionToWorld(float4(MaterialParameters.SvPosition.xy, SceneDeviceZ, 1.0));
		const float DistanceFromScenePixelToWaterTop = max(0.0, MaterialParameters.AbsoluteWorldPosition.z - BehindWaterSceneWorldPos.z);
		const float3 MeanTransmittanceToLightSources = exp(-DistanceFromScenePixelToWaterTop * ExtinctionCoeff);

#if SIMPLE_SINGLE_LAYER_WATER
		const float3 BehindWaterSceneLuminance = 0.0f; // Cannot read back the scene color in this case
#else
		// We use the pixel SvPosition instead of the scene one pre refraction/distortion to avoid those extra ALUs.
		float3 BehindWaterSceneLuminance = SceneColorWithoutSingleLayerWaterTexture.SampleLevel(SceneColorWithoutSingleLayerWaterSampler, ViewportUV, 0).rgb;
		BehindWaterSceneLuminance = MeanTransmittanceToLightSources * (USE_PREEXPOSURE ? ResolvedView.OneOverPreExposure : 1.0f) * BehindWaterSceneLuminance;
#endif

		float3 SunScattLuminance = DirLightPhaseValue * SunIlluminance;
		float3 AmbScattLuminance = IsotropicPhase()   * AmbiantIlluminance;

#define VOLUMETRICSHADOW	0
#if !VOLUMETRICSHADOW || SIMPLE_SINGLE_LAYER_WATER

		const float3 OpticalDepth = ExtinctionCoeff * BehindWaterDeltaDepth;
		float3 Transmittance = exp(-OpticalDepth);
		float3 ScatteredLuminance = ScatteringCoeff * (AmbScattLuminance + SunScattLuminance * DirectionalLightShadow);
		ScatteredLuminance = (ScatteredLuminance - ScatteredLuminance * Transmittance) / ExtinctionCoeffSafe;

#else
		// TODO Make the volumetric shadow part work again
		float3 Transmittance = 1.0f;
		float3 ScatteredLuminance = 0.0f;
		const float RayMarchMaxDistance = min(BehindWaterDeltaDepth, 200.0f);  // 20 meters
		const float RayMarchStepSize = RayMarchMaxDistance / 10.0f; // Less samples wil lresult in a bit brighter look due to TransmittanceToLightThroughWater being 1 on a longer first sample. Would need it part of analiytical integration
		const float ShadowDither = RayMarchStepSize * GBufferDither;
		for (float s = 0.0f; s < RayMarchMaxDistance; s += RayMarchStepSize)
		{
			// Only jitter shadow map sampling to not lose energy on first sample
			float Shadow = ComputeDirectionalLightDynamicShadowing(MaterialParameters.AbsoluteWorldPosition - (s + ShadowDither)*MaterialParameters.CameraVector, GBuffer.Depth);

			float3 WP = MaterialParameters.AbsoluteWorldPosition - s * MaterialParameters.CameraVector;
			float WaterHeightAboveSample = max(0.0, MaterialParameters.AbsoluteWorldPosition.z - WP.z);
			float3 TransmittanceToLightThroughWater = 1.0;												// no self shadow, same energy as above analytical solution
			//float3 TransmittanceToLightThroughWater = exp(-ExtinctionCoeff * WaterHeightAboveSample);	// self shadow as transmittance to water level, close to reference, depends a bit on sample count due to first sample being critical for dense medium

			float3 SampleTransmittance = exp(-ExtinctionCoeff * RayMarchStepSize); // Constant
			float3 SS = (ScatteringCoeff * TransmittanceToLightThroughWater * (SunScattLuminance * Shadow + AmbScattLuminance));
			ScatteredLuminance += Transmittance * (SS - SS * SampleTransmittance) / ExtinctionCoeffSafe;
			Transmittance *= SampleTransmittance;
		}

		// The rest of the medium
		const float3 OpticalDepth2 = ExtinctionCoeff * max(0.0, BehindWaterDeltaDepth - RayMarchMaxDistance);
		if (any(OpticalDepth2 > 0.0f))
		{
			float3 Transmittance2 = exp(-OpticalDepth2);
			float3 ScatteredLuminance2 = ScatteringCoeff * (SunScattLuminance + AmbScattLuminance);
			ScatteredLuminance += Transmittance * (ScatteredLuminance2 - ScatteredLuminance2 * Transmittance2) / ExtinctionCoeffSafe;
			Transmittance *= Transmittance2;
		}
#endif

		// Apply Fresnel effect to out-scattering towards the view
		ScatteredLuminance *= CameraIsUnderWater ? 1.0 : (1.0 - EnvBrdf);	// Under water is less visible due to Fresnel effect
		Transmittance *= CameraIsUnderWater ? (1.0 - EnvBrdf) : 1.0;		// Above	"		"		"		"		"

		// Add single in-scattering apply colored transmittance to scene color
		Output.Luminance = WaterVisibility * (ScatteredLuminance + Transmittance * (BehindWaterSceneLuminance* ColorScaleBehindWater));
		Output.WaterToSceneTransmittance = Transmittance;
		Output.WaterToSceneToLightTransmittance = Transmittance * MeanTransmittanceToLightSources;
	}

海洋是不透明的使用SceneColor缓存合成出的透明效果。

GDC2012 神秘海域3演讲

渲染方案

FlowShader

没看懂为什么需要用2张贴图叠加是因为要过度么

4.5.1.1 Flow Map变体《神秘海域3》Flow Map + Displacement 另外Flow Map可以和其他渲染技术结合使用比如《神秘海域3》中的Flow Map + Displacement image

4.5.1.2 Flow Map变体《堡垒之夜》Flow Map + Distance Fields + Normal Maps 以及《堡垒之夜》中的Flow Map + Distance Fields + Normal Maps [GDC 2019, Technical Artist Bootcamp Distance Fields and Shader Simulation Tricks]

4.5.1.3 Flow Map变体《神秘海域4》Flow Map + Wave Particles 或者《神秘海域4》中的Flow Map + Wave Particles[SIGGRAPH 2016, Rendering Rapids in Uncharted 4],都是进阶模拟水体表面流动与起伏效果的不错选择。

Wave System

如果我们能找到一个好的模型,程序化的几何和动画是不错的。 仿真计算成本太高即使在SPU中设计者也很难控制。 Perlin噪音效果在视觉上不是很好往往看起来很人工化 FFT技术很好但是参数很难被艺术家控制和调整。也是很难搞好的

Gerstner waves 简单易于控制效果,但高频细节不够多,只能叠加几组大浪,否则太消耗资源。

FFT Waves 真实,细节丰富。但是美术难以控制效果。

神秘海域3采用4组Gerstner waves+4组波动粒子的方式来实现Wave Vector Displacement。

大浪

大浪采用贝塞尔曲线建模完成 之后再叠加大浪

image

这是整个波系的局部公式。 bspline是一个均匀的、非理性的bspline。我们本可以使用贝塞尔但它需要更多的代码。 grid(u,v)函数返回一个给定坐标u,v的标量值。在这种情况下我们有一个波标的乘数

Sea of Thieves approach [Ang, 2018]

Crest Siggraph2019

Light Scattering

使用了类似盗贼之海的光线散射算法,光线散射项是基于海面置换项的水平长度。这里补充一下:使它在我们的框架中更好地工作--我们通过将置换项除以波长来做一种特殊的归一化,并将这个归一化版本用于光散射项。

基于海平面高度的散射在海洋参数发生改变时容易出问题。

如果将置换项除以波长,就可以针对大波与小波进行缩放。

Shadering

Cascade scale used to scale shading inputs Normals Foam Underwater bubbles Works for range of viewpoints Breaks up patterns Combats mipmapping Increase visible range of detail Doubles texture samples