| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- #include "$ENGINE$/PerCameraData.bslinc"
- mixin RayMarch
- {
- mixin PerCameraData;
- code
- {
- #ifndef NUM_STEPS
- #define NUM_STEPS 16
- #endif
-
- #ifndef HI_Z
- #define HI_Z 0
- #endif
-
- // 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)
- {
- float4 projected = mul(gMatProj, float4(view, 1));
- projected.xyz /= projected.w;
-
- return projected.xyz;
- }
-
- bool linearSearch(Texture2D depth, SamplerState samp, float3 rayStart, float3 rayStep, int numSteps, float stepIncrement, float compareTolerance, inout float t)
- {
- float lastDiff = 0.0f;
-
- [unroll]
- for(int i = 0; i < numSteps; ++i)
- {
- float3 rayPos = rayStart + rayStep * t;
- #if HI_Z
- float sampleDepth = depth.Sample(samp, rayPos.xy).r;
- #else
- float sampleDepth = depth.SampleLevel(samp, rayPos.xy, 0).r;
- #endif
-
- // Check if ray is behind an object, but not too much behind otherwise we'll have false positives.
- // Instead we treat "compareTolerance" as an approximate thickness of the object. Proper
- // thickness should be calculated by rendering depth buffer for backfaces.
- float depthDiff = rayPos.z - sampleDepth;
- bool hit = abs(depthDiff - compareTolerance) < compareTolerance;
- if(hit)
- {
- // Refine hit using line segment intersection
- float tt = lastDiff / (depthDiff - lastDiff);
- t += tt * stepIncrement + stepIncrement;
-
- return true;
- }
-
- lastDiff = depthDiff;
- t += stepIncrement;
- }
-
- return false;
- }
- bool hiZSearch(Texture2D depth, SamplerState samp, int2 bufferSize, int maxMipLevel, float3 rayPos, float3 rayStep, out float3 hitPos)
- {
- float 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
- 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)
-
- // Get pixel coordinates of the new ray's cell
- float2 newCellIdx = trunc(newRay.xy * bufferSize);
-
- // If we moved to another cell, no intersection with floor
- if(any(curCellIdx != newCellIdx))
- {
- 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 // Intersection with floor, move to higher quality mip
- {
- rayPos = newRay;
- --mipLevel;
-
- if(mipLevel < 0)
- {
- hitPos = rayPos;
- return true;
- }
-
- bufferSize <<= 1;
- }
- ++iterationCount;
- }
-
- hitPos = rayPos;
- return false;
- }
-
- struct RayMarchParams
- {
- int2 bufferSize;
- int numMips;
- float4 NDCToHiZUV; // From NDC to HiZ UV. .xy - multiply, .zw - add
- float2 HiZUVToScreenUV; // From HiZ UV to screen UV. .xy - multiply
- float3 rayOrigin; // World space
- float3 rayDir; // World space
- float jitterOffset;
- };
-
- float4 rayMarch(Texture2D depth, SamplerState samp, RayMarchParams params)
- {
- float3 viewOrigin = mul(gMatView, float4(params.rayOrigin, 1));
- float3 viewDir = mul(gMatView, float4(params.rayDir, 0));
-
- float3 ndcStart = viewToNDC(viewOrigin);
- float3 ndcEnd = viewToNDC(viewOrigin + viewDir);
- float3 ndcStep = ndcEnd - ndcStart;
-
- // Resize ray so it reaches screen edge
- //// We want: start + |step| * t = 1
- //// Solve for t: t = (1 - start) / |step|
- //// This has two solutions, but we can handle them both in a single equation by flipping sign depending on "step", on only one of the components:
- //// t = 1/|step| - start/step
- float epsilon = 0.00001f; // Handle div by zero
- float2 stepScale = 1.0f / abs(ndcStep.xy + epsilon) - ndcStart.xy/(ndcStep.xy + epsilon);
- ndcStep *= min(stepScale.x, stepScale.y);
-
- float deviceZStart = NDCZToDeviceZ(ndcStart.z);
- float deviceZEnd = NDCZToDeviceZ(ndcStart.z + ndcStep.z);
-
- #if HI_Z
- float3 uvStart;
- uvStart.xy = ndcStart.xy * params.NDCToHiZUV.xy + params.NDCToHiZUV.zw;
- uvStart.z = deviceZStart;
-
- float3 uvStep;
- uvStep.xy = ndcStep.xy * params.NDCToHiZUV.xy;
- uvStep.z = deviceZEnd - deviceZStart;
-
- #else
- float3 uvStart = float3(NDCToUV(ndcStart.xy), deviceZStart);
- float3 uvStep = float3(ndcStep.xy * gClipToUVScaleOffset.xy, deviceZEnd - deviceZStart);
- #endif
-
- float stepIncrement = 1.0f / NUM_STEPS;
- // Offset starting position to avoid self-intersection. Use random values to avoid
- // staircase artifacts.
- float t = stepIncrement + stepIncrement * params.jitterOffset;
-
- // Note: Perhaps tweak this value
- float compareTolerance = uvStep.z * stepIncrement * 2.0f;
-
- #if HI_Z
-
- // Note: Perhaps do a few steps of linear search first to handle nearby surfaces
-
- // Hierarchical search
- float3 rayPos = uvStart + uvStep * t;
- float3 hitPos;
- if(hiZSearch(depth, samp, params.bufferSize, params.numMips, rayPos, uvStep, hitPos))
- return float4(hitPos.xy * params.HiZUVToScreenUV.xy, hitPos.z, 0);
-
- #else
-
- // Plain linear search
- if(linearSearch(depth, samp, uvStart, uvStep, NUM_STEPS, stepIncrement, compareTolerance, t))
- return float4(uvStart + uvStep * t, t);
- #endif
-
- // Hit not found
- return float4(0, 0, 0, 1);
- }
- };
- };
|