You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
ViewWorldOrigin -> WorldViewOrigin CameraWorldOrigin -> WorldCameraOrigin CameraTranslatedWorldOrigin -> TranslatedWorldCameraOrigin CameraWorldOriginDelta -> WorldCameraMovementSinceLastFrame PrevCameraWorldOrigin -> PrevWorldCameraOrigin PrevViewWorldOrigin -> PrevWorldViewOrigin #code_review: Martin.Mittring [CL 2666624 by Guillaume Abadie in Main branch]
623 lines
24 KiB
Plaintext
623 lines
24 KiB
Plaintext
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
|
|
|
|
/*=============================================================================
|
|
DistanceFieldSurfaceCacheLighting.usf
|
|
=============================================================================*/
|
|
|
|
#include "Common.usf"
|
|
#include "DeferredShadingCommon.usf"
|
|
#include "DistanceFieldLightingShared.usf"
|
|
#include "DistanceFieldAOShared.usf"
|
|
#include "GlobalDistanceFieldShared.usf"
|
|
|
|
struct FObjectCullVertexOutput
|
|
{
|
|
nointerpolation float4 PositionAndRadius : TEXCOORD0;
|
|
nointerpolation uint ObjectIndex : TEXCOORD1;
|
|
};
|
|
|
|
float ConservativeRadiusScale;
|
|
|
|
/** Used when culling objects into screenspace tile lists */
|
|
void ObjectCullVS(
|
|
float4 InPosition : ATTRIBUTE0,
|
|
uint ObjectIndex : SV_InstanceID,
|
|
out FObjectCullVertexOutput Output,
|
|
out float4 OutPosition : SV_POSITION
|
|
)
|
|
{
|
|
//@todo - implement ConservativelyBoundSphere
|
|
float4 ObjectPositionAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
|
|
//@todo - expand to handle conservative rasterization
|
|
float EffectiveRadius = (ObjectPositionAndRadius.w + AOObjectMaxDistance) * ConservativeRadiusScale;
|
|
float3 WorldPosition = InPosition.xyz * EffectiveRadius + ObjectPositionAndRadius.xyz;
|
|
OutPosition = mul(float4(WorldPosition, 1), View.WorldToClip);
|
|
Output.PositionAndRadius = ObjectPositionAndRadius;
|
|
Output.ObjectIndex = ObjectIndex;
|
|
}
|
|
|
|
Buffer<float4> TileConeAxisAndCos;
|
|
Buffer<float4> TileConeDepthRanges;
|
|
|
|
float2 NumGroups;
|
|
|
|
/** Intersects a single object with the tile and adds to the intersection list if needed. */
|
|
void ObjectCullPS(
|
|
FObjectCullVertexOutput Input,
|
|
in float4 SVPos : SV_POSITION,
|
|
out float4 OutColor : SV_Target0)
|
|
{
|
|
OutColor = 0;
|
|
|
|
uint2 TilePosition = (uint2)SVPos.xy;
|
|
uint TileIndex = TilePosition.y * NumGroups.x + TilePosition.x;
|
|
float4 ConeAxisAndCos = TileConeAxisAndCos.Load(TileIndex);
|
|
float4 ConeAxisDepthRanges = TileConeDepthRanges.Load(TileIndex);
|
|
float3 TileConeVertex = 0;
|
|
float3 TileConeAxis = ConeAxisAndCos.xyz;
|
|
float TileConeAngleCos = ConeAxisAndCos.w;
|
|
float TileConeAngleSin = sqrt(1 - TileConeAngleCos * TileConeAngleCos);
|
|
|
|
float4 WorldSphereCenterAndRadius = Input.PositionAndRadius;
|
|
float3 ViewSpaceSphereCenter = mul(float4(WorldSphereCenterAndRadius.xyz + View.PreViewTranslation.xyz, 1), View.TranslatedWorldToView).xyz;
|
|
|
|
#if USE_DEPTH_RANGE_LISTS
|
|
|
|
// A value of 1 is conservative, but has a huge impact on performance
|
|
float RadiusScale = .5f;
|
|
|
|
float4 SphereCenterAndRadius = float4(ViewSpaceSphereCenter, WorldSphereCenterAndRadius.w + RadiusScale * AOObjectMaxDistance);
|
|
|
|
if (SphereIntersectCone(SphereCenterAndRadius, TileConeVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin))
|
|
{
|
|
float ConeAxisDistance = dot(SphereCenterAndRadius.xyz - TileConeVertex, TileConeAxis);
|
|
float2 ConeAxisDistanceMinMax = float2(ConeAxisDistance + SphereCenterAndRadius.w, ConeAxisDistance - SphereCenterAndRadius.w);
|
|
|
|
uint TotalNumGroups = (uint)(NumGroups.x * NumGroups.y + .5f);
|
|
IntersectObjectWithConeDepthRange(TileConeVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin, ConeAxisDepthRanges.xy, ConeAxisDistanceMinMax, TileIndex, 0, Input.ObjectIndex, TotalNumGroups);
|
|
IntersectObjectWithConeDepthRange(TileConeVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin, ConeAxisDepthRanges.zw, ConeAxisDistanceMinMax, TileIndex, 1, Input.ObjectIndex, TotalNumGroups);
|
|
}
|
|
|
|
#else
|
|
|
|
// A value of 1 is conservative, but has a huge impact on performance
|
|
float RadiusScale = .5f;
|
|
|
|
int SmallestGroupIndex = -1;
|
|
|
|
UNROLL
|
|
for (int GroupIndex = NUM_CULLED_OBJECT_LISTS - 1; GroupIndex >= 0; GroupIndex--)
|
|
{
|
|
uint StartIndex;
|
|
uint EndIndex;
|
|
GetPhaseParameters(GroupIndex, StartIndex, EndIndex);
|
|
float GroupMaxSampleRadius = GetStepOffset(EndIndex) * 2 * RadiusScale;
|
|
|
|
BRANCH
|
|
if (SphereIntersectConeWithDepthRanges(float4(ViewSpaceSphereCenter, WorldSphereCenterAndRadius.w + GroupMaxSampleRadius), TileConeVertex, TileConeAxis, TileConeAngleCos, TileConeAngleSin, ConeAxisDepthRanges))
|
|
{
|
|
SmallestGroupIndex = GroupIndex;
|
|
}
|
|
}
|
|
|
|
if (SmallestGroupIndex >= 0)
|
|
{
|
|
uint ArrayIndex;
|
|
InterlockedAdd(RWTileHeadDataUnpacked[TileIndex * 4 + 1 + (uint)SmallestGroupIndex], 1U, ArrayIndex);
|
|
|
|
if (ArrayIndex < MAX_OBJECTS_PER_TILE)
|
|
{
|
|
// Note: indexing so that threads are writing to RWTileArrayData coherently, has a huge impact on speed, even though the array data for one record is no longer dense
|
|
uint DataIndex = (ArrayIndex * (uint)(NumGroups.x * NumGroups.y + .5f) + TileIndex) * NUM_CULLED_OBJECT_LISTS + SmallestGroupIndex;
|
|
|
|
RWTileArrayData[DataIndex] = Input.ObjectIndex;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/** Computes the distance field normal, using a search through all the nearby objects to find the closest one, whose normal is used. */
|
|
void ComputeDistanceFieldNormalPS(
|
|
in float4 UVAndScreenPos : TEXCOORD0,
|
|
in float4 SVPos : SV_POSITION,
|
|
out float4 OutColor : SV_Target0)
|
|
{
|
|
// Sample from the center of the top left full resolution texel
|
|
float2 ScreenUV = float2((floor(SVPos.xy) * DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + .5f) * View.BufferSizeAndInvSize.zw);
|
|
float SceneDepth = CalcSceneDepth(ScreenUV);
|
|
FGBufferData GBufferData = GetGBufferData(ScreenUV);
|
|
|
|
OutColor = EncodeDownsampledGBuffer(GBufferData, SceneDepth);
|
|
}
|
|
|
|
RWTexture2D<float4> RWDistanceFieldNormal;
|
|
|
|
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
|
|
void ComputeDistanceFieldNormalCS(
|
|
uint3 GroupId : SV_GroupID,
|
|
uint3 DispatchThreadId : SV_DispatchThreadID,
|
|
uint3 GroupThreadId : SV_GroupThreadID)
|
|
{
|
|
float2 ScreenUV = float2((DispatchThreadId.xy * DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + .5f) * View.BufferSizeAndInvSize.zw);
|
|
float SceneDepth = CalcSceneDepth(ScreenUV);
|
|
FGBufferData GBufferData = GetGBufferData(ScreenUV);
|
|
|
|
float4 OutValue = EncodeDownsampledGBuffer(GBufferData, SceneDepth);
|
|
RWDistanceFieldNormal[DispatchThreadId.xy] = OutValue;
|
|
}
|
|
|
|
void WriteDownsampledDepthPS(
|
|
in float4 UVAndScreenPos : TEXCOORD0,
|
|
out float4 OutColor : SV_Target0,
|
|
out float OutDepth : SV_DEPTH)
|
|
{
|
|
float2 DistanceFieldUVs = UVAndScreenPos.xy;
|
|
|
|
OutColor = 0;
|
|
OutDepth = ConvertToDeviceZ(GetDownsampledDepth(DistanceFieldUVs));
|
|
}
|
|
|
|
// For some reason gives innaccurate results at lower resolutions
|
|
#define USE_SCREENVECTOR_WORLD_POSITION FINAL_INTERPOLATION_PASS
|
|
|
|
struct FIrradianceCacheSplatVertexOutput
|
|
{
|
|
nointerpolation float4 PositionRadius : TEXCOORD0;
|
|
nointerpolation float4 NormalAndFade : TEXCOORD1;
|
|
#if FINAL_INTERPOLATION_PASS
|
|
nointerpolation float3 BentNormal : TEXCOORD2;
|
|
#if SUPPORT_IRRADIANCE
|
|
nointerpolation float3 Irradiance : TEXCOORD5;
|
|
#endif
|
|
#endif
|
|
#if USE_SCREENVECTOR_WORLD_POSITION
|
|
float3 ScreenVector : TEXCOORD6;
|
|
#endif
|
|
};
|
|
|
|
float InterpolationRadiusScale;
|
|
float2 NormalizedOffsetToPixelCenter;
|
|
float HackExpand;
|
|
|
|
// +1 = behind volume (safe from near plane clipping without depth testing),
|
|
// -1 = in front of volume (required for conservative results with depth testing)
|
|
//@todo - using -1 currently causes artifacts with architectural scenes
|
|
float InterpolationBoundingDirection;
|
|
|
|
/** Expands a screen-facing polygon to cover a surface cache record for splatting. */
|
|
void IrradianceCacheSplatVS(
|
|
float2 InPosition : ATTRIBUTE0,
|
|
float2 InUV : ATTRIBUTE1,
|
|
uint JobIndex : SV_InstanceID,
|
|
out FIrradianceCacheSplatVertexOutput Output,
|
|
out float4 OutPosition : SV_POSITION
|
|
)
|
|
{
|
|
float4 PositionAndPackedRadius = IrradianceCachePositionRadius[JobIndex];
|
|
|
|
float3 RecordPosition = PositionAndPackedRadius.xyz;
|
|
float RecordRadius = abs(PositionAndPackedRadius.w);
|
|
|
|
float OccluderRadius = IrradianceCacheOccluderRadius[JobIndex];
|
|
RecordRadius = min(RecordRadius, OccluderRadius);
|
|
|
|
float ViewToCenterLength = length(RecordPosition - View.WorldCameraOrigin);
|
|
float3 NormalizedViewToCenter = (RecordPosition - View.WorldCameraOrigin) / ViewToCenterLength;
|
|
|
|
// Only allow full interpolation expanding for small samples, it won't be noticeable for large samples but adds a lot of splatting cost
|
|
float EffectiveInterpolationRadiusScale = lerp(InterpolationRadiusScale, 1, max(saturate(RecordRadius / 80), .2f));
|
|
RecordRadius = max(RecordRadius, ViewToCenterLength * .01f) * EffectiveInterpolationRadiusScale;
|
|
|
|
float OffsetFromCenter = InterpolationBoundingDirection * RecordRadius;
|
|
// Distance from the camera position that NormalizedViewToCenter will intersect the near plane
|
|
float NearPlaneDistance = View.NearPlane / dot(View.ViewForward, NormalizedViewToCenter);
|
|
|
|
// Don't move closer than the near plane to avoid getting clipped
|
|
// Clamping at the vertex level only works because it's a screen-aligned triangle
|
|
// Small bias to push just off the near plane
|
|
if (ViewToCenterLength + OffsetFromCenter < NearPlaneDistance + .001f)
|
|
{
|
|
OffsetFromCenter = -ViewToCenterLength + NearPlaneDistance + .001f;
|
|
}
|
|
|
|
// Construct a virtual sample position that won't be near plane clipped and will have a positive z after projection
|
|
float3 VirtualPosition = RecordPosition + OffsetFromCenter * NormalizedViewToCenter;
|
|
// Clipping the edge of the circle a bit can save perf, but introduces artifacts in the interpolation
|
|
float RadiusSlack = 1.0f;
|
|
// Compute new radius since we moved the center away from the camera, approximate as similar triangles
|
|
// This is really incorrect because it's not taking into account perspective correction on a sphere - the widest part in screenspace is not at the world space center
|
|
float VirtualRadius = RadiusSlack * RecordRadius * (ViewToCenterLength + OffsetFromCenter) / ViewToCenterLength;
|
|
|
|
#if !FINAL_INTERPOLATION_PASS
|
|
// Combat the mysterious bug where samples from last frame do not cover this frame's pixel even with no camera movement, seems to be a precision issue
|
|
VirtualRadius += HackExpand * ViewToCenterLength * 4;
|
|
#endif
|
|
|
|
float4 RecordNormalAndFade = IrradianceCacheNormal[JobIndex];
|
|
|
|
float2 CornerUVs = InUV;
|
|
float3 CornerPosition = VirtualPosition + (View.ViewRight * CornerUVs.x + View.ViewUp * CornerUVs.y) * VirtualRadius;
|
|
|
|
OutPosition = mul(float4(CornerPosition.xyz, 1), View.WorldToClip);
|
|
|
|
#if USE_SCREENVECTOR_WORLD_POSITION
|
|
Output.ScreenVector = mul(float4(OutPosition.xy / OutPosition.w, 1, 0), View.ScreenToWorld).xyz;
|
|
#endif
|
|
|
|
// Move the vertex over to compensate for the difference between the low res pixel center and the high res pixel center where shading is done
|
|
OutPosition.xy += NormalizedOffsetToPixelCenter * OutPosition.w;
|
|
|
|
Output.PositionRadius = float4(RecordPosition, RecordRadius);
|
|
|
|
Output.NormalAndFade = float4(RecordNormalAndFade.xyz, 1);
|
|
|
|
#if FINAL_INTERPOLATION_PASS
|
|
Output.BentNormal = IrradianceCacheBentNormal[JobIndex * BENT_NORMAL_STRIDE].xyz;
|
|
#if SUPPORT_IRRADIANCE
|
|
float ViewDistance = length(View.WorldCameraOrigin - RecordPosition);
|
|
float FadeAlpha = saturate(.001f * (.95f * AOMaxViewDistance - ViewDistance));
|
|
Output.Irradiance = IrradianceCacheIrradiance[JobIndex].xyz * FadeAlpha;
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
float InterpolationAngleNormalization;
|
|
float InvMinCosPointBehindPlane;
|
|
|
|
/** Computes surface cache weighting between the current pixel and the record being splatted. */
|
|
void IrradianceCacheSplatPS(
|
|
FIrradianceCacheSplatVertexOutput Input,
|
|
in float4 SVPos : SV_POSITION
|
|
,out float4 OutBentNormal : SV_Target0
|
|
#if FINAL_INTERPOLATION_PASS && SUPPORT_IRRADIANCE
|
|
, out float4 OutIrradiance : SV_Target1
|
|
#endif
|
|
)
|
|
{
|
|
OutBentNormal = 0;
|
|
#if FINAL_INTERPOLATION_PASS && SUPPORT_IRRADIANCE
|
|
OutIrradiance = 0;
|
|
#endif
|
|
|
|
float2 BaseLevelScreenUV = float2(((floor(SVPos.xy)) * DownsampleFactorToBaseLevel + .5f) * BaseLevelTexelSize);
|
|
|
|
float3 WorldNormal;
|
|
float SceneDepth;
|
|
bool bHasDistanceFieldRepresentation;
|
|
bool bHasHeightfieldRepresentation;
|
|
GetDownsampledGBuffer(BaseLevelScreenUV, WorldNormal, SceneDepth, bHasDistanceFieldRepresentation, bHasHeightfieldRepresentation);
|
|
|
|
#if USE_SCREENVECTOR_WORLD_POSITION
|
|
|
|
float3 OpaqueWorldPosition = View.WorldCameraOrigin + Input.ScreenVector * SceneDepth;
|
|
|
|
#else
|
|
|
|
float2 ScreenUV = float2(((floor(SVPos.xy)) * CurrentLevelDownsampleFactor + View.ViewRectMin.xy + float2(.5f, .5f)) * View.BufferSizeAndInvSize.zw);
|
|
SceneDepth = CalcSceneDepth(ScreenUV);
|
|
float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
|
|
float4 HomogeneousWorldPosition = mul(float4(ScreenPosition.xy * SceneDepth, SceneDepth, 1), View.ScreenToWorld);
|
|
float3 OpaqueWorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w;
|
|
|
|
#endif
|
|
|
|
float Distance = length(OpaqueWorldPosition - Input.PositionRadius.xyz);
|
|
float DistanceError = saturate(Distance / Input.PositionRadius.w);
|
|
float Weight = 0;
|
|
|
|
BRANCH
|
|
if (DistanceError < 1 && SceneDepth < AOMaxViewDistance)
|
|
{
|
|
float3 RecordNormal = Input.NormalAndFade.xyz;
|
|
float NormalError = InterpolationAngleNormalization * sqrt(saturate(1 - dot(WorldNormal, RecordNormal)));
|
|
|
|
// Don't use a lighting record if it's in front of the query point.
|
|
// Query points behind the lighting record may have nearby occluders that the lighting record does not see.
|
|
// Offset the comparison point along the negative normal to prevent self-occlusion
|
|
float3 RecordToVertexVector = OpaqueWorldPosition - (Input.PositionRadius.xyz - 1 * RecordNormal.xyz);
|
|
float DistanceToVertex = length(RecordToVertexVector);
|
|
float PlaneDistance = dot(RecordNormal.xyz, RecordToVertexVector) / DistanceToVertex;
|
|
|
|
// Setup an error metric that goes from 0 if the points are coplanar, to 1 if the point being shaded is at the angle corresponding to MinCosPointBehindPlane behind the plane
|
|
float PointBehindPlaneError = min(max(PlaneDistance * InvMinCosPointBehindPlane, 0.0f), DistanceToVertex / 3.0f);
|
|
|
|
float PrecisionScale = .1f;
|
|
Weight = saturate(PrecisionScale * Input.NormalAndFade.w * (1 - max(DistanceError, max(NormalError, PointBehindPlaneError))));
|
|
//Weight = saturate(PrecisionScale * Input.NormalAndFade.w * (1 - DistanceError));
|
|
//Weight = Input.NormalAndFade.w;
|
|
|
|
float VisualizePlacement = Distance < .1f * Input.PositionRadius.w;
|
|
|
|
#if FINAL_INTERPOLATION_PASS
|
|
// Pixels without a distance field representation interpolate AO instead of a bent normal
|
|
float3 InterpolatedValue = bHasDistanceFieldRepresentation ? Input.BentNormal : length(Input.BentNormal).xxx;
|
|
OutBentNormal.rgb = InterpolatedValue * Weight;
|
|
#if SUPPORT_IRRADIANCE
|
|
OutIrradiance.rgb = Input.Irradiance * Weight;
|
|
#endif
|
|
#endif
|
|
OutBentNormal.a = Weight;
|
|
}
|
|
|
|
#define VISUALIZE_SPLAT_OVERDRAW 0
|
|
#if VISUALIZE_SPLAT_OVERDRAW && FINAL_INTERPOLATION_PASS
|
|
OutBentNormal.rgb = 0;
|
|
//OutBentNormal.a = Input.Position.w <= SceneDepth ? .01f : 0.0f;
|
|
OutBentNormal.a = .005f;// + (Weight > .001f ? .01f : 0);
|
|
#endif
|
|
|
|
#define VISUALIZE_RECORD_POINTS 0
|
|
#if VISUALIZE_RECORD_POINTS && FINAL_INTERPOLATION_PASS
|
|
OutBentNormal.rgb = 1;
|
|
OutBentNormal.a = (DistanceError < .05f) * .1f;
|
|
#endif
|
|
}
|
|
|
|
RWTexture2D<float4> RWVisualizeMeshDistanceFields;
|
|
|
|
bool RayHitSphere( float3 RayOrigin, float3 UnitRayDirection, float3 SphereCenter, float SphereRadius )
|
|
{
|
|
float3 ClosestPointOnRay = max( 0, dot( SphereCenter - RayOrigin, UnitRayDirection ) ) * UnitRayDirection;
|
|
float3 CenterToRay = RayOrigin + ClosestPointOnRay - SphereCenter;
|
|
return dot( CenterToRay, CenterToRay ) <= Square( SphereRadius );
|
|
}
|
|
|
|
#define MAX_INTERSECTING_OBJECTS 512
|
|
groupshared uint IntersectingObjectIndices[MAX_INTERSECTING_OBJECTS];
|
|
|
|
groupshared uint NumIntersectingObjects;
|
|
|
|
void RayTraceThroughTileCulledDistanceFields(float3 WorldRayStart, float3 WorldRayEnd, float MaxRayTime, out float MinRayTime, out float TotalStepsTaken)
|
|
{
|
|
MinRayTime = MaxRayTime;
|
|
TotalStepsTaken = 0;
|
|
|
|
LOOP
|
|
for (uint ListObjectIndex = 0; ListObjectIndex < min(NumIntersectingObjects, MAX_INTERSECTING_OBJECTS); ListObjectIndex++)
|
|
{
|
|
uint ObjectIndex = IntersectingObjectIndices[ListObjectIndex];
|
|
float4 SphereCenterAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
|
|
|
|
float3 LocalPositionExtent = LoadObjectLocalPositionExtent(ObjectIndex);
|
|
float4x4 WorldToVolume = LoadObjectWorldToVolume(ObjectIndex);
|
|
float4 UVScaleAndVolumeScale = LoadObjectUVScale(ObjectIndex);
|
|
float3 UVAdd = LoadObjectUVAdd(ObjectIndex);
|
|
|
|
float3 VolumeRayStart = mul(float4(WorldRayStart, 1), WorldToVolume).xyz;
|
|
float3 VolumeRayEnd = mul(float4(WorldRayEnd, 1), WorldToVolume).xyz;
|
|
float3 VolumeRayDirection = VolumeRayEnd - VolumeRayStart;
|
|
float VolumeRayLength = length(VolumeRayDirection);
|
|
VolumeRayDirection /= VolumeRayLength;
|
|
|
|
float2 IntersectionTimes = LineBoxIntersect(VolumeRayStart, VolumeRayEnd, -LocalPositionExtent, LocalPositionExtent);
|
|
|
|
if (IntersectionTimes.x < IntersectionTimes.y && IntersectionTimes.x < 1)
|
|
{
|
|
float SampleRayTime = IntersectionTimes.x * VolumeRayLength;
|
|
|
|
float MinDistance = 1000000;
|
|
|
|
uint StepIndex = 0;
|
|
uint MaxSteps = 256;
|
|
|
|
LOOP
|
|
for (; StepIndex < MaxSteps; StepIndex++)
|
|
{
|
|
float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
|
|
float3 ClampedSamplePosition = clamp(SampleVolumePosition, -LocalPositionExtent, LocalPositionExtent);
|
|
float3 VolumeUV = DistanceFieldVolumePositionToUV(ClampedSamplePosition, UVScaleAndVolumeScale.xyz, UVAdd);
|
|
float DistanceField = Texture3DSampleLevel(DistanceFieldTexture, DistanceFieldSampler, VolumeUV, 0).x;
|
|
MinDistance = min(MinDistance, DistanceField);
|
|
|
|
float MinStepSize = 1.0f / (4 * MaxSteps);
|
|
float StepDistance = max(DistanceField, MinStepSize);
|
|
SampleRayTime += StepDistance;
|
|
|
|
// Terminate the trace if we reached a negative area or went past the end of the ray
|
|
if (DistanceField < 0
|
|
|| SampleRayTime > IntersectionTimes.y * VolumeRayLength)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Result = max(Result, StepIndex / (float)MaxSteps);
|
|
|
|
if (MinDistance * UVScaleAndVolumeScale.w < 0 || StepIndex == MaxSteps)
|
|
{
|
|
MinRayTime = min(MinRayTime, SampleRayTime * UVScaleAndVolumeScale.w);
|
|
}
|
|
|
|
TotalStepsTaken += StepIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
void RayTraceThroughGlobalDistanceField(
|
|
uniform uint ClipmapIndex,
|
|
float3 WorldRayStart,
|
|
float3 WorldRayEnd,
|
|
float RayLength,
|
|
float MinRayTime,
|
|
out float OutMaxRayTime,
|
|
out float OutIntersectRayTime,
|
|
out float OutTotalStepsTaken)
|
|
{
|
|
OutIntersectRayTime = RayLength;
|
|
OutTotalStepsTaken = 0;
|
|
OutMaxRayTime = 1;
|
|
|
|
float3 GlobalVolumeCenter = GlobalVolumeCenterAndExtent[ClipmapIndex].xyz;
|
|
float GlobalVolumeExtent = GlobalVolumeCenterAndExtent[ClipmapIndex].w;
|
|
float3 VolumeRayStart = WorldRayStart - GlobalVolumeCenter;
|
|
float3 VolumeRayEnd = WorldRayEnd - GlobalVolumeCenter;
|
|
float3 VolumeRayDirection = VolumeRayEnd - VolumeRayStart;
|
|
float VolumeRayLength = length(VolumeRayDirection);
|
|
VolumeRayDirection /= VolumeRayLength;
|
|
|
|
float2 IntersectionTimes = LineBoxIntersect(VolumeRayStart, VolumeRayEnd, -GlobalVolumeExtent.xxx, GlobalVolumeExtent.xxx);
|
|
|
|
if (IntersectionTimes.x < IntersectionTimes.y && IntersectionTimes.x < 1)
|
|
{
|
|
OutMaxRayTime = IntersectionTimes.y;
|
|
float SampleRayTime = max(MinRayTime, IntersectionTimes.x) * VolumeRayLength;
|
|
|
|
float MinDistance = 1000000;
|
|
|
|
uint StepIndex = 0;
|
|
uint MaxSteps = 512;
|
|
|
|
LOOP
|
|
for (; StepIndex < MaxSteps; StepIndex++)
|
|
{
|
|
float3 SampleVolumePosition = VolumeRayStart + VolumeRayDirection * SampleRayTime;
|
|
float3 VolumeUV = ComputeGlobalUV(SampleVolumePosition + GlobalVolumeCenter, ClipmapIndex);
|
|
float DistanceField = Texture3DSampleLevel(GlobalDistanceFieldTexture[ClipmapIndex], GlobalDistanceFieldSampler[ClipmapIndex], VolumeUV, 0).x;
|
|
MinDistance = min(MinDistance, DistanceField);
|
|
|
|
float MinStepSize = GlobalVolumeExtent * 2 / 8000.0f;
|
|
float StepDistance = max(DistanceField, MinStepSize);
|
|
SampleRayTime += StepDistance;
|
|
|
|
// Terminate the trace if we reached a negative area or went past the end of the ray
|
|
if (DistanceField < 0
|
|
|| SampleRayTime > IntersectionTimes.y * VolumeRayLength)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
//Result = max(Result, StepIndex / (float)MaxSteps);
|
|
|
|
if (MinDistance < 0 || StepIndex == MaxSteps)
|
|
{
|
|
OutIntersectRayTime = min(OutIntersectRayTime, SampleRayTime);
|
|
}
|
|
|
|
OutTotalStepsTaken += StepIndex;
|
|
}
|
|
}
|
|
|
|
|
|
[numthreads(THREADGROUP_SIZEX, THREADGROUP_SIZEY, 1)]
|
|
void VisualizeMeshDistanceFieldCS(
|
|
uint3 GroupId : SV_GroupID,
|
|
uint3 DispatchThreadId : SV_DispatchThreadID,
|
|
uint3 GroupThreadId : SV_GroupThreadID)
|
|
{
|
|
uint ThreadIndex = GroupThreadId.y * THREADGROUP_SIZEX + GroupThreadId.x;
|
|
|
|
float2 ScreenUV = float2((DispatchThreadId.xy * DOWNSAMPLE_FACTOR + View.ViewRectMin.xy + .5f) * View.BufferSizeAndInvSize.zw);
|
|
float2 ScreenPosition = (ScreenUV.xy - View.ScreenPositionScaleBias.wz) / View.ScreenPositionScaleBias.xy;
|
|
|
|
float SceneDepth = CalcSceneDepth(ScreenUV);
|
|
float4 HomogeneousWorldPosition = mul(float4(ScreenPosition * SceneDepth, SceneDepth, 1), View.ScreenToWorld);
|
|
float3 OpaqueWorldPosition = HomogeneousWorldPosition.xyz / HomogeneousWorldPosition.w;
|
|
|
|
float TraceDistance = 40000;
|
|
float3 WorldRayStart = View.WorldCameraOrigin;
|
|
float3 WorldRayEnd = WorldRayStart + normalize(OpaqueWorldPosition - View.WorldCameraOrigin) * TraceDistance;
|
|
float3 WorldRayDirection = WorldRayEnd - WorldRayStart;
|
|
float3 UnitWorldRayDirection = normalize(WorldRayDirection);
|
|
|
|
#if USE_GLOBAL_DISTANCE_FIELD
|
|
|
|
float TotalStepsTaken = 0;
|
|
|
|
float MaxRayTime0;
|
|
float IntersectRayTime;
|
|
float StepsTaken;
|
|
RayTraceThroughGlobalDistanceField(0, WorldRayStart, WorldRayEnd, TraceDistance, 0, MaxRayTime0, IntersectRayTime, StepsTaken);
|
|
|
|
TotalStepsTaken += StepsTaken;
|
|
|
|
if (IntersectRayTime >= TraceDistance)
|
|
{
|
|
float MaxRayTime1;
|
|
RayTraceThroughGlobalDistanceField(1, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime0, MaxRayTime1, IntersectRayTime, StepsTaken);
|
|
TotalStepsTaken += StepsTaken;
|
|
|
|
if (IntersectRayTime >= TraceDistance)
|
|
{
|
|
float MaxRayTime2;
|
|
RayTraceThroughGlobalDistanceField(2, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime1, MaxRayTime2, IntersectRayTime, StepsTaken);
|
|
TotalStepsTaken += StepsTaken;
|
|
|
|
if (IntersectRayTime >= TraceDistance)
|
|
{
|
|
float MaxRayTime3;
|
|
RayTraceThroughGlobalDistanceField(3, WorldRayStart, WorldRayEnd, TraceDistance, MaxRayTime2, MaxRayTime3, IntersectRayTime, StepsTaken);
|
|
TotalStepsTaken += StepsTaken;
|
|
}
|
|
}
|
|
}
|
|
|
|
float3 Result = saturate(TotalStepsTaken / 400.0f);
|
|
|
|
#else
|
|
if (ThreadIndex == 0)
|
|
{
|
|
NumIntersectingObjects = 0;
|
|
}
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
uint NumCulledObjects = GetCulledNumObjects();
|
|
|
|
LOOP
|
|
for (uint ObjectIndex = ThreadIndex; ObjectIndex < NumCulledObjects; ObjectIndex += THREADGROUP_TOTALSIZE)
|
|
{
|
|
float4 SphereCenterAndRadius = LoadObjectPositionAndRadius(ObjectIndex);
|
|
|
|
//@todo - make independent of current pixel
|
|
BRANCH
|
|
if (RayHitSphere(WorldRayStart, UnitWorldRayDirection, SphereCenterAndRadius.xyz, SphereCenterAndRadius.w))
|
|
{
|
|
uint ListIndex;
|
|
InterlockedAdd(NumIntersectingObjects, 1U, ListIndex);
|
|
|
|
if (ListIndex < MAX_INTERSECTING_OBJECTS)
|
|
{
|
|
IntersectingObjectIndices[ListIndex] = ObjectIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
GroupMemoryBarrierWithGroupSync();
|
|
|
|
float MinRayTime;
|
|
float TotalStepsTaken;
|
|
|
|
// Trace once to find the distance to first intersection
|
|
RayTraceThroughTileCulledDistanceFields(WorldRayStart, WorldRayEnd, TraceDistance, MinRayTime, TotalStepsTaken);
|
|
|
|
float TempMinRayTime;
|
|
// Recompute the ray end point
|
|
WorldRayEnd = WorldRayStart + UnitWorldRayDirection * MinRayTime;
|
|
// Trace a second time to only accumulate steps taken before the first intersection, improves visualization
|
|
RayTraceThroughTileCulledDistanceFields(WorldRayStart, WorldRayEnd, MinRayTime, TempMinRayTime, TotalStepsTaken);
|
|
|
|
float3 Result = saturate(TotalStepsTaken / 200.0f);
|
|
|
|
if (MinRayTime < TraceDistance)
|
|
{
|
|
Result += .1f;
|
|
}
|
|
|
|
#endif
|
|
|
|
RWVisualizeMeshDistanceFields[DispatchThreadId.xy] = float4(Result, 0);
|
|
}
|
|
|
|
Texture2D VisualizeDistanceFieldTexture;
|
|
SamplerState VisualizeDistanceFieldSampler;
|
|
|
|
void VisualizeDistanceFieldUpsamplePS(in float4 UVAndScreenPos : TEXCOORD0, out float4 OutColor : SV_Target0)
|
|
{
|
|
// Distance field AO was computed at 0,0 regardless of viewrect min
|
|
float2 DistanceFieldUVs = UVAndScreenPos.xy - View.ViewRectMin.xy * View.BufferSizeAndInvSize.zw;
|
|
|
|
float3 Value = Texture2DSampleLevel(VisualizeDistanceFieldTexture, VisualizeDistanceFieldSampler, DistanceFieldUVs, 0).xyz;
|
|
|
|
OutColor = float4(Value, 1);
|
|
} |