RayMarch.bslinc 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #include "$ENGINE$/PerCameraData.bslinc"
  2. mixin RayMarch
  3. {
  4. mixin PerCameraData;
  5. code
  6. {
  7. #ifndef NUM_STEPS
  8. #define NUM_STEPS 12
  9. #endif
  10. #ifndef HI_Z
  11. #define HI_Z 0
  12. #endif
  13. #define MAX_HIZ_ITERATIONS 9
  14. #define HIZ_START_LEVEL 1
  15. float3 viewToNDC(float3 view)
  16. {
  17. float4 projected = mul(gMatProj, float4(view, 1));
  18. projected.xyz /= projected.w;
  19. return projected.xyz;
  20. }
  21. bool linearSearch(Texture2D depth, SamplerState samp, float3 rayStart, float3 rayStep, int numSteps, float stepIncrement, float compareTolerance, inout float t)
  22. {
  23. float lastDiff = 0.0f;
  24. [unroll]
  25. for(int i = 0; i < numSteps; ++i)
  26. {
  27. float3 rayPos = rayStart + rayStep * t;
  28. #if HI_Z
  29. float sampleDepth = depth.Sample(samp, rayPos.xy).r;
  30. #else
  31. float sampleDepth = depth.SampleLevel(samp, rayPos.xy, 0).r;
  32. #endif
  33. float depthDiff = rayPos.z - sampleDepth;
  34. bool hit = depthDiff > -compareTolerance;
  35. if(hit)
  36. {
  37. // Refine hit using line segment intersection
  38. float tt = lastDiff / (depthDiff - lastDiff);
  39. t += tt * stepIncrement + stepIncrement;
  40. return true;
  41. }
  42. lastDiff = depthDiff;
  43. t += stepIncrement;
  44. }
  45. return false;
  46. }
  47. bool hiZSearch(Texture2D depth, SamplerState samp, int2 bufferSize, int maxMipLevel, float3 rayStart, float3 rayDir, inout float t)
  48. {
  49. float iterationCount = 0.0f;
  50. int mipLevel = HIZ_START_LEVEL;
  51. bufferSize >>= mipLevel;
  52. float3 rayPos = rayStart + rayDir * t;
  53. while(mipLevel >= 0 && iterationCount < MAX_HIZ_ITERATIONS)
  54. {
  55. if(any(rayPos < 0.0f) || any(rayPos > 1.0f))
  56. return false; // Reached the end of valid range
  57. // Get position of the ray, relative to the current cell (sub-pixel)
  58. float2 subCellPos = frac(rayPos.xy * bufferSize);
  59. // Move subCellPos to [-1,1] range, as it makes the calculation below easier
  60. subCellPos *= 2.0f - 1.0f;
  61. // Find how much we can move the ray (in "t") before we hit a cell wall
  62. //// We want: subCellPos + |rayDir| * t = 1
  63. //// Solve for t: t = (1 - subCellPos) / |rayDir|
  64. float epsilon = 0.00001f; // Handle div by zero
  65. float2 maxXY = (1.0f - subCellPos) / abs(rayDir.xy + epsilon);
  66. float maxT = min(maxXY.x, maxXY.y);
  67. // Get depth of the current cell
  68. float cellZ = depth.SampleLevel(samp, rayPos.xy, mipLevel).r;
  69. // Find intersection with the cell
  70. //// We want: rayPos.z + rayDir.z * t = cellZ
  71. //// Solve for t: t = (cellZ - rayPos.z) / rayDir.z
  72. t = (cellZ - rayPos.z) / rayDir.z;
  73. // The hit was within the cell walls, meaning we hit the floor of the cell (ray depth is higher than cell depth)
  74. float hitBias = 0.002;
  75. if(t < (maxT + hitBias))
  76. {
  77. // We're at the highest detail level, hit found
  78. if(mipLevel < 1)
  79. return true;
  80. // Increase detail level and refine search
  81. mipLevel -= 1;
  82. bufferSize <<= 1;
  83. }
  84. else
  85. {
  86. // We hit the cell wall, meaning we should move to the next cell
  87. rayPos = rayStart + rayDir * maxT * 1.04;
  88. // Decrease detail level
  89. int oldMipLevel = mipLevel;
  90. mipLevel = min(maxMipLevel, mipLevel + 1);
  91. bufferSize >>= (mipLevel - oldMipLevel);
  92. }
  93. iterationCount += 1.0f;
  94. }
  95. return false;
  96. }
  97. struct RayMarchParams
  98. {
  99. int2 bufferSize;
  100. int numMips;
  101. float4 hiZUVMapping; // From NDC to HiZ UV. .xy - multiply, .zw - add
  102. float3 rayOrigin; // World space
  103. float3 rayDir; // World space
  104. float rayLength;
  105. float jitterOffset;
  106. };
  107. float4 rayMarch(Texture2D depth, SamplerState samp, RayMarchParams params)
  108. {
  109. float3 viewOrigin = mul(float4(params.rayOrigin, 1), gMatView);
  110. float3 viewDir = mul(float4(params.rayDir, 0), gMatView);
  111. // Clip ray length so it doesn't go past the near plane
  112. float rayLength = (viewOrigin.z + viewDir.z * params.rayLength) > gNearFar.x
  113. ? (gNearFar.x - viewOrigin.z) / viewDir.z
  114. : params.rayLength;
  115. float3 ndcStart = viewToNDC(viewOrigin);
  116. float3 ndcEnd = viewToNDC(viewOrigin + viewDir * rayLength);
  117. float3 ndcStep = ndcEnd - ndcStart;
  118. // Resize ray so it reaches screen edge
  119. //// We want: start + |step| * t = 1
  120. //// Solve for t: t = (1 - start) / |step|
  121. //// 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:
  122. //// t = 1/|step| - start/step
  123. float epsilon = 0.00001f; // Handle div by zero
  124. float2 stepScale = 1.0f / abs(ndcStep.xy + epsilon) - ndcStart.xy/(ndcStep.xy + epsilon);
  125. ndcStep *= min(stepScale.x, stepScale.y);
  126. #if HI_Z
  127. float3 uvStart;
  128. uvStart.xy = ndcStart.xy * params.hiZUVMapping.xy + params.hiZUVMapping.zw;
  129. uvStart.z = NDCZToDeviceZ(ndcStart.z);
  130. float3 uvStep;
  131. uvStep.xy = ndcStep.xy * params.hiZUVMapping.xy + params.hiZUVMapping.zw;
  132. uvStep.z = NDCZToDeviceZ(ndcStep.z);
  133. #else
  134. float3 uvStart = float3(NDCToUV(ndcStart.xy), NDCZToDeviceZ(ndcStart.z));
  135. float3 uvStep = float3(NDCToUV(ndcStep.xy), NDCZToDeviceZ(ndcStep.z));
  136. #endif
  137. float stepIncrement = 1.0f / NUM_STEPS;
  138. // Offset starting position to avoid self-intersection. Use random values to avoid
  139. // staircase artifacts.
  140. float t = stepIncrement + stepIncrement * params.jitterOffset;
  141. // Note: Perhaps tweak this value
  142. float compareTolerance = uvStep.z * stepIncrement;
  143. // Always do three steps of linear search
  144. // (HiZ search is more expensive for short runs)
  145. if(linearSearch(depth, samp, uvStart, uvStep, 3, stepIncrement, compareTolerance, t))
  146. return float4(uvStart + uvStep * t, t);
  147. #if HI_Z
  148. // Hierarchical search
  149. if(hiZSearch(depth, samp, params.bufferSize, params.numMips, uvStart, normalize(uvStep), t))
  150. return float4(uvStart + uvStep * t, t);
  151. #else
  152. // Plain linear search
  153. if(linearSearch(depth, samp, uvStart, uvStep, NUM_STEPS - 3, stepIncrement, compareTolerance, t))
  154. return float4(uvStart + uvStep * t, t);
  155. #endif
  156. // Hit not found
  157. return float4(0, 0, 0, 1);
  158. }
  159. };
  160. };