diff --git a/com.unity.render-pipelines.high-definition/CHANGELOG.md b/com.unity.render-pipelines.high-definition/CHANGELOG.md index a77a4636782..6e7a1f0188c 100644 --- a/com.unity.render-pipelines.high-definition/CHANGELOG.md +++ b/com.unity.render-pipelines.high-definition/CHANGELOG.md @@ -50,12 +50,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Fixed double contribution from the clear coat when having SSR or RTR on the Lit and StackLit shaders (case 1352424). - Fixed texture fields for volume parameters accepting textures with wrong dimensions. - Fixed Realtime lightmap not working correctly in player with various lit shader (case 1360021) +- Fixed unexpectedly strong contribution from directional lights in path-traced volumetric scattering (case 1304688). ### Changed - Visual Environment ambient mode is now Dynamic by default. - Surface ReflectionTypeLoadExceptions in HDUtils.GetRenderPipelineMaterialList(). Without surfacing these exceptions, developers cannot act on any underlying reflection errors in the HDRP assembly. - Improved the DynamicArray class by adding several utility APIs. - Moved AMD FidelityFX shaders to core +- Improved sampling of overlapping point/area lights in path-traced volumetric scattering (case 1358777). +- Path-traced volumetric scattering now takes fog color into account, adding scattered contribution on top of the non-scattered result (cases 1346105, 1358783). ## [12.0.0] - 2021-01-11 diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingLight.hlsl b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingLight.hlsl index 64009558e37..faa8e3e75bf 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingLight.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingLight.hlsl @@ -109,7 +109,7 @@ bool IsDistantLightActive(DirectionalLightData lightData, float3 normal) return dot(normal, lightData.forward) <= sin(lightData.angularDiameter * 0.5); } -LightList CreateLightList(float3 position, float3 normal, uint lightLayers = DEFAULT_LIGHT_LAYERS, bool withLocal = true, bool withDistant = true) +LightList CreateLightList(float3 position, float3 normal, uint lightLayers = DEFAULT_LIGHT_LAYERS, bool withLocal = true, bool withDistant = true, float3 lightPosition = FLT_MAX) { LightList list; uint i; @@ -118,53 +118,62 @@ LightList CreateLightList(float3 position, float3 normal, uint lightLayers = DEF list.localCount = 0; list.localPointCount = 0; -if (withLocal) -{ - uint localPointCount, localCount; + if (withLocal) + { + uint localPointCount, localCount; #ifdef USE_LIGHT_CLUSTER - if (PointInsideCluster(position)) - { - list.cellIndex = GetClusterCellIndex(position); - localPointCount = GetPunctualLightClusterCellCount(list.cellIndex); - localCount = GetAreaLightClusterCellCount(list.cellIndex); - } - else - { - localPointCount = 0; - localCount = 0; - } + if (PointInsideCluster(position)) + { + list.cellIndex = GetClusterCellIndex(position); + localPointCount = GetPunctualLightClusterCellCount(list.cellIndex); + localCount = GetAreaLightClusterCellCount(list.cellIndex); + } + else + { + localPointCount = 0; + localCount = 0; + } #else - localPointCount = _PunctualLightCountRT; - localCount = _PunctualLightCountRT + _AreaLightCountRT; + localPointCount = _PunctualLightCountRT; + localCount = _PunctualLightCountRT + _AreaLightCountRT; #endif - // First point lights (including spot lights) - for (i = 0; i < localPointCount && list.localPointCount < MAX_LOCAL_LIGHT_COUNT; i++) - { + // Do we have an imposed local light (identificed by position), for volumetric scattering? + bool forceLightPosition = (lightPosition.x != FLT_MAX); + + // First point lights (including spot lights) + for (i = 0; i < localPointCount && list.localPointCount < MAX_LOCAL_LIGHT_COUNT; i++) + { #ifdef USE_LIGHT_CLUSTER - const LightData lightData = FetchClusterLightIndex(list.cellIndex, i); + const LightData lightData = FetchClusterLightIndex(list.cellIndex, i); #else - const LightData lightData = _LightDatasRT[i]; + const LightData lightData = _LightDatasRT[i]; #endif - if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsPointLightActive(lightData, position, normal)) - list.localIndex[list.localPointCount++] = i; - } + if (forceLightPosition && any(lightPosition - lightData.positionRWS)) + continue; - // Then rect area lights - for (list.localCount = list.localPointCount; i < localCount && list.localCount < MAX_LOCAL_LIGHT_COUNT; i++) - { + if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsPointLightActive(lightData, position, normal)) + list.localIndex[list.localPointCount++] = i; + } + + // Then rect area lights + for (list.localCount = list.localPointCount; i < localCount && list.localCount < MAX_LOCAL_LIGHT_COUNT; i++) + { #ifdef USE_LIGHT_CLUSTER - const LightData lightData = FetchClusterLightIndex(list.cellIndex, i); + const LightData lightData = FetchClusterLightIndex(list.cellIndex, i); #else - const LightData lightData = _LightDatasRT[i]; + const LightData lightData = _LightDatasRT[i]; #endif - if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsRectAreaLightActive(lightData, position, normal)) - list.localIndex[list.localCount++] = i; + if (forceLightPosition && any(lightPosition - lightData.positionRWS)) + continue; + + if (IsMatchingLightLayer(lightData.lightLayers, lightLayers) && IsRectAreaLightActive(lightData, position, normal)) + list.localIndex[list.localCount++] = i; + } } -} // Then filter the active distant lights (directional) list.distantCount = 0; @@ -673,12 +682,62 @@ bool GetPointLightInterval(LightData lightData, float3 rayOrigin, float3 rayDire return tMin < tMax; } -float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tMin, out float tMax) +// This function has been deprecated in favor of PickLocalLightInterval() right below +// float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tMin, out float tMax) +// { +// tMin = FLT_MAX; +// tMax = 0.0; + +// float tLightMin, tLightMax; + +// // First process point lights +// uint i = 0, n = _PunctualLightCountRT, localCount = 0; +// for (; i < n; i++) +// { +// if (GetPointLightInterval(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax)) +// { +// tMin = min(tMin, tLightMin); +// tMax = max(tMax, tLightMax); +// localCount++; +// } +// } + +// // Then area lights +// n += _AreaLightCountRT; +// for (; i < n; i++) +// { +// if (GetRectAreaLightInterval(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax)) +// { +// tMin = min(tMin, tLightMin); +// tMax = max(tMax, tLightMax); +// localCount++; +// } +// } + +// uint lightCount = localCount + _DirectionalLightCount; + +// return lightCount ? float(localCount) / lightCount : -1.0; +// } + +float GetLocalLightWeight(LightData lightData, float3 rayOrigin, float3 rayDirection, float tMin, float tMax) +{ + float tDist = clamp(dot(lightData.positionRWS - rayOrigin, rayDirection), tMin, tMax); + float3 vDist = rayOrigin + tDist * rayDirection - lightData.positionRWS; + + // By offsetting the square distance by 1.0, we reduce the range of the weight to ]0.0, 1.0], + // while avoiding a singularity when distance goes towards 0.0. + float distSq = 1.0 + Length2(vDist); + + return rcp(distSq); +} + +float PickLocalLightInterval(float3 rayOrigin, float3 rayDirection, inout float inputSample, out float3 lightPosition, out float lightWeight, out float tMin, out float tMax) { tMin = FLT_MAX; tMax = 0.0; float tLightMin, tLightMax; + float wLight, wSum = 0.0; // First process point lights uint i = 0, n = _PunctualLightCountRT, localCount = 0; @@ -686,9 +745,31 @@ float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tM { if (GetPointLightInterval(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax)) { - tMin = min(tMin, tLightMin); - tMax = max(tMax, tLightMax); - localCount++; + wLight = GetLocalLightWeight(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax); + + if (wLight > 0.0) + { + wSum += wLight; + wLight /= wSum; + + if (inputSample < wLight) + { + lightPosition = _LightDatasRT[i].positionRWS; + lightWeight = wLight; + tMin = tLightMin; + tMax = tLightMax; + + inputSample /= wLight; + } + else + { + lightWeight *= 1.0 - wLight; + + inputSample = (inputSample - wLight) / (1.0 - wLight); + } + + localCount++; + } } } @@ -698,9 +779,31 @@ float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tM { if (GetRectAreaLightInterval(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax)) { - tMin = min(tMin, tLightMin); - tMax = max(tMax, tLightMax); - localCount++; + wLight = GetLocalLightWeight(_LightDatasRT[i], rayOrigin, rayDirection, tLightMin, tLightMax); + + if (wLight > 0.0) + { + wSum += wLight; + wLight /= wSum; + + if (inputSample < wLight) + { + lightPosition = _LightDatasRT[i].positionRWS; + lightWeight = wLight; + tMin = tLightMin; + tMax = tLightMax; + + inputSample /= wLight; + } + else + { + lightWeight *= 1.0 - wLight; + + inputSample = (inputSample - wLight) / (1.0 - wLight); + } + + localCount++; + } } } @@ -709,9 +812,9 @@ float GetLocalLightsInterval(float3 rayOrigin, float3 rayDirection, out float tM return lightCount ? float(localCount) / lightCount : -1.0; } -LightList CreateLightList(float3 position, bool sampleLocalLights) +LightList CreateLightList(float3 position, bool sampleLocalLights, float3 lightPosition = FLT_MAX) { - return CreateLightList(position, 0.0, DEFAULT_LIGHT_LAYERS, sampleLocalLights, !sampleLocalLights); + return CreateLightList(position, 0.0, DEFAULT_LIGHT_LAYERS, sampleLocalLights, !sampleLocalLights, lightPosition); } #endif // UNITY_PATH_TRACING_LIGHT_INCLUDED diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingMain.raytrace b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingMain.raytrace index acc8fd1423e..fa80ff8edb0 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingMain.raytrace +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingMain.raytrace @@ -46,7 +46,7 @@ void MissCamera(inout PathIntersection pathIntersection : SV_RayPayload) if (_EnableVolumetricFog && _RaytracingMinRecursion <= 1) { - float3 envValue = pathIntersection.value; + float3 lightPosition, envValue = pathIntersection.value; // Generate a 4D unit-square sample for this depth, from our QMC sequence float4 inputSample = GetSample4D(pathIntersection.pixelCoord, _RaytracingSampleIndex, 0); @@ -56,15 +56,15 @@ void MissCamera(inout PathIntersection pathIntersection : SV_RayPayload) pathIntersection.t = FLT_MAX; float pdf = 1.0; bool sampleLocalLights; - if (SampleVolumeScatteringPosition(inputSample.w, pathIntersection.t, pdf, sampleLocalLights)) + if (SampleVolumeScatteringPosition(pathIntersection.pixelCoord, inputSample.w, pathIntersection.t, pdf, sampleLocalLights, lightPosition)) { - ComputeVolumeScattering(pathIntersection, inputSample.xyz, sampleLocalLights); + ComputeVolumeScattering(pathIntersection, inputSample.xyz, sampleLocalLights, lightPosition); // Apply the pdf pathIntersection.value /= pdf; // Apply volumetric attenuation - ApplyFogAttenuation(WorldRayOrigin(), WorldRayDirection(), pathIntersection.t, pathIntersection.value); + ApplyFogAttenuation(WorldRayOrigin(), WorldRayDirection(), pathIntersection.t, pathIntersection.value, false); } // Reinject the environment value diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingVolume.hlsl b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingVolume.hlsl index cba0bea2484..f52a62edf15 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingVolume.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/PathTracing/Shaders/PathTracingVolume.hlsl @@ -10,7 +10,7 @@ float ComputeHeightFogMultiplier(float height) return ComputeHeightFogMultiplier(height, _HeightFogBaseHeight, _HeightFogExponents); } -bool SampleVolumeScatteringPosition(inout float theSample, inout float t, inout float pdf, out bool sampleLocalLights) +bool SampleVolumeScatteringPosition(uint2 pixelCoord, inout float inputSample, inout float t, inout float pdf, out bool sampleLocalLights, out float3 lightPosition) { sampleLocalLights = false; @@ -23,28 +23,30 @@ bool SampleVolumeScatteringPosition(inout float theSample, inout float t, inout float tFog = min(t, _MaxFogDistance); #ifdef HAS_LIGHTLOOP - float localWeight = GetLocalLightsInterval(WorldRayOrigin(), WorldRayDirection(), tMin, tMax); + + float pickedLightWeight; + float localWeight = PickLocalLightInterval(WorldRayOrigin(), WorldRayDirection(), inputSample, lightPosition, pickedLightWeight, tMin, tMax); if (localWeight < 0.0) return false; - sampleLocalLights = theSample < localWeight; + sampleLocalLights = inputSample < localWeight; if (sampleLocalLights) { tMax = min(tMax, tFog); if (tMin >= tMax) return false; - theSample /= localWeight; - pdfVol *= localWeight; + inputSample /= localWeight; + pdfVol *= localWeight * pickedLightWeight; } else { tMin = 0.0; tMax = tFog; - theSample -= localWeight; - theSample /= 1.0 - localWeight; + inputSample -= localWeight; + inputSample /= 1.0 - localWeight; pdfVol *= 1.0 - localWeight; } #else @@ -54,14 +56,15 @@ bool SampleVolumeScatteringPosition(inout float theSample, inout float t, inout // FIXME: not quite sure what the sigmaT value is supposed to be... const float sigmaT = _HeightFogBaseExtinction; + const float transmittanceTMin = max(exp(-tMin * sigmaT), 0.01); const float transmittanceTMax = max(exp(-tMax * sigmaT), 0.01); const float transmittanceThreshold = t < FLT_MAX ? 1.0 - min(0.5, transmittanceTMax) : 1.0; - if (theSample >= transmittanceThreshold) + if (inputSample >= transmittanceThreshold) { // Re-scale the sample - theSample -= transmittanceThreshold; - theSample /= 1.0 - transmittanceThreshold; + inputSample -= transmittanceThreshold; + inputSample /= 1.0 - transmittanceThreshold; // Adjust the pdf pdf *= 1.0 - transmittanceThreshold; @@ -70,35 +73,23 @@ bool SampleVolumeScatteringPosition(inout float theSample, inout float t, inout } // Re-scale the sample - theSample /= transmittanceThreshold; + inputSample /= transmittanceThreshold; // Adjust the pdf pdf *= pdfVol * transmittanceThreshold; - if (sampleLocalLights) - { - // Linear sampling - float deltaT = tMax - tMin; - t = tMin + theSample * deltaT; + // Exponential sampling + float transmittance = transmittanceTMax + inputSample * (transmittanceTMin - transmittanceTMax); + t = -log(transmittance) / sigmaT; - // Adjust the pdf - pdf /= deltaT; - } - else - { - // Exponential sampling - float transmittance = transmittanceTMax + theSample * (1.0 - transmittanceTMax); - t = -log(transmittance) / sigmaT; - - // Adjust the pdf - pdf *= sigmaT * transmittance; - } + // Adjust the pdf + pdf *= sigmaT * transmittance / (transmittanceTMin - transmittanceTMax); return true; } // Function responsible for volume scattering -void ComputeVolumeScattering(inout PathIntersection pathIntersection : SV_RayPayload, float3 inputSample, bool sampleLocalLights) +void ComputeVolumeScattering(inout PathIntersection pathIntersection : SV_RayPayload, float3 inputSample, bool sampleLocalLights, float3 lightPosition) { // Reset the ray intersection color, which will store our final result pathIntersection.value = 0.0; @@ -114,8 +105,8 @@ void ComputeVolumeScattering(inout PathIntersection pathIntersection : SV_RayPay // Compute the scattering position float3 scatteringPosition = WorldRayOrigin() + pathIntersection.t * WorldRayDirection(); - // Create the list of active lights - LightList lightList = CreateLightList(scatteringPosition, sampleLocalLights); + // Create the list of active lights (a local light can be forced by providing its position) + LightList lightList = CreateLightList(scatteringPosition, sampleLocalLights, lightPosition); // Bunch of variables common to material and light sampling float pdf; diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Common/AtmosphericScatteringRayTracing.hlsl b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Common/AtmosphericScatteringRayTracing.hlsl index 7b47928c9c0..695664cc719 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Common/AtmosphericScatteringRayTracing.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/Raytracing/Shaders/Common/AtmosphericScatteringRayTracing.hlsl @@ -11,10 +11,7 @@ void ApplyFogAttenuation(float3 origin, float3 direction, float t, inout float3 float absFogBaseHeight = _HeightFogBaseHeight; float fogTransmittance = TransmittanceHeightFog(_HeightFogBaseExtinction, absFogBaseHeight, _HeightFogExponents, direction.y, origin.y, dist); - // This is designed to match the raster volumes... even though I'm not sure why it's working that way - float3 fogColor = useFogColor && !_EnableVolumetricFog ? - GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction : - 0.0; + float3 fogColor = useFogColor? GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction : 0.0; value = lerp(fogColor, value, fogTransmittance); } } @@ -27,10 +24,7 @@ void ApplyFogAttenuation(float3 origin, float3 direction, inout float3 value) float absFogBaseHeight = _HeightFogBaseHeight; float fogTransmittance = TransmittanceHeightFog(_HeightFogBaseExtinction, absFogBaseHeight, _HeightFogExponents, direction.y, origin.y, dist); - // This is designed to match the raster volumes... even though I'm not sure why it's working that way - float3 fogColor = !_EnableVolumetricFog ? - GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction : - 0.0; + float3 fogColor = GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction; value = lerp(fogColor, value, fogTransmittance); } } @@ -43,10 +37,7 @@ void ApplyFogAttenuation(float3 origin, float3 direction, inout float3 value, in float absFogBaseHeight = _HeightFogBaseHeight; float fogTransmittance = TransmittanceHeightFog(_HeightFogBaseExtinction, absFogBaseHeight, _HeightFogExponents, direction.y, origin.y, dist); - // This is designed to match the raster volumes... even though I'm not sure why it's working that way - float3 fogColor = !_EnableVolumetricFog ? - GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction : - 0.0; + float3 fogColor = GetFogColor(-direction, dist) * _HeightFogBaseScattering.xyz / _HeightFogBaseExtinction; value = lerp(fogColor, value, fogTransmittance); alpha = saturate(1.0 - fogTransmittance); } diff --git a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassPathTracing.hlsl b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassPathTracing.hlsl index e6cfe902429..8504ae40bfb 100644 --- a/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassPathTracing.hlsl +++ b/com.unity.render-pipelines.high-definition/Runtime/RenderPipeline/ShaderPass/ShaderPassPathTracing.hlsl @@ -294,6 +294,7 @@ void ClosestHit(inout PathIntersection pathIntersection : SV_RayPayload, Attribu #ifdef HAS_LIGHTLOOP + float3 lightPosition; float pdf = 1.0; bool sampleLocalLights, sampleVolume = false; @@ -304,11 +305,11 @@ void ClosestHit(inout PathIntersection pathIntersection : SV_RayPayload, Attribu // For the time being, we test for volumetric scattering only on camera rays if (!currentDepth) - sampleVolume = SampleVolumeScatteringPosition(inputSample.w, pathIntersection.t, pdf, sampleLocalLights); + sampleVolume = SampleVolumeScatteringPosition(pathIntersection.pixelCoord, inputSample.w, pathIntersection.t, pdf, sampleLocalLights, lightPosition); } if (sampleVolume) - ComputeVolumeScattering(pathIntersection, inputSample.xyz, sampleLocalLights); + ComputeVolumeScattering(pathIntersection, inputSample.xyz, sampleLocalLights, lightPosition); else ComputeSurfaceScattering(pathIntersection, attributeData, inputSample);