// Copyright Epic Games, Inc. All Rights Reserved. #include "HeterogeneousVolumesSparseVoxelUtils.ush" #include "../Common.ush" #define SUPPORT_CONTACT_SHADOWS 0 #define DYNAMICALLY_SHADOWED 1 #define TREAT_MAXDEPTH_UNSHADOWED 1 #define SHADOW_QUALITY 2 #define NO_TRANSLUCENCY_AVAILABLE #if VIRTUAL_SHADOW_MAP #include "../VirtualShadowMaps/VirtualShadowMapProjectionCommon.ush" #endif #include "HeterogeneousVolumesSparseVoxelUniformBufferUtils.ush" #include "HeterogeneousVolumesTracingUtils.ush" #include "HeterogeneousVolumesLightingUtils.ush" #ifndef THREADGROUP_SIZE_1D #define THREADGROUP_SIZE_1D 1 #endif // THREADGROUP_SIZE_1D #ifndef THREADGROUP_SIZE_2D #define THREADGROUP_SIZE_2D 1 #endif // THREADGROUP_SIZE_2D #ifndef THREADGROUP_SIZE_3D #define THREADGROUP_SIZE_3D 1 #endif // THREADGROUP_SIZE_3D #ifndef DIM_SPARSE_VOXEL_TRACING #define DIM_SPARSE_VOXEL_TRACING 1 #endif // DIM_SPARSE_VOXEL_TRACING #ifndef DIM_VOXEL_CULLING #define DIM_VOXEL_CULLING 1 #endif // DIM_VOXEL_CULLING #ifndef DIM_APPLY_SHADOW_TRANSMITTANCE #define DIM_APPLY_SHADOW_TRANSMITTANCE 0 #endif // DIM_APPLY_SHADOW_TRANSMITTANCE // Lighting int bApplyEmissionAndTransmittance; int bApplyDirectLighting; int bApplyShadowTransmittance; int LightType; float VolumetricScatteringIntensity; // Shadowing uint VirtualShadowMapId; // Ray data float MaxTraceDistance; float MaxShadowTraceDistance; float StepSize; int MaxStepCount; int bJitter; // Dispatch data int3 GroupCount; int DownsampleFactor; // Volume data int MipLevel; // Output RWTexture2D RWLightingTexture; RWTexture2D RWVelocityTexture; int3 TextureResolution; Texture3D InputTexture; RWTexture3D RWOutputTexture; [numthreads(THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D)] void CopyTexture3D( uint3 DispatchThreadId : SV_DispatchThreadID ) { uint3 Index = DispatchThreadId; if (all(Index < TextureResolution)) { RWOutputTexture[Index] = InputTexture[Index].xyz; } } SamplerState TextureSampler; [numthreads(THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D)] void GenerateMips3D( uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID ) { uint3 Index = DispatchThreadId; if (all(Index < TextureResolution)) { #if 1 float3 UVW = (Index + 0.5) / float3(TextureResolution); RWOutputTexture[Index] = InputTexture.SampleLevel(TextureSampler, UVW, 0).rgb; #else float3 Value = 0; UNROLL for (uint i = 0; i < 8; ++i) { uint3 BaseIndex = Index * 2 + float3((i & 4u) >> 2, (i & 2u) >> 1, (i & 1u)); BaseIndex = min(BaseIndex, TextureResolution * 2); Value += InputTexture[BaseIndex].xyz; } Value *= 0.125; RWOutputTexture[Index] = Value; #endif } } [numthreads(THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D)] void GenerateMin3D( uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID ) { uint3 Index = DispatchThreadId; if (all(Index < TextureResolution)) { float3 Value = 65535.0f; UNROLL for (uint i = 0; i < 8; ++i) { int3 BaseIndex = Index * 2 + float3((i & 4u) >> 2, (i & 2u) >> 1, (i & 1u)); BaseIndex = min(BaseIndex, TextureResolution * 2); Value = min(Value, Luminance(InputTexture[BaseIndex].xyz)); } RWOutputTexture[Index] = Value; } } StructuredBuffer VoxelBuffer; Buffer NumVoxelsBuffer; // A screen-tile is an 8x8 block of computation for ray marching struct FRayMarchingTile { uint2 PixelOffset; uint Voxels[2]; // TODO: For tracking purposes.. uint Id; uint Padding[3]; }; FRayMarchingTile CreateRayMarchingTile(uint TileId, uint2 PixelOffset, uint VoxelMin, uint VoxelMax) { FRayMarchingTile Tile = (FRayMarchingTile) 0; Tile.PixelOffset = PixelOffset; Tile.Voxels[0] = VoxelMin; Tile.Voxels[1] = VoxelMax; Tile.Id = TileId; return Tile; } RWStructuredBuffer RWRayMarchingTilesBuffer; RWBuffer RWNumRayMarchingTilesBuffer; RWStructuredBuffer RWVoxelsPerTileBuffer; RWBuffer RWNumVoxelsPerTileBuffer; struct FRayMarchingDebug { float4 Planes[5]; float4 BBox[2]; float Padding[4]; }; RWStructuredBuffer RWRayMarchingDebugBuffer; float4 CreatePlane( float3 P_000, float3 P_100, float3 P_010 ) { float3 X = P_100 - P_000; float3 Y = P_010 - P_000; float3 N = normalize(cross(X, Y)); float d = -dot(N, P_000); return float4(N, d); } float CalcSignedDistance(float3 P, float4 Plane) { return dot(P, Plane.xyz) + Plane.w; } bool PointIntersectsFrustum(float3 P, float4 Planes[5]) { for (int i = 0; i < 5; ++i) { if (CalcSignedDistance(P, Planes[i]) > 0.0) { return false; } } return true; } bool BoxIntersectsFrustum(float3 BoxOrigin, float3 BoxExtent, float4 Planes[5]) { for (int i = 0; i < 5; ++i) { float Distance = CalcSignedDistance(BoxOrigin, Planes[i]); float ProjectedExtent = dot(BoxExtent, abs(Planes[i].xyz)); if (Distance > ProjectedExtent) { return false; } } return true; } [numthreads(THREADGROUP_SIZE_2D, THREADGROUP_SIZE_2D, 1)] void GenerateRayMarchingTiles( uint2 DispatchThreadId : SV_DispatchThreadID ) { if (!any(DispatchThreadId)) { RWNumRayMarchingTilesBuffer[1] = 1; RWNumRayMarchingTilesBuffer[2] = 1; } uint4 Viewport = uint4(DispatchThreadId.xy * THREADGROUP_SIZE_2D, (DispatchThreadId.xy + 1) * THREADGROUP_SIZE_2D); uint2 PixelOffset = Viewport.xy; // Scale to full-res Viewport *= DownsampleFactor; uint2 ViewPixelOffset = Viewport.xy; if (any(ViewPixelOffset >= View.ViewSizeAndInvSize.xy)) { return; } // Build tile-frustum float Epsilon = 1.0e-6; #if HAS_INVERTED_Z_BUFFER float NearDepth = 1.0; float FarDepth = Epsilon; #else float NearDepth = 0.0; float FarDepth = 1.0 - Epsilon; #endif // HAS_INVERTED_Z_BUFFER FDFInverseMatrix WorldToLocal = DFPromoteInverse(GetWorldToLocal()); // LWC_TODO float4x4 TranslatedWorldToLocal = DFFastToTranslatedWorld(WorldToLocal, PrimaryView.PreViewTranslation); float3 W_000 = SvPositionToTranslatedWorld(float4(Viewport.xw, NearDepth, 1)); float3 W_100 = SvPositionToTranslatedWorld(float4(Viewport.zw, NearDepth, 1)); float3 W_010 = SvPositionToTranslatedWorld(float4(Viewport.xy, NearDepth, 1)); float3 W_110 = SvPositionToTranslatedWorld(float4(Viewport.zy, NearDepth, 1)); float3 W_001 = SvPositionToTranslatedWorld(float4(Viewport.xw, FarDepth, 1)); float3 W_101 = SvPositionToTranslatedWorld(float4(Viewport.zw, FarDepth, 1)); float3 W_011 = SvPositionToTranslatedWorld(float4(Viewport.xy, FarDepth, 1)); float3 W_111 = SvPositionToTranslatedWorld(float4(Viewport.zy, FarDepth, 1)); float3 L_000 = mul(float4(W_000, 1), TranslatedWorldToLocal).xyz; float3 L_100 = mul(float4(W_100, 1), TranslatedWorldToLocal).xyz; float3 L_010 = mul(float4(W_010, 1), TranslatedWorldToLocal).xyz; float3 L_110 = mul(float4(W_110, 1), TranslatedWorldToLocal).xyz; float3 L_001 = mul(float4(W_001, 1), TranslatedWorldToLocal).xyz; float3 L_101 = mul(float4(W_101, 1), TranslatedWorldToLocal).xyz; float3 L_011 = mul(float4(W_011, 1), TranslatedWorldToLocal).xyz; float3 L_111 = mul(float4(W_111, 1), TranslatedWorldToLocal).xyz; float4 Planes[5] = { // Front CreatePlane(L_000, L_010, L_100), // Back //CreatePlane(L_101, L_001, L_111), // +X CreatePlane(L_100, L_110, L_101), // +Y CreatePlane(L_010, L_011, L_110), // -X CreatePlane(L_000, L_001, L_010), // -Y CreatePlane(L_000, L_100, L_001) }; uint DebugIndex = DispatchThreadId.y * THREADGROUP_SIZE_2D + DispatchThreadId.x; #if DIM_DEBUG for (uint i = 0; i < 5; ++i) { RWRayMarchingDebugBuffer[DebugIndex].Planes[i] = Planes[i]; } RWRayMarchingDebugBuffer[DebugIndex].BBox[0] = float4(GetLocalBoundsOrigin() - GetLocalBoundsExtent(), 0.0); RWRayMarchingDebugBuffer[DebugIndex].BBox[1] = float4(GetLocalBoundsOrigin() + GetLocalBoundsExtent(), 0.0); #endif // DIM_DEBUG // Intersect frustum against bounding volume if (!BoxIntersectsFrustum(GetLocalBoundsOrigin(), GetLocalBoundsExtent(), Planes)) { return; } #if DIM_VOXEL_CULLING float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent(); float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent(); // Intersect against voxels to determine allocation uint VoxelCount = 0; for (uint VoxelIndex = 0; VoxelIndex < GetNumVoxels(); ++VoxelIndex) { FVoxelData VoxelData = DecodeVoxelData(GetVoxelDataPacked(VoxelIndex), GetVolumeResolution()); FBox VoxelBoundingBox = GetVoxelBoundingBox(VoxelData, GetVolumeResolution(), LocalBoundsMin, LocalBoundsMax); float3 BoxOrigin = (VoxelBoundingBox.Max + VoxelBoundingBox.Min) / 2.0; float3 BoxExtent = VoxelBoundingBox.Max - VoxelBoundingBox.Min; if (BoxIntersectsFrustum(BoxOrigin, BoxExtent, Planes)) { VoxelCount++; } } // Allocate contiguous span of voxel indices uint VoxelEntryIndex; if (VoxelCount > 0) { InterlockedAdd(RWNumVoxelsPerTileBuffer[0], VoxelCount, VoxelEntryIndex); // Intersect and store voxels VoxelCount = 0; for (uint VoxelIndex = 0; VoxelIndex < GetNumVoxels(); ++VoxelIndex) { FVoxelData VoxelData = DecodeVoxelData(GetVoxelDataPacked(VoxelIndex), GetVolumeResolution()); FBox VoxelBoundingBox = GetVoxelBoundingBox(VoxelData, GetVolumeResolution(), LocalBoundsMin, LocalBoundsMax); float3 BoxOrigin = (VoxelBoundingBox.Max + VoxelBoundingBox.Min) / 2.0; float3 BoxExtent = VoxelBoundingBox.Max - VoxelBoundingBox.Min; if (BoxIntersectsFrustum(BoxOrigin, BoxExtent, Planes)) { RWVoxelsPerTileBuffer[VoxelEntryIndex + VoxelCount] = GetVoxelDataPacked(VoxelIndex); VoxelCount++; } } } #else uint VoxelEntryIndex = 0; uint VoxelCount = GetNumVoxels(); #endif // Allocate tile if (VoxelCount > 0) { uint TileId; InterlockedAdd(RWNumRayMarchingTilesBuffer[0], 1, TileId); RWRayMarchingTilesBuffer[TileId] = CreateRayMarchingTile(TileId, PixelOffset, VoxelEntryIndex, VoxelEntryIndex + VoxelCount); } } struct FTraceVoxelResult { FVoxelData VoxelData; float2 HitT; }; FTraceVoxelResult TraceVoxels(float3 LocalRayOrigin, float3 LocalRayDirection, float LocalRayTMin, float LocalRayTMax, float3 LocalBoundsMin, float3 LocalBoundsMax) { FTraceVoxelResult TraceVoxelResult; TraceVoxelResult.HitT.x = LocalRayTMax; TraceVoxelResult.HitT.y = LocalRayTMax; for (uint VoxelIndex = 0; VoxelIndex < GetNumVoxels(); ++VoxelIndex) { FVoxelData VoxelData = DecodeVoxelData(GetVoxelDataPacked(VoxelIndex), GetVolumeResolution()); FBox VoxelBoundingBox = GetVoxelBoundingBox(VoxelData, GetVolumeResolution(), LocalBoundsMin, LocalBoundsMax); float2 HitT = IntersectAABB(LocalRayOrigin, LocalRayDirection, LocalRayTMin, LocalRayTMax, VoxelBoundingBox.Min, VoxelBoundingBox.Max); bool bIsHit = (HitT.y - HitT.x) > 0.0; if (bIsHit && (HitT.x < TraceVoxelResult.HitT.x)) { TraceVoxelResult.HitT = HitT; TraceVoxelResult.VoxelData = VoxelData; } } return TraceVoxelResult; } StructuredBuffer VoxelsPerTileBuffer; FTraceVoxelResult TraceVoxels(float3 LocalRayOrigin, float3 LocalRayDirection, float LocalRayTMin, float LocalRayTMax, float3 LocalBoundsMin, float3 LocalBoundsMax, uint VoxelStartIndex, uint VoxelEndIndex) { FTraceVoxelResult TraceVoxelResult; TraceVoxelResult.HitT.x = LocalRayTMax; TraceVoxelResult.HitT.y = LocalRayTMax; for (uint VoxelIndex = VoxelStartIndex; VoxelIndex < VoxelEndIndex; ++VoxelIndex) { FVoxelData VoxelData = DecodeVoxelData(VoxelsPerTileBuffer[VoxelIndex], GetVolumeResolution()); FBox VoxelBoundingBox = GetVoxelBoundingBox(VoxelData, GetVolumeResolution(), LocalBoundsMin, LocalBoundsMax); float2 HitT = IntersectAABB(LocalRayOrigin, LocalRayDirection, LocalRayTMin, LocalRayTMax, VoxelBoundingBox.Min, VoxelBoundingBox.Max); bool bIsHit = (HitT.y - HitT.x) > 0.0; if (bIsHit && (HitT.x < TraceVoxelResult.HitT.x)) { TraceVoxelResult.HitT = HitT; TraceVoxelResult.VoxelData = VoxelData; } } return TraceVoxelResult; } // TODO: Implement ambient occlusion for preshading pipeline float EvalAmbientOcclusion(float3 WorldPosition) { return 1.0; } // Cinematic feature options bool UseInscatteringVolume() { return true; } #define INDIRECT_LIGHTING_MODE_OFF 0 #define INDIRECT_LIGHTING_MODE_LIGHTING_CACHE 1 #define INDIRECT_LIGHTING_MODE_SINGLE_SCATTERING 2 int GetIndirectLightingMode() { return INDIRECT_LIGHTING_MODE_OFF; } bool ShouldWriteVelocity() { return false; } bool IsOfflineRender() { return false; } #define FOG_INSCATTERING_MODE_OFF 0 #define FOG_INSCATTERING_MODE_REFERENCE 1 #define FOG_INSCATTERING_MODE_LINEAR_APPROX 2 int GetFogInscatteringMode() { return FOG_INSCATTERING_MODE_LINEAR_APPROX; } #include "HeterogeneousVolumesRayMarchingUtils.ush" RWTexture3D RWLightingCacheTexture; [numthreads(THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D, THREADGROUP_SIZE_3D)] void RenderLightingCacheWithPreshadingCS( uint3 GroupId : SV_GroupID, uint3 GroupThreadId : SV_GroupThreadID, uint3 DispatchThreadId : SV_DispatchThreadID ) { float3 VoxelIndex = DispatchThreadId; if (any(VoxelIndex >= LightingCacheResolution)) { return; } float3 VoxelJitter = 0.5; if (bJitter) { uint3 Rand32Bits = Rand4DPCG32(uint4(VoxelIndex, View.StateFrameIndexMod8)).xyz; VoxelJitter = float3(Rand32Bits) / float(uint(0xffffffff)); } //float3 UVW = (VoxelIndex + VectorJitter) / float3(GetLightingCacheResolution()); float3 UVW = (VoxelIndex + 0.5) / float3(GetLightingCacheResolution()); float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent(); float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent(); float3 LocalRayOrigin = UVW * GetLocalBoundsExtent() * 2.0 + LocalBoundsMin; float3 WorldRayOrigin = mul(float4(LocalRayOrigin, 1.0), GetLocalToWorld()).xyz; // Existence culling float MipLevel = 0.0f; FVolumeSampleContext SampleContext = CreateVolumeSampleContext(LocalRayOrigin, WorldRayOrigin, MipLevel); float3 Extinction = SampleExtinction(SampleContext); float Epsilon = 1.0e-7; if (all(Extinction < Epsilon)) { #if DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_TRANSMITTANCE RWLightingCacheTexture[VoxelIndex] = 1.0f; #elif DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_INSCATTERING RWLightingCacheTexture[VoxelIndex] = 0.0f; #endif // DIM_LIGHTING_CACHE_MODE return; } // Build shadow ray const FDeferredLightData LightData = InitDeferredLightFromUniforms(LightType, VolumetricScatteringIntensity); #if DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_TRANSMITTANCE float3 L = LightData.Direction; float3 ToLight = L * GetMaxShadowTraceDistance(); if (LightType != LIGHT_TYPE_DIRECTIONAL) { // Note: this function also permutes ToLight and L float3 TranslatedWorldPosition = DFFastToTranslatedWorld(WorldRayOrigin, PrimaryView.PreViewTranslation); GetLocalLightAttenuation(TranslatedWorldPosition, LightData, ToLight, L); if (LightData.bRectLight) { FRect Rect = GetRect(ToLight, LightData); } else { FCapsuleLight Capsule = GetCapsule(ToLight, LightData); } } float3 Transmittance = ComputeTransmittance(WorldRayOrigin, ToLight, MaxStepCount); RWLightingCacheTexture[VoxelIndex] = Transmittance; #elif DIM_LIGHTING_CACHE_MODE == LIGHTING_CACHE_INSCATTERING float3 Inscattering = ComputeInscattering(WorldRayOrigin, LightData, LightType, MaxStepCount, CalcShadowBias(), bApplyShadowTransmittance); RWLightingCacheTexture[VoxelIndex] += Inscattering * View.PreExposure; #endif // DIM_LIGHTING_CACHE_MODE } RWStructuredBuffer RWVoxelOutputBuffer; StructuredBuffer RayMarchingTilesBuffer; Buffer NumRayMarchingTilesBuffer; float2 GetDispatchSize() { return View.ViewSizeAndInvSize.xy / DownsampleFactor; } [numthreads(THREADGROUP_SIZE_1D, 1, 1)] void RenderSingleScatteringWithPreshadingCS( uint GroupId : SV_GroupID, uint GroupThreadId : SV_GroupThreadID ) { // Convert 1D index to pixel coordinate FRayMarchingTile RayMarchingTile = RayMarchingTilesBuffer[GroupId]; // TODO: Morton.. uint2 TileCoord = uint2(GroupThreadId % THREADGROUP_SIZE_2D, GroupThreadId / THREADGROUP_SIZE_2D); //float2 PixelCoord = View.ViewRectMin.xy + RayMarchingTile.PixelOffset + TileCoord; float2 PixelCoord = RayMarchingTile.PixelOffset + TileCoord; float2 ViewPixelCoord = PixelCoord * DownsampleFactor; float3 Radiance = 0.0; float4 LightingTexture = RWLightingTexture[PixelCoord]; float3 Transmittance = LightingTexture.aaa; float PrevOpacity = Luminance(1.0 - Transmittance); // Create screen ray if (any(PixelCoord.xy >= GetDispatchSize())) { return; } PixelCoord += View.ViewRectMin.xy; // Extract depth float DeviceZ = SceneDepthTexture.Load(int3(ViewPixelCoord, 0)).r; #if HAS_INVERTED_Z_BUFFER DeviceZ = max(0.000000000001, DeviceZ); #endif // HAS_INVERTED_Z_BUFFER // Clip trace distance float SceneDepth = min(ConvertFromDeviceZ(DeviceZ), MaxTraceDistance); DeviceZ = ConvertToDeviceZ(SceneDepth); float Jitter = bJitter ? InterleavedGradientNoise(ViewPixelCoord, View.StateFrameIndexMod8) : 0.0; // Intersect ray with bounding volume // TODO: LWC integration.. float3 WorldRayOrigin = DFHackToFloat(DFFastSubtract(GetTranslatedWorldCameraPosFromView(ViewPixelCoord + View.TemporalAAJitter.xy), PrimaryView.PreViewTranslation)); float3 WorldRayEnd = DFHackToFloat(SvPositionToWorld(float4(ViewPixelCoord + View.TemporalAAJitter.xy, DeviceZ, 1))); float3 WorldRayDirection = WorldRayEnd - WorldRayOrigin; float WorldRayLength = length(WorldRayDirection); WorldRayDirection /= WorldRayLength; float3 LocalRayOrigin = mul(float4(WorldRayOrigin, 1.0), GetWorldToLocal()).xyz; float3 LocalRayEnd = mul(float4(WorldRayEnd, 1.0), GetWorldToLocal()).xyz; float3 LocalRayDirection = LocalRayEnd - LocalRayOrigin; float LocalRayLength = length(LocalRayDirection); LocalRayDirection /= LocalRayLength; float3 LocalBoundsMin = GetLocalBoundsOrigin() - GetLocalBoundsExtent(); float3 LocalBoundsMax = GetLocalBoundsOrigin() + GetLocalBoundsExtent(); // Test bounding volume float2 HitT = IntersectAABB(LocalRayOrigin, LocalRayDirection, 0.0, LocalRayLength, LocalBoundsMin, LocalBoundsMax); float HitSpan = HitT.y - HitT.x; if (HitSpan <= 0.0) { return; } float WorldHitTMin = WorldRayLength; #if DIM_SPARSE_VOXEL_TRACING float RayTMax = HitT.y; #if DIM_VOXEL_CULLING HitT = TraceVoxels(LocalRayOrigin, LocalRayDirection, 0.0, RayTMax, LocalBoundsMin, LocalBoundsMax, RayMarchingTile.Voxels[0], RayMarchingTile.Voxels[1]).HitT; #else HitT = TraceVoxels(LocalRayOrigin, LocalRayDirection, 0.0, RayTMax, LocalBoundsMin, LocalBoundsMax).HitT; #endif // DIM_VOXEL_CULLING HitSpan = HitT.y - HitT.x; while (HitSpan > 0.0) #endif // DIM_SPARSE_VOXEL_TRACING { FRayMarchingContext RayMarchingContext = CreateRayMarchingContext( // Local-space LocalRayOrigin, LocalRayDirection, HitT.x, HitT.y, // World-space WorldRayOrigin, WorldRayDirection, // Ray-step attributes Jitter, CalcStepSize(LocalRayDirection), MaxStepCount, bApplyEmissionAndTransmittance, bApplyDirectLighting, DIM_APPLY_SHADOW_TRANSMITTANCE ); const FDeferredLightData LightData = InitDeferredLightFromUniforms(LightType, VolumetricScatteringIntensity); uint StepCount = CalcStepCount(RayMarchingContext); float ScatterHitT = HitT.y; RayMarchSingleScattering( RayMarchingContext, LightData, LightType, StepCount, ScatterHitT, Radiance, Transmittance ); WorldHitTMin = min(WorldHitTMin, ScatterHitT * RayMarchingContext.LocalToWorldScale); #if DIM_SPARSE_VOXEL_TRACING #if DIM_VOXEL_CULLING HitT = TraceVoxels(LocalRayOrigin, LocalRayDirection, HitT.y + 0.01, RayTMax, LocalBoundsMin, LocalBoundsMax, RayMarchingTile.Voxels[0], RayMarchingTile.Voxels[1]).HitT; #else HitT = TraceVoxels(LocalRayOrigin, LocalRayDirection, HitT.y + 0.0001, RayTMax, LocalBoundsMin, LocalBoundsMax).HitT; #endif // DIM_VOXEL_CULLING HitSpan = HitT.y - HitT.x; #endif // DIM_SPARSE_VOXEL_TRACING } // Radiance LightingTexture.rgb += Radiance * View.PreExposure; if (bApplyEmissionAndTransmittance) { float Opacity = Luminance(1.0 - Transmittance); LightingTexture.a = Luminance(Transmittance); if (ShouldWriteVelocity()) { bool bWriteVelocity = (WorldHitTMin < WorldRayLength) && (PrevOpacity < 0.5) && (Opacity > 0.5); if (bWriteVelocity) { float3 Velocity = Calculate3DVelocityAtWorldPos(WorldRayOrigin, WorldRayDirection, WorldHitTMin); RWVelocityTexture[PixelCoord] = EncodeVelocityToTexture(Velocity); } } #if DIM_DEBUG //if (!all(DispatchThreadId)) if (GroupId == 0 && GroupThreadId == 0) { RWVoxelOutputBuffer[0].MipLevel = GetNumVoxels(); for (uint VoxelIndex = 0; VoxelIndex < GetNumVoxels(); ++VoxelIndex) { RWVoxelOutputBuffer[VoxelIndex + 1] = GetVoxelDataPacked(VoxelIndex); } } #endif } RWLightingTexture[PixelCoord] = LightingTexture; }