You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
7.9 KiB

  1. /**
  2. \author Michael Mara and Morgan McGuire, Casual Effects. 2015.
  3. */
  4. #ifndef __SCREEN_SPACE_RAYTRACE__
  5. #define __SCREEN_SPACE_RAYTRACE__
  6. sampler2D_float _CameraDepthTexture;
  7. float distanceSquared(float2 A, float2 B)
  8. {
  9. A -= B;
  10. return dot(A, A);
  11. }
  12. float distanceSquared(float3 A, float3 B)
  13. {
  14. A -= B;
  15. return dot(A, A);
  16. }
  17. void swap(inout float v0, inout float v1)
  18. {
  19. float temp = v0;
  20. v0 = v1;
  21. v1 = temp;
  22. }
  23. bool isIntersecting(float rayZMin, float rayZMax, float sceneZ, float layerThickness)
  24. {
  25. return (rayZMax >= sceneZ - layerThickness) && (rayZMin <= sceneZ);
  26. }
  27. void rayIterations(in bool traceBehindObjects, inout float2 P, inout float stepDirection, inout float end, inout int stepCount, inout int maxSteps, inout bool intersecting,
  28. inout float sceneZ, inout float2 dP, inout float3 Q, inout float3 dQ, inout float k, inout float dk,
  29. inout float rayZMin, inout float rayZMax, inout float prevZMaxEstimate, inout bool permute, inout float2 hitPixel,
  30. inout float2 invSize, inout float layerThickness)
  31. {
  32. bool stop = intersecting;
  33. UNITY_LOOP
  34. for (; (P.x * stepDirection) <= end && stepCount < maxSteps && !stop; P += dP, Q.z += dQ.z, k += dk, stepCount += 1)
  35. {
  36. // The depth range that the ray covers within this loop iteration.
  37. // Assume that the ray is moving in increasing z and swap if backwards.
  38. rayZMin = prevZMaxEstimate;
  39. //rayZMin = (dQ.z * -0.5 + Q.z) / (dk * -0.5 + k);
  40. // Compute the value at 1/2 pixel into the future
  41. rayZMax = (dQ.z * 0.5 + Q.z) / (dk * 0.5 + k);
  42. prevZMaxEstimate = rayZMax;
  43. if (rayZMin > rayZMax)
  44. {
  45. swap(rayZMin, rayZMax);
  46. }
  47. // Undo the homogeneous operation to obtain the camera-space
  48. // Q at each point
  49. hitPixel = permute ? P.yx : P;
  50. sceneZ = tex2Dlod(_CameraDepthTexture, float4(hitPixel * invSize,0,0)).r;
  51. sceneZ = -LinearEyeDepth(sceneZ);
  52. bool isBehind = (rayZMin <= sceneZ);
  53. intersecting = isBehind && (rayZMax >= sceneZ - layerThickness);
  54. stop = traceBehindObjects ? intersecting : isBehind;
  55. } // pixel on ray
  56. P -= dP, Q.z -= dQ.z, k -= dk;
  57. }
  58. /**
  59. \param csOrigin must have z < -0.01, and project within the valid screen rectangle
  60. \param stepRate Set to 1.0 by default, higher to step faster
  61. */
  62. bool castDenseScreenSpaceRay
  63. (float3 csOrigin,
  64. float3 csDirection,
  65. float4x4 projectToPixelMatrix,
  66. float2 csZBufferSize,
  67. float3 clipInfo,
  68. float jitterFraction,
  69. int maxSteps,
  70. float layerThickness,
  71. float maxRayTraceDistance,
  72. out float2 hitPixel,
  73. int stepRate,
  74. bool traceBehindObjects,
  75. out float3 csHitPoint,
  76. out float stepCount) {
  77. float2 invSize = float2(1.0 / csZBufferSize.x, 1.0 / csZBufferSize.y);
  78. // Initialize to off screen
  79. hitPixel = float2(-1, -1);
  80. float nearPlaneZ = -0.01;
  81. // Clip ray to a near plane in 3D (doesn't have to be *the* near plane, although that would be a good idea)
  82. float rayLength = ((csOrigin.z + csDirection.z * maxRayTraceDistance) > nearPlaneZ) ?
  83. ((nearPlaneZ - csOrigin.z) / csDirection.z) :
  84. maxRayTraceDistance;
  85. float3 csEndPoint = csDirection * rayLength + csOrigin;
  86. // Project into screen space
  87. // This matrix has a lot of zeroes in it. We could expand
  88. // out these multiplies to avoid multiplying by zero
  89. // ...but 16 MADDs are not a big deal compared to what's ahead
  90. float4 H0 = mul(projectToPixelMatrix, float4(csOrigin, 1.0));
  91. float4 H1 = mul(projectToPixelMatrix, float4(csEndPoint, 1.0));
  92. // There are a lot of divisions by w that can be turned into multiplications
  93. // at some minor precision loss...and we need to interpolate these 1/w values
  94. // anyway.
  95. //
  96. // Because the caller was required to clip to the near plane,
  97. // this homogeneous division (projecting from 4D to 2D) is guaranteed
  98. // to succeed.
  99. float k0 = 1.0 / H0.w;
  100. float k1 = 1.0 / H1.w;
  101. // Screen-space endpoints
  102. float2 P0 = H0.xy * k0;
  103. float2 P1 = H1.xy * k1;
  104. // Switch the original points to values that interpolate linearly in 2D:
  105. float3 Q0 = csOrigin * k0;
  106. float3 Q1 = csEndPoint * k1;
  107. #if 1 // Clipping to the screen coordinates. We could simply modify maxSteps instead
  108. float yMax = csZBufferSize.y - 0.5;
  109. float yMin = 0.5;
  110. float xMax = csZBufferSize.x - 0.5;
  111. float xMin = 0.5;
  112. // 2D interpolation parameter
  113. float alpha = 0.0;
  114. // P0 must be in bounds
  115. if (P1.y > yMax || P1.y < yMin) {
  116. float yClip = (P1.y > yMax) ? yMax : yMin;
  117. float yAlpha = (P1.y - yClip) / (P1.y - P0.y); // Denominator is not zero, since P0 != P1 (or P0 would have been clipped!)
  118. alpha = yAlpha;
  119. }
  120. // P0 must be in bounds
  121. if (P1.x > xMax || P1.x < xMin) {
  122. float xClip = (P1.x > xMax) ? xMax : xMin;
  123. float xAlpha = (P1.x - xClip) / (P1.x - P0.x); // Denominator is not zero, since P0 != P1 (or P0 would have been clipped!)
  124. alpha = max(alpha, xAlpha);
  125. }
  126. // These are all in homogeneous space, so they interpolate linearly
  127. P1 = lerp(P1, P0, alpha);
  128. k1 = lerp(k1, k0, alpha);
  129. Q1 = lerp(Q1, Q0, alpha);
  130. #endif
  131. // We're doing this to avoid divide by zero (rays exactly parallel to an eye ray)
  132. P1 = (distanceSquared(P0, P1) < 0.0001) ? P0 + float2(0.01, 0.01) : P1;
  133. float2 delta = P1 - P0;
  134. // Assume horizontal
  135. bool permute = false;
  136. if (abs(delta.x) < abs(delta.y)) {
  137. // More-vertical line. Create a permutation that swaps x and y in the output
  138. permute = true;
  139. // Directly swizzle the inputs
  140. delta = delta.yx;
  141. P1 = P1.yx;
  142. P0 = P0.yx;
  143. }
  144. // From now on, "x" is the primary iteration direction and "y" is the secondary one
  145. float stepDirection = sign(delta.x);
  146. float invdx = stepDirection / delta.x;
  147. float2 dP = float2(stepDirection, invdx * delta.y);
  148. // Track the derivatives of Q and k
  149. float3 dQ = (Q1 - Q0) * invdx;
  150. float dk = (k1 - k0) * invdx;
  151. dP *= stepRate;
  152. dQ *= stepRate;
  153. dk *= stepRate;
  154. P0 += dP * jitterFraction;
  155. Q0 += dQ * jitterFraction;
  156. k0 += dk * jitterFraction;
  157. // Slide P from P0 to P1, (now-homogeneous) Q from Q0 to Q1, and k from k0 to k1
  158. float3 Q = Q0;
  159. float k = k0;
  160. // We track the ray depth at +/- 1/2 pixel to treat pixels as clip-space solid
  161. // voxels. Because the depth at -1/2 for a given pixel will be the same as at
  162. // +1/2 for the previous iteration, we actually only have to compute one value
  163. // per iteration.
  164. float prevZMaxEstimate = csOrigin.z;
  165. stepCount = 0.0;
  166. float rayZMax = prevZMaxEstimate, rayZMin = prevZMaxEstimate;
  167. float sceneZ = 100000;
  168. // P1.x is never modified after this point, so pre-scale it by
  169. // the step direction for a signed comparison
  170. float end = P1.x * stepDirection;
  171. bool intersecting = isIntersecting(rayZMin, rayZMax, sceneZ, layerThickness);
  172. // We only advance the z field of Q in the inner loop, since
  173. // Q.xy is never used until after the loop terminates
  174. //int rayIterations = min(maxSteps, stepsToGetOffscreen);
  175. float2 P = P0;
  176. int originalStepCount = 0;
  177. rayIterations(traceBehindObjects, P, stepDirection, end, originalStepCount, maxSteps, intersecting,
  178. sceneZ, dP, Q, dQ, k, dk,
  179. rayZMin, rayZMax, prevZMaxEstimate, permute, hitPixel,
  180. invSize, layerThickness);
  181. stepCount = originalStepCount;
  182. // Loop only advanced the Z component. Now that we know where we are going
  183. // update xy
  184. Q.xy += dQ.xy * stepCount;
  185. // Q is a vector, so we are trying to get by with 1 division instead of 3.
  186. csHitPoint = Q * (1.0 / k);
  187. return intersecting;
  188. }
  189. #endif // __SCREEN_SPACE_RAYTRACE__