#define PATCH_UTIL_USE_RW_IRRADIANCE_BUFFER
#include "Common.hlsl"
#include "PathTracing.hlsl"
#include "Estimation.hlsl"
#include "RingBuffer.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/Sampling/PseudoRandom.hlsl"
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Common.hlsl"
struct Sample
{
float3 direction;
};
struct Reservoir
{
Sample sample;
float weightSum;
void Init()
{
sample = (Sample)0;
weightSum = 0.0f;
}
void Update(in Sample newSample, float weight, float u)
{
weightSum += weight;
if (u * weightSum < weight)
sample = newSample;
}
};
StructuredBuffer<uint> _RingConfigBuffer;
RWStructuredBuffer<SphericalHarmonics::RGBL1> _PatchIrradiances;
RWStructuredBuffer<PatchUtil::PatchStatisticsSet> _PatchStatistics;
StructuredBuffer<PatchUtil::PatchGeometry> _PatchGeometries;
RWStructuredBuffer<PatchUtil::PatchCounterSet> _PatchCounterSets;
StructuredBuffer<uint> _CellPatchIndices;
StructuredBuffer<int3> _CascadeOffsets;
RWStructuredBuffer<SphericalHarmonics::ScalarL2> _PatchAccumulatedLuminances;
StructuredBuffer<MaterialPool::MaterialEntry> _MaterialEntries;
Texture2DArray<float4> _AlbedoTextures;
Texture2DArray<float4> _TransmissionTextures;
Texture2DArray<float4> _EmissionTextures;
SamplerState sampler_EmissionTextures;
SamplerState sampler_AlbedoTextures;
SamplerState sampler_TransmissionTextures;
TextureCube<float3> _EnvironmentCubemap;
SamplerState sampler_EnvironmentCubemap;
UNIFIED_RT_DECLARE_ACCEL_STRUCT(_RayTracingAccelerationStructure);
uint _FrameIdx;
uint _GridSize;
uint _CascadeCount;
float _VoxelMinSize;
uint _MultiBounce;
uint _CandidateCount;
float _TargetFunctionUpdateWeight;
uint _RingConfigOffset;
float _ShortHysteresis;
float3 _GridTargetPos;
float _MaterialAtlasTexelSize; // The size of 1 texel in the atlases above
float _AlbedoBoost;
float3 _DirectionalLightDirection;
float3 _DirectionalLightIntensity;
void ProcessAndStoreLuminanceSample(RWStructuredBuffer<SphericalHarmonics::ScalarL2> patchLuminances, uint patchIdx, SphericalHarmonics::ScalarL2 luminanceSample, float updateWeight)
{
const SphericalHarmonics::ScalarL2 oldLuminance = patchLuminances[patchIdx];
SphericalHarmonics::ScalarL2 output = SphericalHarmonics::Lerp(oldLuminance, luminanceSample, updateWeight);
patchLuminances[patchIdx] = output;
}
float TargetFunction(Sample sample, SphericalHarmonics::ScalarL2 accumulatedPatchLuminance)
{
float luminance = SphericalHarmonics::Eval(accumulatedPatchLuminance, sample.direction);
if (luminance < FLT_EPSILON)
{
luminance = 0.05 * accumulatedPatchLuminance.l0 * SphericalHarmonics::y0 + FLT_EPSILON;
}
return luminance;
}
SphericalHarmonics::ScalarL2 EstimateFromLuminanceSample(float luminanceSample, float3 rayDirection)
{
SphericalHarmonics::ScalarL2 estimate;
estimate.l0 = luminanceSample * SphericalHarmonics::y0;
estimate.l1s[0] = luminanceSample * (SphericalHarmonics::y1Constant * rayDirection.y);
estimate.l1s[1] = luminanceSample * (SphericalHarmonics::y1Constant * rayDirection.z);
estimate.l1s[2] = luminanceSample * (SphericalHarmonics::y1Constant * rayDirection.x);
estimate.l2s[0] = luminanceSample * (SphericalHarmonics::y20Constant * rayDirection.x * rayDirection.y);
estimate.l2s[1] = luminanceSample * (SphericalHarmonics::y21Constant * rayDirection.y * rayDirection.z);
estimate.l2s[2] = luminanceSample * (SphericalHarmonics::y22Constant * (3.0f * rayDirection.z * rayDirection.z - 1.0f));
estimate.l2s[3] = luminanceSample * (SphericalHarmonics::y23Constant * rayDirection.x * rayDirection.z);
estimate.l2s[4] = luminanceSample * (0.5 * SphericalHarmonics::y24Constant * (rayDirection.x * rayDirection.x - rayDirection.y * rayDirection.y));
return estimate;
}
SphericalHarmonics::RGBL1 EstimateFromSampleAndWeight(float3 radianceSample, float3 rayDirection, float weight)
{
SphericalHarmonics::RGBL1 estimate;
estimate.l0 = radianceSample * SphericalHarmonics::y0;
estimate.l1s[0] = radianceSample * SphericalHarmonics::y1Constant * rayDirection.y;
estimate.l1s[1] = radianceSample * SphericalHarmonics::y1Constant * rayDirection.z;
estimate.l1s[2] = radianceSample * SphericalHarmonics::y1Constant * rayDirection.x;
SphericalHarmonics::MulMut(estimate, weight);
return estimate;
}
void Estimate(UnifiedRT::DispatchInfo dispatchInfo)
{
uint patchIdx = dispatchInfo.dispatchThreadID.x;
if (RingBuffer::IsPositionUnused(_RingConfigBuffer, _RingConfigOffset, patchIdx))
return;
uint candidateCount = _CandidateCount;
UnifiedRT::RayTracingAccelStruct accelStruct = UNIFIED_RT_GET_ACCEL_STRUCT(_RayTracingAccelerationStructure);
Reservoir reservoir;
reservoir.Init();
QrngPcg4D rng;
rng.Init(uint2(patchIdx, 0), _FrameIdx);
const PatchUtil::PatchGeometry patchGeo = _PatchGeometries[patchIdx];
const SphericalHarmonics::ScalarL2 accumulatedPatchLuminance = _PatchAccumulatedLuminances[patchIdx];
for(uint candidateIdx = 0; candidateIdx < candidateCount; ++candidateIdx)
{
Sample sample;
sample.direction = UniformHemisphereSample(float2(rng.GetFloat(0), rng.GetFloat(1)), patchGeo.normal);
float invCandidateWeight = 2.0f * PI;
float candidateWeight = TargetFunction(sample, accumulatedPatchLuminance) * invCandidateWeight;
reservoir.Update(sample, candidateWeight, rng.GetFloat(2));
rng.NextSample();
}
const float outputWeight = reservoir.weightSum / (TargetFunction(reservoir.sample, accumulatedPatchLuminance) * candidateCount);
UnifiedRT::Ray ray;
ray.direction = reservoir.sample.direction;
ray.origin = OffsetRayOrigin(patchGeo.position, patchGeo.normal);
ray.tMin = 0;
ray.tMax = FLT_MAX;
MaterialPoolParamSet matPoolParams;
matPoolParams.materialEntries = _MaterialEntries;
matPoolParams.albedoTextures = _AlbedoTextures;
matPoolParams.transmissionTextures = _TransmissionTextures;
matPoolParams.emissionTextures = _EmissionTextures;
matPoolParams.emissionSampler = sampler_EmissionTextures;
matPoolParams.albedoSampler = sampler_AlbedoTextures;
matPoolParams.transmissionSampler = sampler_TransmissionTextures;
matPoolParams.atlasTexelSize = _MaterialAtlasTexelSize;
matPoolParams.albedoBoost = _AlbedoBoost;
const float3 radianceSample = SampleIncomingRadianceAssumingLambertianBrdf(
dispatchInfo,
accelStruct,
ray,
matPoolParams,
_DirectionalLightDirection,
_DirectionalLightIntensity,
_MultiBounce,
_EnvironmentCubemap,
sampler_EnvironmentCubemap,
_PatchIrradiances,
_CellPatchIndices,
_GridSize,
_CascadeOffsets,
_GridTargetPos,
_CascadeCount,
_VoxelMinSize);
// If we hit the backface of a water-tight piece of geometry we do nothing. This is to prevent accumulating "false" darkness
// which can give artifacts if a patch reappears after temporarily being inside moving geometry.
// If we hit the backface of geometry which is not water tight, then this most likely a user authoring problem.
if (all(radianceSample != invalidRadianceSample))
{
float luminance = dot(radianceSample, float3(0.2126f, 0.7152f, 0.0722f));
SphericalHarmonics::ScalarL2 luminanceEstimate = EstimateFromLuminanceSample(luminance, reservoir.sample.direction);
ProcessAndStoreLuminanceSample(_PatchAccumulatedLuminances, patchIdx, luminanceEstimate, _TargetFunctionUpdateWeight);
const SphericalHarmonics::RGBL1 estimate = EstimateFromSampleAndWeight(radianceSample, reservoir.sample.direction, outputWeight);
ProcessAndStoreRadianceSample(_PatchIrradiances, _PatchStatistics, _PatchCounterSets, patchIdx, estimate, _ShortHysteresis);
}
}