void PathTracingKernel(
in uint RenderPassIndex,
float3 TranslatedWorldPosition,
float3 ShadingNormal,
inout RandomSequence RandSequence,
inout uint SampleIndex,
inout bool bIsValidSample,
inout float3 RadianceValue,
inout float3 RadianceDirection,
inout float3 DirectLightingIrradiance,
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
inout FL2SHAndCorrection DirectLightingSH,
#else
inout FThreeBandSHVectorRGBFloat DirectLightingSH,
#endif
inout float LuminanceForFirstBounceRayGuiding,
inout float3 SkyLightBentNormal,
inout float4 PrimaryRandSample)
{
uint2 LaunchIndex = uint2(0, 0);
#if USE_IRRADIANCE_CACHING
bool bShouldEmitGeometryHitPoint = false;
FFinalGatherHitPoint IrradianceCacheFinalGatherHitPoint = (FFinalGatherHitPoint)0;
float RadianceProbe_Pdf = 1.0f / (2 * PI);
uint NewEntrySize = 32;
int NearestCacheEntryID = -1;
#endif
float3 Radiance = 0;
// GPULightmass's SampleEmitter(): generate a fake camera ray hitting the texel
FRayDesc Ray;
Ray.Origin = TranslatedWorldPosition + ShadingNormal;
Ray.Direction = -ShadingNormal;
Ray.TMin = -0.01f;
Ray.TMax = 1.01f;
// This array will hold a CDF for light picking
// Seed the array with a uniform CDF at first so that we always have a valid CDF
float LightPickingCdf[RAY_TRACING_LIGHT_COUNT_MAXIMUM];
Ray.Direction = normalize(Ray.Direction);
// path state variables (these cary information between bounces)
float3 PathThroughput = 1.0;
float PathRoughness = 0;
// number of directly visible lights for the first bounce
uint NumVisibleLights = SceneVisibleLightCount;
for (int Bounce = 0; Bounce <= MaxBounces; Bounce++)
{
const bool bIsCameraRay = Bounce == 0;
const bool bIsLastBounce = Bounce == MaxBounces;
const bool bIncludeEmissive = (EnableDirectLighting != 0 || Bounce > 1) &&
(EnableEmissive != 0 || bIsCameraRay);
FPathTracingPayload Payload = (FPathTracingPayload)0;
if (bIsCameraRay)
{
// GPULightmass fakes a 'camera' ray that hits the lightmap texel perpendicularly
Payload.TranslatedWorldPos = TranslatedWorldPosition;
Payload.WorldNormal = ShadingNormal;
Payload.WorldGeoNormal = ShadingNormal;
Payload.WorldSmoothNormal = ShadingNormal;
Payload.Radiance = float3(0, 0, 0);
Payload.BaseColor = float3(1, 1, 1);
Payload.DiffuseColor = float3(1, 1, 1);
Payload.SpecularColor = float3(0, 0, 0);
Payload.Specular = 0;
Payload.Roughness = 1;
Payload.Metallic = 0;
Payload.Ior = 1;
Payload.CustomData = float4(0, 0, 0, 0);
Payload.Opacity = 1;
Payload.ShadingModelID = SHADINGMODELID_DEFAULT_LIT;
Payload.BlendingMode = RAY_TRACING_BLEND_MODE_OPAQUE;
Payload.PrimitiveLightingChannelMask = 0b111;
Payload.HitT = 1.0f;
Payload.SetFrontFace();
}
else
{
Payload = TraceTransparentRay(Ray, false , bIsLastBounce, bIncludeEmissive, LaunchIndex, NumVisibleLights, RandSequence, PathThroughput, Radiance);
}
// As soon as the path is blurry enough, we can get away with diffuse sampling only
const bool bSimplifySSS = PathRoughness >= 0.15;
FLightLoopCount LightLoopCount = LightGridLookup(Payload.TranslatedWorldPos);
// Choose a random number for both Light sampling and BxDF sampling
float4 RandSample = RandomSequence_GenerateSample4D(RandSequence);
// Does this material require NEE? (will be false if MaterialPdf is always +inf)
const bool bIsNeeValid = IsNeeValidMaterial(Payload);
const bool bDoLightLoop = EnableDirectLighting != 0 || Bounce > 0;
float LightPickingCdfSum = 0;
// If we are using Light sampling and the material can use it ...
if (MISMode != 0 && bIsNeeValid && SceneLightCount > 0 && bDoLightLoop)
{
// Choose a light and sample it
float3 TranslatedWorldPos = Payload.TranslatedWorldPos;
float3 WorldNormal = Payload.WorldNormal;
uint PrimitiveLightingChannelMask = Payload.PrimitiveLightingChannelMask;
bool IsTransmissiveMaterial = ENABLE_TRANSMISSION && Payload.IsMaterialTransmissive();
for (uint Index = 0, Num = LightLoopCount.NumLights; Index < Num; ++Index)
{
uint LightIndex = GetLightId(Index, LightLoopCount);
LightPickingCdfSum += EstimateLight(LightIndex, TranslatedWorldPos, WorldNormal, PrimitiveLightingChannelMask, IsTransmissiveMaterial);
LightPickingCdf[Index] = LightPickingCdfSum;
}
if (LightPickingCdfSum > 0)
{
// init worked
int LightId;
float LightPickPdf = 0;
SelectLight(RandSample.x * LightPickingCdfSum, LightLoopCount.NumLights, LightPickingCdf, LightId, LightPickPdf);
LightId = GetLightId(LightId, LightLoopCount);
FLightSample LightSample = SampleLight(LightId, RandSample.yz, TranslatedWorldPos, WorldNormal);
LightPickPdf /= LightPickingCdfSum;
LightSample.RadianceOverPdf /= LightPickPdf;
LightSample.Pdf *= LightPickPdf;
if (LightSample.Pdf > 0)
{
float3 Visibility = float3(1, 1, 1);
if (CastsShadow(LightId))
{
// for transmissive materials, bias the position to the other side of the surface if the light is coming from behind
const float SignedPositionBias = IsTransmissiveMaterial ? sign(dot(Payload.WorldNormal, LightSample.Direction)) : 1.0;
FRayDesc LightRay;
LightRay.Origin = TranslatedWorldPos;
LightRay.TMin = 0;
LightRay.Direction = LightSample.Direction;
LightRay.TMax = LightSample.Distance;
ApplyRayBias(LightRay, Payload.HitT, SignedPositionBias * Payload.WorldGeoNormal);
float AvgRoughness = ApproximateCaustics ? GetAverageRoughness(Payload) : 0.0;
Visibility = TraceTransparentVisibilityRay(LightRay, LaunchIndex, AvgRoughness);
LightSample.RadianceOverPdf *= Visibility;
}
// #dxr_todo: Is it cheaper to fire the ray first? Or eval the material first?
if (any(LightSample.RadianceOverPdf > 0))
{
// Evaluate material
FMaterialEval MaterialEval = EvalMaterial(Ray.Direction, LightSample.Direction, Payload, Bounce == 0);
// Record the contribution
float3 LightContrib = PathThroughput * LightSample.RadianceOverPdf * MaterialEval.Weight * MaterialEval.Pdf;
float3 BentNormalVector = LightSample.Direction / LightSample.Pdf / PI * Visibility;
if (MISMode == 2)
{
LightContrib *= MISWeightRobust(LightSample.Pdf, MaterialEval.Pdf);
BentNormalVector *= MISWeightRobust(LightSample.Pdf, MaterialEval.Pdf);
}
// Record the contribution
if (Bounce > 0)
{
AccumulateRadiance(Radiance, LightContrib);
LuminanceForFirstBounceRayGuiding += Luminance(LightContrib);
}
else
{
// GPU Lightmass records contribution of direct lighting separately, and only for static lights
if (!IsStationary(LightId))
{
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
float TangentZ = saturate(dot(LightSample.Direction, Payload.WorldNormal));
DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), LightSample.Direction, TangentZ);
DirectLightingIrradiance += LightContrib * TangentZ;
#else
DirectLightingSH.R.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.r;
DirectLightingSH.R.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.r;
DirectLightingSH.R.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.r;
DirectLightingSH.G.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.g;
DirectLightingSH.G.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.g;
DirectLightingSH.G.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.g;
DirectLightingSH.B.V0 = SHBasisFunction3Float(LightSample.Direction).V0 * LightContrib.b;
DirectLightingSH.B.V1 = SHBasisFunction3Float(LightSample.Direction).V1 * LightContrib.b;
DirectLightingSH.B.V2 = SHBasisFunction3Float(LightSample.Direction).V2 * LightContrib.b;
#endif
}
if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId))
{
SkyLightBentNormal += BentNormalVector;
}
}
}
}
}
}
// Sample material
FMaterialSample MaterialSample = SampleMaterial(Ray.Direction, Payload, RandSample, Bounce == 0);
float3 NextPathThroughput = PathThroughput * MaterialSample.Weight;
float ContinuationProb = sqrt(saturate(max(NextPathThroughput.x, max(NextPathThroughput.y, NextPathThroughput.z)) / max(PathThroughput.x, max(PathThroughput.y, PathThroughput.z))));
if (ContinuationProb < 1)
{
// If there is some chance we should terminate the ray, draw an extra random value
float RussianRouletteRand = RandSample.w; // SampleMaterial does not use this value at the moment
//RussianRouletteRand = RandomSequence_GenerateSample1D(RandSequence);
if (RussianRouletteRand >= ContinuationProb)
{
// stochastically terminate the path
break;
}
PathThroughput = NextPathThroughput / ContinuationProb;
}
else
{
PathThroughput = NextPathThroughput;
}
// Update ray according to material sample
Ray.Origin = Payload.TranslatedWorldPos;
Ray.Direction = MaterialSample.Direction;
Ray.TMin = 0;
Ray.TMax = POSITIVE_INFINITY;
if (Bounce == 0)
{
RadianceDirection = Ray.Direction;
PrimaryRandSample = RandSample;
}
// If we are using Material sampling for lights
if (MISMode != 1 && bDoLightLoop)
{
// Check which lights can be seen by the material ray and trace a dedicated shadow ray
// While it would be possible to just loop around and use the indirect ray to do this, it would prevent the application
// of shadow ray specific logic for transparent shadows or various per light tricks like shadow casting
const bool bUseMIS = MISMode == 2 && LightPickingCdfSum > 0;
for (uint Index = 0, Num = LightLoopCount.NumMISLights; Index < Num; ++Index)
{
uint LightId = GetLightId(Index, LightLoopCount);
if ((Payload.PrimitiveLightingChannelMask & GetLightingChannelMask(LightId)) == 0)
{
// light does not affect the current ray
continue;
}
FLightHit LightResult = TraceLight(Ray, LightId);
if (LightResult.IsMiss())
{
continue;
}
float3 LightContrib = PathThroughput * LightResult.Radiance;
float3 BentNormalVector = PathThroughput * MaterialSample.Direction / PI;
if (bUseMIS)
{
float PreviousCdfValue = 0.0;
BRANCH if (Index > 0)
{
PreviousCdfValue = LightPickingCdf[Index - 1];
}
float LightPickPdf = (LightPickingCdf[Index] - PreviousCdfValue) / LightPickingCdfSum;
LightContrib *= MISWeightRobust(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf);
// Separate direct lighting (bounce 0) for GPU Lightmass
BentNormalVector *= MISWeightRobust(MaterialSample.Pdf, LightResult.Pdf * LightPickPdf);
}
if (any(LightContrib > 0))
{
if (CastsShadow(LightId))
{
FRayDesc LightRay = Ray;
LightRay.TMax = LightResult.HitT;
float3 Visibility = TraceTransparentVisibilityRay(LightRay, LaunchIndex, PathRoughness);
LightContrib *= Visibility;
// Separate direct lighting (bounce 0) for GPU Lightmass
BentNormalVector *= Visibility;
}
if (Bounce > 0)
{
// the light made some contribution, and there was nothing along the shadow ray
AccumulateRadiance(Radiance, LightContrib);
}
else // Bounce == 0
{
if (!IsStationary(LightId))
{
#ifdef LIGHTMAP_PATH_TRACING_MAIN_RG
float TangentZ = saturate(dot(MaterialSample.Direction, Payload.WorldNormal));
DirectLightingSH.AddIncomingRadiance(Luminance(LightContrib), MaterialSample.Direction, TangentZ);
DirectLightingIrradiance += LightContrib * TangentZ;
#else
DirectLightingSH.R.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.r;
DirectLightingSH.R.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.r;
DirectLightingSH.R.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.r;
DirectLightingSH.G.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.g;
DirectLightingSH.G.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.g;
DirectLightingSH.G.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.g;
DirectLightingSH.B.V0 = SHBasisFunction3Float(MaterialSample.Direction).V0 * LightContrib.b;
DirectLightingSH.B.V1 = SHBasisFunction3Float(MaterialSample.Direction).V1 * LightContrib.b;
DirectLightingSH.B.V2 = SHBasisFunction3Float(MaterialSample.Direction).V2 * LightContrib.b;
#endif
}
if (IsStationary(LightId) && IsEnvironmentLight(LightId) && CastsShadow(LightId))
{
SkyLightBentNormal += BentNormalVector;
}
}
LuminanceForFirstBounceRayGuiding += Luminance(LightContrib);
}
}
}
// from this point on, we don't need to include lights in the trace call
// because NEE handled it for us
NumVisibleLights = 0;
}
RadianceValue = Radiance;
}