Shader
RWTexture2D<float3> RWDirectLightingAtlas;
StructuredBuffer<uint> CardTiles;
StructuredBuffer<uint> LightTileOffsetNumPerCardTile;
StructuredBuffer<uint2> LightTilesPerCardTile;
[numthreads( 8 , 8 , 1)]
void LumenCardBatchDirectLightingCS(
uint3 GroupId : SV_GroupID,
uint3 GroupThreadId : SV_GroupThreadID)
{
uint CardTileIndex = GroupId.x;
uint2 TexelCoordInTile = GroupThreadId.xy;
uint PackedOffsetNum = LightTileOffsetNumPerCardTile[CardTileIndex];
uint LightTilesOffset = BitFieldExtractU32(PackedOffsetNum, 24, 0);
uint NumLightTiles = BitFieldExtractU32(PackedOffsetNum, 8, 24);
FCardTileData CardTile = UnpackCardTileData(CardTiles[CardTileIndex]);
FLumenCardPageData CardPage = GetLumenCardPageData(CardTile.CardPageIndex);
uint2 CoordInCardPage = 8 * CardTile.TileCoord + TexelCoordInTile;
uint2 AtlasCoord = CardPage.PhysicalAtlasCoord + CoordInCardPage;
float2 AtlasUV = CardPage.PhysicalAtlasUVRect.xy + CardPage.PhysicalAtlasUVTexelScale * (CoordInCardPage + 0.5);
float2 CardUV = CardPage.CardUVRect.xy + CardPage.CardUVTexelScale * (CoordInCardPage + 0.5);
FLumenCardData Card = GetLumenCardData(CardPage.CardIndex);
FLumenSurfaceCacheData SurfaceCacheData = GetSurfaceCacheData(Card, CardUV, AtlasUV);
if (NumLightTiles == 0 || !SurfaceCacheData.bValid)
{
RWDirectLightingAtlas[AtlasCoord] = 0;
return;
}
float3 Irradiance = 0.0f;
for (uint CulledLightIndex = 0; CulledLightIndex < NumLightTiles; ++CulledLightIndex)
{
FLightTileForLightPass LightTile = UnpackLightTileForLightPass(LightTilesPerCardTile[LightTilesOffset + CulledLightIndex]);
FShadowMaskRay ShadowMaskRay;
ShadowMaskRay.bShadowFactorComplete = true;
ShadowMaskRay.ShadowFactor = 1.0f;
if (LightTile.ShadowMaskIndex != 0xffffffff)
{
ReadShadowMaskRay(LightTile.ShadowMaskIndex, TexelCoordInTile, ShadowMaskRay);
}
if (ShadowMaskRay.ShadowFactor > 0.0f)
{
uint ViewIndex = 0 ? LightTile.ViewIndex : 0;
Irradiance += GetIrradianceForLight(LightTile.LightIndex, SurfaceCacheData, LWCToFloat(PreViewTranslation[ViewIndex]) .xyz, ShadowMaskRay.ShadowFactor);
}
}
RWDirectLightingAtlas[AtlasCoord] = Irradiance;
}
#ifdef LUMEN_CARD_DATA_STRIDE
FLumenSurfaceCacheData GetSurfaceCacheData(FLumenCardData Card, float2 CardUV, float2 AtlasUV)
{
float Depth = Texture2DSampleLevel(LumenCardScene.DepthAtlas, GlobalPointClampedSampler, AtlasUV, 0).x;
FLumenSurfaceCacheData SurfaceCacheData;
SurfaceCacheData.Depth = Depth;
SurfaceCacheData.bValid = IsSurfaceCacheDepthValid(Depth);
SurfaceCacheData.Albedo = float3(0.0f, 0.0f, 0.0f);
SurfaceCacheData.Emissive = float3(0.0f, 0.0f, 0.0f);
float2 NormalXY = float2(0.5f, 0.5f);
if (SurfaceCacheData.bValid)
{
SurfaceCacheData.Albedo = DecodeSurfaceCacheAlbedo(Texture2DSampleLevel(LumenCardScene.AlbedoAtlas, GlobalPointClampedSampler, AtlasUV, 0).xyz);
SurfaceCacheData.Emissive = Texture2DSampleLevel(LumenCardScene.EmissiveAtlas, GlobalPointClampedSampler, AtlasUV, 0).x;
NormalXY = Texture2DSampleLevel(LumenCardScene.NormalAtlas, GlobalPointClampedSampler, AtlasUV, 0).xy;
}
SurfaceCacheData.WorldNormal = DecodeSurfaceCacheNormal(Card, NormalXY);
SurfaceCacheData.WorldPosition = GetCardWorldPosition(Card, CardUV, SurfaceCacheData.Depth);
return SurfaceCacheData;
}
#endif
#ifdef LUMEN_CARD_DATA_STRIDE
float3 DecodeSurfaceCacheNormal(FLumenCardData Card, float2 EncodedNormal)
{
float3 CardSpaceNormal;
CardSpaceNormal.xy = EncodedNormal.xy * 2.0f - 1.0f;
CardSpaceNormal.z = sqrt(max(1.0f - length2(CardSpaceNormal.xy), 0.0001f));
return normalize(mul(Card.WorldToLocalRotation, CardSpaceNormal));
}
#endif
float3 GetCardLocalPosition(float3 CardLocalExtent, float2 CardUV, float Depth)
{
CardUV.x = 1.0f - CardUV.x;
float3 LocalPosition;
LocalPosition.xy = CardLocalExtent.xy * (1.0f - 2.0f * CardUV);
LocalPosition.z = -(2.0f * Depth - 1.0f) * CardLocalExtent.z;
return LocalPosition;
}
void GetCardLocalBBox(FLumenCardPageData CardPage, FLumenCardData Card, float2 UVMin, float2 UVMax, out float3 CardPageLocalCenter, out float3 CardPageLocalExtent)
{
float2 CardUVMin = lerp(CardPage.CardUVRect.xw, CardPage.CardUVRect.zy, UVMin);
float2 CardUVMax = lerp(CardPage.CardUVRect.xw, CardPage.CardUVRect.zy, UVMax);
float3 CardPageLocalBoxMin = GetCardLocalPosition(Card.LocalExtent, CardUVMin, 1.0f);
float3 CardPageLocalBoxMax = GetCardLocalPosition(Card.LocalExtent, CardUVMax, 0.0f);
CardPageLocalCenter = 0.5f * (CardPageLocalBoxMax + CardPageLocalBoxMin);
CardPageLocalExtent = 0.5f * (CardPageLocalBoxMax - CardPageLocalBoxMin);
}
void GetCardPageLocalBBox(FLumenCardPageData CardPage, FLumenCardData Card, out float3 CardPageLocalCenter, out float3 CardPageLocalExtent)
{
GetCardLocalBBox(CardPage, Card, 0, 1, CardPageLocalCenter, CardPageLocalExtent);
}
float3 GetCardWorldPosition(FLumenCardData Card, float2 CardUV, float Depth)
{
float3 LocalPosition = GetCardLocalPosition(Card.LocalExtent, CardUV, Depth);
float3 WorldPosition = mul(Card.WorldToLocalRotation, LocalPosition) + Card.Origin;
return WorldPosition;
}
void ReadShadowMaskRay(uint CardTileIndex, uint2 CoordInCardTile, inout FShadowMaskRay ShadowMaskRay)
{
uint BitOffset = SHADOW_MASK_RAY_BITS * (CoordInCardTile.x + CoordInCardTile.y * CARD_TILE_SIZE);
uint ShadowMask = ShadowMaskTiles[SHADOW_MASK_CARD_TILE_DWORDS * CardTileIndex + BitOffset / 32];
ShadowMask = ShadowMask >> (BitOffset % 32);
ShadowMaskRay.ShadowFactor = float(ShadowMask & SHADOW_FACTOR_BITS_MASK) / SHADOW_FACTOR_BITS_MASK;
ShadowMaskRay.bShadowFactorComplete = (ShadowMask & SHADOW_FACTOR_COMPLETE_BITS_MASK) != 0;
}
float3 GetIrradianceForLight(
uint LightIndex,
FLumenSurfaceCacheData SurfaceCacheData,
float3 PreViewTranslation,
float ShadowFactor)
{
FDeferredLightData LightData = GetLumenDirectLightingLightData(LightIndex, PreViewTranslation);
float3 WorldNormal = SurfaceCacheData.WorldNormal;
float3 WorldPosition = SurfaceCacheData.WorldPosition;
float3 TranslatedWorldPosition = WorldPosition + PreViewTranslation;
float3 LightColor = LightData.Color;
float3 L = LightData.Direction;
float3 ToLight = L;
float3 AreaLightFalloffColor = 1;
float CombinedAttenuation = 1;
float NoL = saturate(dot(WorldNormal, L));
if (LightData.bRadialLight)
{
FAreaLightIntegrateContext Context = (FAreaLightIntegrateContext) 0;
float LightMask = GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L);
float Attenuation = 0.0f;
float Roughness = 1;
float3 V = float3(1, 0, 0);
if (LightData.bRectLight)
{
FRect Rect = GetRect(ToLight, LightData);
Attenuation = IntegrateLight(Rect);
if (IsRectVisible(Rect))
{
const FRectTexture SourceTexture = InitRectTexture(LightData);
Context = CreateRectIntegrateContext(Roughness, WorldNormal, V, Rect, SourceTexture);
}
}
else
{
FCapsuleLight Capsule = GetCapsule(ToLight, LightData);
Capsule.DistBiasSqr = 0;
Context = CreateCapsuleIntegrateContext(Roughness, WorldNormal, V, Capsule, LightData.bInverseSquared);
Attenuation = IntegrateLight(Capsule, LightData.bInverseSquared);
}
CombinedAttenuation = Attenuation * LightMask;
AreaLightFalloffColor = Context.AreaLight.FalloffColor;
NoL = Context.NoL;
}
float3 Irradiance = LightColor * AreaLightFalloffColor * (CombinedAttenuation * NoL * ShadowFactor);
return Irradiance;
}
// Should this be SH instead?
float IntegrateLight( FCapsuleLight Capsule, bool bInverseSquared )
{
float Falloff;
BRANCH
if( Capsule.Length > 0 )
{
float NoL;
float LineCosSubtended = 1;
LineIrradiance( 0, Capsule.LightPos[0], Capsule.LightPos[1], Capsule.DistBiasSqr, LineCosSubtended, Falloff, NoL );
}
else
{
float3 ToLight = Capsule.LightPos[0];
float DistSqr = dot( ToLight, ToLight );
Falloff = rcp( DistSqr + Capsule.DistBiasSqr );
}
Falloff = bInverseSquared ? Falloff : 1;
return Falloff;
}