|
@@ -14,8 +14,11 @@ mixin RayMarch
|
|
|
#define HI_Z 0
|
|
#define HI_Z 0
|
|
|
#endif
|
|
#endif
|
|
|
|
|
|
|
|
- #define MAX_HIZ_ITERATIONS 9
|
|
|
|
|
- #define HIZ_START_LEVEL 1
|
|
|
|
|
|
|
+ // Note this is a very high number of iterations, but it is expected only a small number of pixels will use
|
|
|
|
|
+ // this full range. Generally those are pixels that keep having false positives when intersecting a higher
|
|
|
|
|
+ // Z level.
|
|
|
|
|
+ #define MAX_HIZ_ITERATIONS 64
|
|
|
|
|
+ #define HIZ_START_LEVEL 2
|
|
|
|
|
|
|
|
float3 viewToNDC(float3 view)
|
|
float3 viewToNDC(float3 view)
|
|
|
{
|
|
{
|
|
@@ -61,67 +64,153 @@ mixin RayMarch
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- bool hiZSearch(Texture2D depth, SamplerState samp, int2 bufferSize, int maxMipLevel, float3 rayStart, float3 rayDir, inout float t)
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ // TODO - Debug only
|
|
|
|
|
+ bool hiZSearchLinear(Texture2D depth, SamplerState samp, int2 bufferSize, int maxMipLevel, float3 rayPos, float3 rayStep, out float3 hitPos)
|
|
|
|
|
+ {
|
|
|
|
|
+ bufferSize >>= 2;
|
|
|
|
|
+
|
|
|
float iterationCount = 0.0f;
|
|
float iterationCount = 0.0f;
|
|
|
- int mipLevel = HIZ_START_LEVEL;
|
|
|
|
|
-
|
|
|
|
|
- bufferSize >>= mipLevel;
|
|
|
|
|
|
|
+ float mipLevel = 0.0f;
|
|
|
|
|
+
|
|
|
|
|
+ // Get the ray equation, in the form so that t == Z
|
|
|
|
|
+ float3 D = rayStep / rayStep.z; // Scale vector so Z is moved into [0, 1] range
|
|
|
|
|
+ float3 O = rayPos + D * -rayPos.z; // Get point where Z equals 0 (on near plane)
|
|
|
|
|
+ // Our ray is now O + D * t, where t = 0 = near plane, and t = 1 = far plane
|
|
|
|
|
|
|
|
- float3 rayPos = rayStart + rayDir * t;
|
|
|
|
|
- while(mipLevel >= 0 && iterationCount < MAX_HIZ_ITERATIONS)
|
|
|
|
|
|
|
+ // Avoid division by zero
|
|
|
|
|
+ D.x = abs(D.x) < 0.00001f ? 0.00001f : D.x;
|
|
|
|
|
+ D.y = abs(D.y) < 0.00001f ? 0.00001f : D.y;
|
|
|
|
|
+
|
|
|
|
|
+ while(rayPos.z <= 1.0f && iterationCount < 800.0f)
|
|
|
{
|
|
{
|
|
|
- if(any(rayPos < 0.0f) || any(rayPos > 1.0f))
|
|
|
|
|
- return false; // Reached the end of valid range
|
|
|
|
|
|
|
+ // Get depth of the current cell
|
|
|
|
|
+ float cellZ = depth.SampleLevel(samp, rayPos.xy, mipLevel).r;
|
|
|
|
|
+
|
|
|
|
|
+ // Get pixel coordinates of the current cell
|
|
|
|
|
+ float2 curCellIdx = trunc(rayPos.xy * bufferSize);
|
|
|
|
|
|
|
|
- // Get position of the ray, relative to the current cell (sub-pixel)
|
|
|
|
|
- float2 subCellPos = frac(rayPos.xy * bufferSize);
|
|
|
|
|
|
|
+ // Find intersection with the cell floor plane
|
|
|
|
|
+ float3 newRay = O + D * max(cellZ, rayPos.z); // max() so we can't hit the ceiling (ray going backwards)
|
|
|
|
|
|
|
|
- // Move subCellPos to [-1,1] range, as it makes the calculation below easier
|
|
|
|
|
- subCellPos *= 2.0f - 1.0f;
|
|
|
|
|
|
|
+ // Get pixel coordinates of the new ray's cell
|
|
|
|
|
+ float2 newCellIdx = trunc(newRay.xy * bufferSize);
|
|
|
|
|
|
|
|
- // Find how much we can move the ray (in "t") before we hit a cell wall
|
|
|
|
|
- //// We want: subCellPos + |rayDir| * t = 1
|
|
|
|
|
- //// Solve for t: t = (1 - subCellPos) / |rayDir|
|
|
|
|
|
- float epsilon = 0.00001f; // Handle div by zero
|
|
|
|
|
- float2 maxXY = (1.0f - subCellPos) / abs(rayDir.xy + epsilon);
|
|
|
|
|
- float maxT = min(maxXY.x, maxXY.y);
|
|
|
|
|
|
|
+ // If we moved to another cell, no intersection with floor
|
|
|
|
|
+ if(any(curCellIdx != newCellIdx))
|
|
|
|
|
+ {
|
|
|
|
|
+ // Find intersection with neighbor cell
|
|
|
|
|
+ float2 cellStart = (curCellIdx / bufferSize);
|
|
|
|
|
+ float2 cellEnd = ((curCellIdx + 1) / bufferSize);
|
|
|
|
|
+
|
|
|
|
|
+ float2 intersectStart = (cellStart - rayPos.xy) / D.xy;
|
|
|
|
|
+ float2 intersectEnd = (cellEnd - rayPos.xy) / D.xy;
|
|
|
|
|
+
|
|
|
|
|
+ // Only care about positive t
|
|
|
|
|
+ float maxIntersectX = max(intersectStart.x, intersectEnd.x);
|
|
|
|
|
+ float maxIntersectY = max(intersectStart.y, intersectEnd.y);
|
|
|
|
|
+
|
|
|
|
|
+ // Closest t is the one at the boundary
|
|
|
|
|
+ float minIntersect = min(maxIntersectX, maxIntersectY);
|
|
|
|
|
|
|
|
|
|
+ // Little extra to ensure the boundary is crossed. max() to ensure the value isn't too
|
|
|
|
|
+ // small to prevent it ever leaving a cell. Note that this clamping results in a quality loss,
|
|
|
|
|
+ // you want to keep the clamp value as low as possible, but not too low to avoid artifacts.
|
|
|
|
|
+ minIntersect = max(minIntersect * 1.05f, 1.0f / (bufferSize * 512)); // 1/512th of a pixel
|
|
|
|
|
+
|
|
|
|
|
+ // Move the ray past the boundary
|
|
|
|
|
+ rayPos = O + D * (rayPos.z + minIntersect);
|
|
|
|
|
+ }
|
|
|
|
|
+ else // Intersection with floor
|
|
|
|
|
+ {
|
|
|
|
|
+ hitPos = newRay;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ ++iterationCount;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ hitPos = rayPos;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bool hiZSearch(Texture2D depth, SamplerState samp, int2 bufferSize, int maxMipLevel, float3 rayPos, float3 rayStep, out float3 hitPos, out float iterationCount)
|
|
|
|
|
+ {
|
|
|
|
|
+ iterationCount = 0.0f;
|
|
|
|
|
+ int mipLevel = HIZ_START_LEVEL;
|
|
|
|
|
+
|
|
|
|
|
+ bufferSize >>= mipLevel;
|
|
|
|
|
+
|
|
|
|
|
+ // Get the ray equation, in the form so that t == Z
|
|
|
|
|
+ float3 D = rayStep / rayStep.z; // Scale vector so Z is moved into [0, 1] range
|
|
|
|
|
+ float3 O = rayPos + D * -rayPos.z; // Get point where Z equals 0 (on near plane)
|
|
|
|
|
+ // Our ray is now O + D * t, where t = 0 = near plane, and t = 1 = far plane
|
|
|
|
|
+
|
|
|
|
|
+ // Avoid division by zero
|
|
|
|
|
+ D.x = abs(D.x) < 0.00001f ? 0.00001f : D.x;
|
|
|
|
|
+ D.y = abs(D.y) < 0.00001f ? 0.00001f : D.y;
|
|
|
|
|
+
|
|
|
|
|
+ while(rayPos.z <= 1.0f && iterationCount < MAX_HIZ_ITERATIONS)
|
|
|
|
|
+ {
|
|
|
// Get depth of the current cell
|
|
// Get depth of the current cell
|
|
|
float cellZ = depth.SampleLevel(samp, rayPos.xy, mipLevel).r;
|
|
float cellZ = depth.SampleLevel(samp, rayPos.xy, mipLevel).r;
|
|
|
|
|
+
|
|
|
|
|
+ // Get pixel coordinates of the current cell
|
|
|
|
|
+ float2 curCellIdx = trunc(rayPos.xy * bufferSize);
|
|
|
|
|
+
|
|
|
|
|
+ // Find intersection with the cell floor plane
|
|
|
|
|
+ float3 newRay = O + D * max(cellZ, rayPos.z); // max() so we can't hit the ceiling (ray going backwards)
|
|
|
|
|
|
|
|
- // Find intersection with the cell
|
|
|
|
|
- //// We want: rayPos.z + rayDir.z * t = cellZ
|
|
|
|
|
- //// Solve for t: t = (cellZ - rayPos.z) / rayDir.z
|
|
|
|
|
- t = (cellZ - rayPos.z) / rayDir.z;
|
|
|
|
|
|
|
+ // Get pixel coordinates of the new ray's cell
|
|
|
|
|
+ float2 newCellIdx = trunc(newRay.xy * bufferSize);
|
|
|
|
|
|
|
|
- // The hit was within the cell walls, meaning we hit the floor of the cell (ray depth is higher than cell depth)
|
|
|
|
|
- float hitBias = 0.002;
|
|
|
|
|
- if(t < (maxT + hitBias))
|
|
|
|
|
|
|
+ // If we moved to another cell, no intersection with floor
|
|
|
|
|
+ if(any(curCellIdx != newCellIdx))
|
|
|
{
|
|
{
|
|
|
- // We're at the highest detail level, hit found
|
|
|
|
|
- if(mipLevel < 1)
|
|
|
|
|
- return true;
|
|
|
|
|
-
|
|
|
|
|
- // Increase detail level and refine search
|
|
|
|
|
- mipLevel -= 1;
|
|
|
|
|
- bufferSize <<= 1;
|
|
|
|
|
|
|
+ float2 cellStart = (curCellIdx / bufferSize);
|
|
|
|
|
+ float2 cellEnd = ((curCellIdx + 1) / bufferSize);
|
|
|
|
|
+
|
|
|
|
|
+ float2 intersectStart = (cellStart - rayPos.xy) / D.xy;
|
|
|
|
|
+ float2 intersectEnd = (cellEnd - rayPos.xy) / D.xy;
|
|
|
|
|
+
|
|
|
|
|
+ // Only care about positive t
|
|
|
|
|
+ float maxIntersectX = max(intersectStart.x, intersectEnd.x);
|
|
|
|
|
+ float maxIntersectY = max(intersectStart.y, intersectEnd.y);
|
|
|
|
|
+
|
|
|
|
|
+ // Closest t is the one at the boundary
|
|
|
|
|
+ float minIntersect = min(maxIntersectX, maxIntersectY);
|
|
|
|
|
+
|
|
|
|
|
+ // Little extra to ensure the boundary is crossed. max() to ensure the value isn't too
|
|
|
|
|
+ // small to prevent it ever leaving a cell. Note that this clamping results in a quality loss,
|
|
|
|
|
+ // you want to keep the clamp value as low as possible, but not too low to avoid artifacts.
|
|
|
|
|
+ minIntersect = max(minIntersect * 1.05f, 1.0f / (bufferSize * 512)); // 1/512th of a pixel
|
|
|
|
|
+
|
|
|
|
|
+ // Move the ray past the boundary
|
|
|
|
|
+ rayPos = O + D * (rayPos.z + minIntersect);
|
|
|
|
|
+
|
|
|
|
|
+ if(mipLevel < maxMipLevel)
|
|
|
|
|
+ {
|
|
|
|
|
+ ++mipLevel;
|
|
|
|
|
+ bufferSize >>= 1;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
- else
|
|
|
|
|
|
|
+ else // Intersection with floor, move to higher quality mip
|
|
|
{
|
|
{
|
|
|
- // We hit the cell wall, meaning we should move to the next cell
|
|
|
|
|
- rayPos = rayStart + rayDir * maxT * 1.04;
|
|
|
|
|
|
|
+ rayPos = newRay;
|
|
|
|
|
+ --mipLevel;
|
|
|
|
|
|
|
|
- // Decrease detail level
|
|
|
|
|
- int oldMipLevel = mipLevel;
|
|
|
|
|
|
|
+ if(mipLevel < 0)
|
|
|
|
|
+ {
|
|
|
|
|
+ hitPos = rayPos;
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- mipLevel = min(maxMipLevel, mipLevel + 1);
|
|
|
|
|
- bufferSize >>= (mipLevel - oldMipLevel);
|
|
|
|
|
|
|
+ bufferSize <<= 1;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- iterationCount += 1.0f;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ ++iterationCount;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
|
|
+ hitPos = rayPos;
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -179,14 +268,16 @@ mixin RayMarch
|
|
|
|
|
|
|
|
// Always do three steps of linear search
|
|
// Always do three steps of linear search
|
|
|
// (HiZ search is more expensive for short runs)
|
|
// (HiZ search is more expensive for short runs)
|
|
|
- if(linearSearch(depth, samp, uvStart, uvStep, 3, stepIncrement, compareTolerance, t))
|
|
|
|
|
- return float4(uvStart + uvStep * t, t);
|
|
|
|
|
|
|
+ //if(linearSearch(depth, samp, uvStart, uvStep, 3, stepIncrement, compareTolerance, t))
|
|
|
|
|
+ // return float4(uvStart + uvStep * t, t);
|
|
|
|
|
|
|
|
#if HI_Z
|
|
#if HI_Z
|
|
|
|
|
|
|
|
// Hierarchical search
|
|
// Hierarchical search
|
|
|
- if(hiZSearch(depth, samp, params.bufferSize, params.numMips, uvStart, normalize(uvStep), t))
|
|
|
|
|
- return float4(uvStart + uvStep * t, t);
|
|
|
|
|
|
|
+ float3 rayPos = uvStart + uvStep * t;
|
|
|
|
|
+ float3 hitPos;
|
|
|
|
|
+ if(hiZSearch(depth, samp, params.bufferSize, params.numMips, rayPos, uvStep, hitPos, dbg))
|
|
|
|
|
+ return float4(NDCToUV((hitPos.xy - params.hiZUVMapping.zw) / params.hiZUVMapping.xy), hitPos.z, 0);
|
|
|
#else
|
|
#else
|
|
|
|
|
|
|
|
// Plain linear search
|
|
// Plain linear search
|