Files
UnrealEngine/Engine/Shaders/Private/PathTracing/Light/PathTracingLightCommon.ush
2025-05-18 13:04:45 +08:00

380 lines
9.4 KiB
HLSL

// Copyright Epic Games, Inc. All Rights Reserved.
/*=============================================================================================
LightCommon.usf: Common utility functions for light sampling
===============================================================================================*/
#pragma once
#include "/Engine/Shared/RayTracingTypes.h"
#include "/Engine/Shared/PathTracingDefinitions.h"
uint SceneLightCount;
StructuredBuffer<FPathTracingLight> SceneLights;
struct FLightHit
{
float3 Radiance;
float Pdf;
float HitT;
bool IsMiss() { return HitT <= 0; }
bool IsHit() { return HitT > 0; }
};
struct FLightSample
{
float3 RadianceOverPdf;
float Pdf;
float3 Direction;
float Distance;
};
struct FVolumeLightSampleSetup
{
float VolumeTMin; // range of 't' values along the ray that overlap the light (and the volume)
float VolumeTMax;
float LightImportance; // estimate of the light radiance for this ray (could be 0 if light does not support equi-angular sampling)
// Equi-Angular case
float T0; // distance from ray origin to projected light center
float Height; // distance from light center to ray (when negative, use uniform sampler)
float ThetaMin; // angle to start of ray interval
float ThetaMax; // angle to end of ray interval
float K; // normalization constant for the pdf
bool IsValid() { return VolumeTMin < VolumeTMax; } // NOTE: we assume the range is a subset of the ray's TMin/TMax
float2 SampleDistance(float RandSample)
{
if (Height > 0)
{
// equi-angular pdf
const float TanTheta = tan(lerp(ThetaMin, ThetaMax, RandSample));
const float T = Height * TanTheta + T0;
const float PdfLocal = rcp((1 + TanTheta * TanTheta) * K);
return float2(T, PdfLocal);
}
else
{
// uniform pdf
const float T = lerp(VolumeTMin, VolumeTMax, RandSample);
const float PdfLocal = rcp(K);
return float2(T, PdfLocal);
}
}
float Pdf(float T)
{
if (T > VolumeTMin && T < VolumeTMax)
{
if (Height > 0)
{
const float TanTheta = (T - T0) / Height;
return rcp((1 + TanTheta * TanTheta) * K);
}
else
{
return rcp(K);
}
}
return 0.0;
}
};
FVolumeLightSampleSetup CreateEquiAngularSampler(float LightImportance, float3 Center, float3 Ro, float3 Rd, float TMin, float TMax)
{
const float3 V = Center - Ro;
FVolumeLightSampleSetup Result = (FVolumeLightSampleSetup)0;
Result.VolumeTMin = TMin;
Result.VolumeTMax = TMax;
Result.T0 = dot(V, Rd);
Result.Height = length(cross(V, Rd));
// TODO: handle Height == 0 case better, the pdf becomes degenerate in this case
// but unless the light is parented to the camera exactly, it is hard to see in practice
const float RcpHeight = Result.Height > 0 ? rcp(Result.Height) : 0.0;
Result.ThetaMin = atan((TMin - Result.T0) * RcpHeight);
Result.ThetaMax = atan((TMax - Result.T0) * RcpHeight);
Result.K = (Result.ThetaMax - Result.ThetaMin) * Result.Height;
Result.LightImportance = LightImportance * (Result.ThetaMax - Result.ThetaMin) * RcpHeight;
return Result;
}
FVolumeLightSampleSetup CreateUniformSampler(float LightImportance, float TMin, float TMax)
{
FVolumeLightSampleSetup Result = (FVolumeLightSampleSetup)0;
Result.VolumeTMin = TMin;
Result.VolumeTMax = TMax;
Result.LightImportance = LightImportance * (TMax - TMin);
Result.Height = -1.0; // mark that we don't want equi-angular sampling here
Result.K = TMax - TMin;
return Result;
}
FLightHit NullLightHit()
{
return (FLightHit)0;
}
FLightHit CreateLightHit(float3 Radiance, float Pdf, float HitT)
{
FLightHit Result;
Result.Radiance = Radiance;
Result.Pdf = Pdf;
Result.HitT = HitT;
return Result;
}
FLightSample NullLightSample()
{
return (FLightSample)0;
}
FLightSample CreateLightSample(float3 RadianceOverPdf, float Pdf, float3 Direction, float Distance)
{
FLightSample Sample;
Sample.RadianceOverPdf = RadianceOverPdf;
Sample.Pdf = Pdf;
Sample.Direction = Direction;
Sample.Distance = Distance;
return Sample;
}
FVolumeLightSampleSetup NullVolumeLightSetup()
{
return (FVolumeLightSampleSetup)0;
}
bool IsEnvironmentLight(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TYPE_MASK) == PATHTRACING_LIGHT_SKY;
}
bool IsPointLight(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TYPE_MASK) == PATHTRACING_LIGHT_POINT;
}
bool IsDirectionalLight(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TYPE_MASK) == PATHTRACING_LIGHT_DIRECTIONAL;
}
bool IsRectLight(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TYPE_MASK) == PATHTRACING_LIGHT_RECT;
}
bool IsSpotLight(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TYPE_MASK) == PATHTRACING_LIGHT_SPOT;
}
// A light is a physical light if it can be intersected by a ray.
bool IsPhysicalLight(int LightId)
{
return IsEnvironmentLight(LightId);
}
float3 GetTranslatedPosition(int LightId)
{
return SceneLights[LightId].TranslatedWorldPosition;
}
float3 GetNormal(int LightId)
{
return SceneLights[LightId].Normal;
}
float3 GetdPdu(int LightId)
{
return cross(SceneLights[LightId].Normal, SceneLights[LightId].Tangent);
}
float3 GetdPdv(int LightId)
{
return SceneLights[LightId].Tangent;
}
float GetWidth(int LightId)
{
return SceneLights[LightId].Dimensions.x;
}
float GetHeight(int LightId)
{
return SceneLights[LightId].Dimensions.y;
}
float2 GetRectSize(int LightId)
{
return SceneLights[LightId].Dimensions.xy;
}
float2 GetCosConeAngles(int LightId)
{
return SceneLights[LightId].Shaping;
}
float3 GetColor(int LightId)
{
return SceneLights[LightId].Color;
}
float GetRadius(int LightId)
{
return SceneLights[LightId].Dimensions.x;
}
float GetSourceLength(int LightId)
{
return SceneLights[LightId].Dimensions.y;
}
float GetAttenuation(int LightId)
{
return SceneLights[LightId].Attenuation;
}
float GetRectLightBarnCosAngle(int LightId)
{
return SceneLights[LightId].Shaping.x;
}
float GetRectLightBarnLength(int LightId)
{
return SceneLights[LightId].Shaping.y;
}
float2 GetRectLightAtlasUVScale(int LightId)
{
return SceneLights[LightId].RectLightAtlasUVScale;
}
float2 GetRectLightAtlasUVOffset(int LightId)
{
return SceneLights[LightId].RectLightAtlasUVOffset;
}
#ifndef USE_ATTENUATION_TERM
#define USE_ATTENUATION_TERM 1
#endif
float ComputeAttenuationFalloff(float DistanceSquared, int LightId)
{
#if USE_ATTENUATION_TERM
// Mirrors GetLocalLightAttenuation() custom attenuation controls
// #dxr_todo: UE-72508: encapsulate this function in a shared space
float InvAttenuationRadius = GetAttenuation(LightId);
float NormalizeDistanceSquared = DistanceSquared * Square(InvAttenuationRadius);
if ((SceneLights[LightId].Flags & PATHTRACER_FLAG_NON_INVERSE_SQUARE_FALLOFF_MASK) == 0)
{
return Square(saturate(1.0 - Square(NormalizeDistanceSquared)));
}
else
{
// roughly cancel out "native" square distance falloff before applying exponent based falloff function
// this appears to match the behavior of the deferred lighting passes
float FalloffExponent = SceneLights[LightId].FalloffExponent;
return DistanceSquared * pow(1.0 - saturate(NormalizeDistanceSquared), FalloffExponent);
}
#else
return 1.0;
#endif
}
#ifndef USE_IES_TERM
#define USE_IES_TERM 1
#endif
float ComputeIESAttenuation(int LightId, float3 TranslatedWorldPosition)
{
float Result = 1.f;
#if USE_IES_TERM
if (SceneLights[LightId].IESAtlasIndex >= 0)
{
float3 LightDirection = normalize(GetTranslatedPosition(LightId) - TranslatedWorldPosition);
float DotProd = dot(LightDirection, GetNormal(LightId));
float Angle = asin(DotProd);
const float NormAngle = Angle / PI + 0.5f;
float du = dot(LightDirection, GetdPdu(LightId));
float dv = dot(LightDirection, GetdPdv(LightId));
float NormTangentAngle = atan2(dv, du) / (PI * 2.f) + 0.5f;
const float3 UVW = float3(NormAngle, NormTangentAngle, SceneLights[LightId].IESAtlasIndex);
Result = View.IESAtlasTexture.SampleLevel(View.IESAtlasSampler, UVW, 0);
}
#endif
return Result;
}
bool HasTransmission(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_TRANSMISSION_MASK) != 0;
}
uint GetLightingChannelMask(int LightId)
{
return SceneLights[LightId].Flags & PATHTRACER_FLAG_LIGHTING_CHANNEL_MASK;
}
bool IsStationary(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_STATIONARY_MASK) != 0;
}
bool CastsShadow(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_CAST_SHADOW_MASK) != 0;
}
bool CastsVolumeShadow(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_CAST_VOL_SHADOW_MASK) != 0;
}
bool CastsCloudShadow(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_CAST_CLOUD_SHADOW_MASK) != 0;
}
bool HasRectTexture(int LightId)
{
return (SceneLights[LightId].Flags & PATHTRACER_FLAG_HAS_RECT_TEXTURE_MASK) != 0;
}
float GetVolumetricScatteringIntensity(int LightId)
{
return SceneLights[LightId].VolumetricScatteringIntensity;
}
float GetLightSpecularScale(int LightId)
{
return UnpackFloat2FromUInt(SceneLights[LightId].DiffuseSpecularScale).y;
}
float GetLightDiffuseScale(int LightId)
{
return UnpackFloat2FromUInt(SceneLights[LightId].DiffuseSpecularScale).x;
}
float GetLightIndirectScale(int LightId, float PathRoughness)
{
// Directly visible hits and sharp reflections should not be scaled, but as the roughness increases, we want to apply the indirect lighting scale
return lerp(1.0, SceneLights[LightId].IndirectLightingScale, saturate(4.0 * PathRoughness));
}
uint GetLightMissShaderIndex(int LightId)
{
return SceneLights[LightId].MissShaderIndex;
}