SsRaymarching.hlsl 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
  2. // All rights reserved.
  3. // Code licensed under the BSD License.
  4. // http://www.anki3d.org/LICENSE
  5. // Screen space ray marching
  6. #pragma once
  7. #include <AnKi/Shaders/Functions.hlsl>
  8. // If the angle between the reflection and the normal grows more than 90 deg than the return value turns to zero
  9. F32 rejectBackFaces(Vec3 reflection, Vec3 normalAtHitPoint)
  10. {
  11. return smoothstep(-0.17, 0.0, dot(normalAtHitPoint, -reflection));
  12. }
  13. // Find the intersection of a ray and a AABB when the ray is inside the AABB
  14. void rayAabbIntersectionInside2d(Vec2 rayOrigin, Vec2 rayDir, Vec2 aabbMin, Vec2 aabbMax, out F32 t)
  15. {
  16. // Find the boundary of the AABB that the rayDir points at
  17. Vec2 boundary;
  18. boundary.x = (rayDir.x > 0.0) ? aabbMax.x : aabbMin.x;
  19. boundary.y = (rayDir.y > 0.0) ? aabbMax.y : aabbMin.y;
  20. // Find the intersection of the ray with the line y=boundary.y
  21. // The intersection is: rayOrigin + T * rayDir
  22. // For y it's: rayOrigin.y + T * rayDir.y
  23. // And it's equal to the boundary.y: rayOrigin.y + T * rayDir.y = boundary.y
  24. const F32 ty = (boundary.y - rayOrigin.y) / rayDir.y;
  25. // Same for x=boundary.x
  26. const F32 tx = (boundary.x - rayOrigin.x) / rayDir.x;
  27. // Chose the shortest t
  28. t = min(ty, tx);
  29. }
  30. // Find the cell the rayOrigin is in and push it outside that cell towards the direction of the rayDir
  31. void stepToNextCell(Vec3 rayOrigin, Vec3 rayDir, U32 mipLevel, UVec2 hizSize, out Vec3 newRayOrigin)
  32. {
  33. const UVec2 mipSize = hizSize >> mipLevel;
  34. const Vec2 mipSizef = Vec2(mipSize);
  35. // Position in texture space
  36. const Vec2 texPos = rayOrigin.xy * mipSizef;
  37. // Compute the boundaries of the cell in UV space
  38. const Vec2 cellMin = floor(texPos) / mipSizef;
  39. const Vec2 cellMax = ceil(texPos) / mipSizef;
  40. // Find the intersection
  41. F32 t;
  42. rayAabbIntersectionInside2d(rayOrigin.xy, rayDir.xy, cellMin, cellMax, t);
  43. // Bump t a bit to stop touching the cell
  44. const F32 texelSizeX = 1.0 / mipSizef.x;
  45. t += texelSizeX / 10.0;
  46. // Compute the new origin
  47. newRayOrigin = rayOrigin + rayDir * t;
  48. }
  49. // Note: All calculations in view space
  50. void raymarch(Vec3 rayOrigin, // Ray origin in view space
  51. Vec3 rayDir, // Ray dir in view space
  52. F32 tmin, // Shoot rays from
  53. Vec2 uv, // UV the ray starts
  54. F32 depthRef, // Depth the ray starts
  55. Mat4 projMat, // Projection matrix
  56. U32 randFrom0To3, U32 maxIterations, Texture2D hizTex, SamplerState hizSampler, U32 hizMipCount, UVec2 hizMip0Size, out Vec3 hitPoint,
  57. out F32 attenuation)
  58. {
  59. ANKI_MAYBE_UNUSED(uv);
  60. ANKI_MAYBE_UNUSED(depthRef);
  61. attenuation = 0.0;
  62. // Check for view facing reflections [sakibsaikia]
  63. const Vec3 viewDir = normalize(rayOrigin);
  64. const F32 cameraContribution = 1.0 - smoothstep(0.25, 0.5, dot(-viewDir, rayDir));
  65. [branch] if(cameraContribution <= 0.0)
  66. {
  67. return;
  68. }
  69. // Dither and set starting pos
  70. const F32 bayerMat[4] = {1.0, 4.0, 2.0, 3.0};
  71. const Vec3 p0 = rayOrigin + rayDir * (tmin * bayerMat[randFrom0To3]);
  72. // p1
  73. const F32 tmax = 10.0;
  74. const Vec3 p1 = rayOrigin + rayDir * tmax;
  75. // Compute start & end in clip space (well not clip space since x,y are in [0, 1])
  76. Vec4 v4 = mul(projMat, Vec4(p0, 1.0));
  77. Vec3 start = v4.xyz / v4.w;
  78. start.xy = ndcToUv(start.xy);
  79. v4 = mul(projMat, Vec4(p1, 1.0));
  80. Vec3 end = v4.xyz / v4.w;
  81. end.xy = ndcToUv(end.xy);
  82. // Ray
  83. Vec3 origin = start;
  84. const Vec3 dir = normalize(end - start);
  85. // Start looping
  86. I32 mipLevel = 0;
  87. while(mipLevel > -1 && maxIterations > 0u)
  88. {
  89. // Step to the next cell
  90. Vec3 newOrigin;
  91. stepToNextCell(origin, dir, U32(mipLevel), hizMip0Size, newOrigin);
  92. origin = newOrigin;
  93. if(all(origin.xy > Vec2(0.0, 0.0)) && all(origin.xy < Vec2(1.0, 1.0)))
  94. {
  95. const F32 newDepth = hizTex.SampleLevel(hizSampler, origin.xy, F32(mipLevel)).r;
  96. if(origin.z < newDepth)
  97. {
  98. // In front of depth
  99. mipLevel = min(mipLevel + 1, I32(hizMipCount - 1u));
  100. }
  101. else
  102. {
  103. // Behind depth
  104. const F32 t = (origin.z - newDepth) / dir.z;
  105. origin -= dir * t;
  106. --mipLevel;
  107. }
  108. --maxIterations;
  109. }
  110. else
  111. {
  112. // Out of the screen
  113. break;
  114. }
  115. }
  116. // Write the values
  117. const F32 blackMargin = 0.05 / 4.0;
  118. const F32 whiteMargin = 0.1 / 2.0;
  119. const Vec2 marginAttenuation2d =
  120. smoothstep(blackMargin, whiteMargin, origin.xy) * (1.0 - smoothstep(1.0 - whiteMargin, 1.0 - blackMargin, origin.xy));
  121. const F32 marginAttenuation = marginAttenuation2d.x * marginAttenuation2d.y;
  122. attenuation = marginAttenuation * cameraContribution;
  123. hitPoint = origin;
  124. }
  125. struct RayMarchingConfig
  126. {
  127. U32 m_maxIterations; // The max iterations of the base algorithm
  128. F32 m_depthTextureLod; // LOD to pass to the SampleLevel of the depth texture
  129. U32 m_stepIncrement; // The step increment of each iteration
  130. U32 m_initialStepIncrement; // The initial step
  131. Bool m_backfaceRejection; // If it's zero then no rejection will happen
  132. F32 m_minRayToBackgroundDistance; // Distance between the hit point on the ray and the hit point that is touching the depth buffer.
  133. // Make it kMaxF32 to disable the test
  134. };
  135. struct DummySampleNormalFunc
  136. {
  137. Vec3 sample(Vec2 uv)
  138. {
  139. return 0.0;
  140. }
  141. };
  142. // Note: All calculations in view space
  143. template<typename TSampleNormalFunc>
  144. Bool raymarchGroundTruth(Vec3 rayOrigin, // Ray origin in view space
  145. Vec3 rayDir, // Ray dir in view space
  146. Vec2 uv, // UV the ray starts
  147. F32 depthRef, // Depth the ray starts
  148. Vec4 projMat00_11_22_23, // Projection matrix
  149. Vec4 unprojectionParams,
  150. Texture2D<Vec4> depthTex, // Depth tex
  151. SamplerState depthSampler, // Sampler for depthTex
  152. RayMarchingConfig config,
  153. TSampleNormalFunc sampleNormalFunc, // Only used if m_backfaceRejection is true
  154. out Vec3 hitPoint, // Hit point in view space
  155. out Vec2 hitUv, out F32 hitDepth)
  156. {
  157. hitPoint = rayOrigin;
  158. hitUv = uv;
  159. hitDepth = 0.0;
  160. // Check for view facing reflections [sakibsaikia]
  161. const Vec3 viewDir = normalize(rayOrigin);
  162. const F32 cameraContribution = 1.0 - smoothstep(0.25, 0.5, dot(-viewDir, rayDir));
  163. if(cameraContribution <= 0.0)
  164. {
  165. return false;
  166. }
  167. // Find the depth's mipmap size
  168. UVec2 depthTexSize;
  169. U32 depthTexMipCount;
  170. depthTex.GetDimensions(0u, depthTexSize.x, depthTexSize.y, depthTexMipCount);
  171. config.m_depthTextureLod = min(config.m_depthTextureLod, F32(depthTexMipCount) - 1.0f);
  172. const UVec2 depthTexMipSize = depthTexSize >> U32(config.m_depthTextureLod);
  173. // Start point
  174. const Vec3 start = Vec3(uv, depthRef);
  175. // Project end point
  176. const Vec3 p1 = rayOrigin + rayDir * 0.1;
  177. const Vec4 end4 = cheapPerspectiveProjection(projMat00_11_22_23, Vec4(p1, 1.0));
  178. Vec3 end = end4.xyz / end4.w;
  179. end.xy = ndcToUv(end.xy);
  180. // Compute the ray and step size
  181. Vec3 dir = end - start;
  182. const Vec2 dir2d = normalize(dir.xy);
  183. dir = dir * (dir2d.x / dir.x); // Normalize only on xy
  184. const F32 stepSize = 1.0 / ((dir.x > dir.y) ? depthTexMipSize.x : depthTexMipSize.y);
  185. // Compute step
  186. I32 stepIncrement = I32(config.m_stepIncrement);
  187. I32 crntStep = I32(config.m_initialStepIncrement);
  188. // Search
  189. [loop] while(config.m_maxIterations-- != 0u)
  190. {
  191. const Vec3 newHit = start + dir * (F32(crntStep) * stepSize);
  192. // Check if it's out of the view
  193. if(any(newHit <= 0.0) || any(newHit >= 1.0))
  194. {
  195. return false;
  196. }
  197. const F32 depth = depthTex.SampleLevel(depthSampler, newHit.xy, config.m_depthTextureLod).r;
  198. const Bool hit = newHit.z >= depth;
  199. // Move the hit point to where the depth is
  200. Vec3 newHitPoint = cheapPerspectiveUnprojection(unprojectionParams, uvToNdc(newHit.xy), newHit.z);
  201. if(!hit)
  202. {
  203. crntStep += stepIncrement;
  204. hitPoint = newHitPoint;
  205. hitUv = newHit.xy;
  206. hitDepth = depth;
  207. }
  208. else if(stepIncrement > 1)
  209. {
  210. // There is a hit but the step increment is a bit high, need a more fine-grained search
  211. crntStep = max(1, crntStep - stepIncrement + 1);
  212. stepIncrement = stepIncrement / 2;
  213. }
  214. else
  215. {
  216. // Maybe found it
  217. if(config.m_backfaceRejection)
  218. {
  219. const Vec3 hitNormal = sampleNormalFunc.sample(newHit.xy);
  220. const F32 backFaceAttenuation = rejectBackFaces(rayDir, hitNormal);
  221. if(backFaceAttenuation <= 0.5)
  222. {
  223. return false;
  224. }
  225. }
  226. if(config.m_minRayToBackgroundDistance < kMaxF32)
  227. {
  228. const Vec3 pointInDepth = cheapPerspectiveUnprojection(unprojectionParams, uvToNdc(newHit.xy), depth);
  229. const F32 dist = length(newHitPoint - pointInDepth);
  230. if(dist > config.m_minRayToBackgroundDistance)
  231. {
  232. return false;
  233. }
  234. newHitPoint = pointInDepth;
  235. }
  236. // Found it
  237. hitPoint = newHitPoint;
  238. hitUv = newHit.xy;
  239. hitDepth = depth;
  240. return true;
  241. }
  242. }
  243. return false;
  244. }