TemporalResolve.bslinc 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484
  1. #include "$ENGINE$/PerCameraData.bslinc"
  2. #include "$ENGINE$/ColorSpace.bslinc"
  3. mixin TemporalResolve
  4. {
  5. mixin PerCameraData;
  6. mixin ColorSpace;
  7. code
  8. {
  9. ////////////////// CUSTOMIZATION PARAMETERS /////////////////////////////
  10. // When enabled, the system will sample a specific sample from a MS texture. UV coordinates are assumed
  11. // to be in pixel space in that case. When disabled sampleIdx parameter is ignored and UV coordinates
  12. // are assumed be in standard [0, 1] range.
  13. #ifndef MSAA
  14. #define MSAA 0
  15. #endif
  16. // Only relevant when MSAA is enabled. When disabled color textures are assumed to be non-MSAA. When
  17. // enabled all textures are assumed to be MSAA.
  18. #ifndef MSAA_COLOR
  19. #define MSAA_COLOR MSAA
  20. #endif
  21. // 0 - System will use the velocity of the current pixel
  22. // 1 - System will search 4 neighbor pixels in + pattern, and choose the velocity of the pixel nearest
  23. // to the camera
  24. // 2 - System will search 8 surrounding pixels and choose the velocity of the pixel nearest to the camera
  25. //
  26. // Searching the neighborhod instead of just using current velocity yields nicer edges for objects in
  27. // motion. See TEMPORAL_SEARCH_RADIUS in order to customize how far away to search.
  28. //
  29. // Only relevant if TEMPORAL_LOCAL_VELOCITY is enabled, since without it no per-object velocity
  30. // information is present and everything is blended based on camera movement.
  31. #ifndef TEMPORAL_SEARCH_NEAREST
  32. #define TEMPORAL_SEARCH_NEAREST 1
  33. #endif
  34. // Determine how far away to sample pixels when TEMPORAL_SEARCH_NEAREST is enabled.
  35. // 1 - Immediately adjacent pixels are searched
  36. // 2 - Pixels two away are searched (looks better than 1)
  37. // 3 - etc.
  38. #ifndef TEMPORAL_SEARCH_RADIUS
  39. #define TEMPORAL_SEARCH_RADIUS 2
  40. #endif
  41. // 0 - The system will only account for velocity due to camera movement (not due to individual objects)
  42. // 1 - The system will account both for velocity due to camera movement, as well as individual object
  43. // movement. Requires the user to provide a per-pixel velocity buffer.
  44. #ifndef TEMPORAL_LOCAL_VELOCITY
  45. #define TEMPORAL_LOCAL_VELOCITY 1
  46. #endif
  47. // When enabled, the resolve operation will be performed in YCoCg color space. This can yield better
  48. // results, requires less color samples and no value clipping.
  49. #ifndef TEMPORAL_YCOCG
  50. #define TEMPORAL_YCOCG 0
  51. #endif
  52. // When enabled, green color will be used instead of calculating luminosity. This will yield better
  53. // performance but can result in lower quality. Ignored when TEMPORAL_YCOCG is enabled, since luminosity
  54. // is already available as part of the YCoCg color space.
  55. #ifndef TEMPORAL_GREEN_AS_LUMA
  56. #define TEMPORAL_GREEN_AS_LUMA 0
  57. #endif
  58. // When enabled the input samples will be tonemapped using the provided exposure value. Once the final
  59. // value is resolved, it will be scaled back into original range. This ensures high frequency data from
  60. // HDR content is removed, as it would cause aliasing otherwise. We scale the result back into high range
  61. // so the high-quality tonemap shader can be ran on it.
  62. #ifndef TEMPORAL_TONEMAP
  63. #define TEMPORAL_TONEMAP 1
  64. #endif
  65. // When enabled an extra low-pass filter is ran when sampling scene color, for better quality.
  66. #ifndef TEMPORAL_LOWPASS
  67. #define TEMPORAL_LOWPASS 1
  68. #endif
  69. // When enabled, clamp/clip color neighborhood will be deduced using standard deviation of all the
  70. // neighborhood samples. When disabled a min/max operation is performed instead.
  71. #ifndef TEMPORAL_SMOOTH_NEIGHBORHOOD
  72. #define TEMPORAL_SMOOTH_NEIGHBORHOOD 1
  73. #endif
  74. // When enabled, neighborhood clipping will use an AABB intersection to clip the history value. When disabled
  75. // just a clamp will be used instead. Not relevant when TEMPORAL_YCOCG is enabled because it always uses a clamp.
  76. #ifndef TEMPORAL_CLIP_AABB
  77. #define TEMPORAL_CLIP_AABB 1
  78. #endif
  79. // Determines how is the history value blended with the current value.
  80. // 0 - The system will calculate the optimal blend value automatically
  81. // >0 - A fixed blend factor will be used, equal to the multiplicative inverse of the provided value.
  82. // (i.e. a value of 8 will result in blend factor of 1/8, meaning 12.5% of the history value will be used)
  83. #ifndef TEMPORAL_BLEND_FACTOR
  84. #define TEMPORAL_BLEND_FACTOR 0
  85. #endif
  86. // Determines how many frames should pixels deemed as "bad" (too different from current pixel) contribute to the
  87. // current frame.
  88. #ifndef TEMPORAL_BAD_RETENTION
  89. #define TEMPORAL_BAD_RETENTION 3
  90. #endif
  91. // Determines how many frames should pixels deemed as "good" (similar to the current pixel) contribute to the
  92. // current frame.
  93. #ifndef TEMPORAL_GOOD_RETENTION
  94. #define TEMPORAL_GOOD_RETENTION 10
  95. #endif
  96. ////////////////////////// HELPER MACROS /////////////////////////
  97. #if MSAA
  98. #define _TEX2D(n) Texture2DMS<float> n
  99. #if MSAA_COLOR
  100. #define _TEXCOLOR(n) Texture2DMS<float4> n
  101. #else
  102. #define _TEXCOLOR(n) Texture2D n, SamplerState n##SampState, float2 n##TexelSize
  103. #endif
  104. #define _PTEX2D(n) n
  105. #define _SAMPLE(n, uv) n.Load((int2)uv, sampleIdx)
  106. #define _SAMPLEOFF(n, uv, offset) n.Load((int2)(uv) + offset, sampleIdx)
  107. #if MSAA_COLOR
  108. #define _SAMPLECOL(n, uv, offset) _SAMPLEOFF(n, uv, offset)
  109. #else
  110. #define _SAMPLECOL(n, uv, offset) n.Sample(n##SampState, uv, offset)
  111. #endif
  112. #define _PIXSIZE(n) int2(1, 1)
  113. #else
  114. #define _TEX2D(n) Texture2D n, SamplerState n##SampState, float2 n##TexelSize
  115. #define _TEXCOLOR(n) _TEX2D(n)
  116. #define _PTEX2D(n) n, n##SampState, n##TexelSize
  117. #define _SAMPLE(n, uv) n.Sample(n##SampState, uv)
  118. #define _SAMPLEOFF(n, uv, offset) n.Sample(n##SampState, uv, offset)
  119. #define _SAMPLECOL(n, uv, offset) _SAMPLEOFF(n, uv, offset)
  120. #define _PIXSIZE(n) n##TexelSize
  121. #endif
  122. ///////////////////////// HELPER FUNCTIONS ////////////////////////
  123. float3 findNearest3x3(_TEX2D(sceneDepth), float2 uv, int sampleIdx)
  124. {
  125. int r = TEMPORAL_SEARCH_RADIUS;
  126. float3 dmin = float3(0, 0, 1);
  127. [unroll]
  128. for(int y = -r; y <= r; y += r)
  129. {
  130. [unroll]
  131. for(int x = -r; x <= r; x += r)
  132. {
  133. float depth = _SAMPLEOFF(sceneDepth, uv, int2(x, y)).x;
  134. dmin = depth < dmin.z ? float3(x, y, depth) : dmin;
  135. }
  136. }
  137. return float3(uv + dmin.xy * _PIXSIZE(sceneDepth), dmin.z);
  138. }
  139. float3 findNearestCross(_TEX2D(sceneDepth), float2 uv, int sampleIdx)
  140. {
  141. int r = TEMPORAL_SEARCH_RADIUS;
  142. float3 dmin = float3(0, 0, 1);
  143. {
  144. float depth = _SAMPLE(sceneDepth, uv).x;
  145. dmin = depth < dmin.z ? float3(0, 0, depth) : dmin;
  146. }
  147. {
  148. float depth = _SAMPLEOFF(sceneDepth, uv, int2(-r, 0)).x;
  149. dmin = depth < dmin.z ? float3(-r, 0, depth) : dmin;
  150. }
  151. {
  152. float depth = _SAMPLEOFF(sceneDepth, uv, int2(r, 0)).x;
  153. dmin = depth < dmin.z ? float3(r, 0, depth) : dmin;
  154. }
  155. {
  156. float depth = _SAMPLEOFF(sceneDepth, uv, int2(0, -r)).x;
  157. dmin = depth < dmin.z ? float3(0, -r, depth) : dmin;
  158. }
  159. {
  160. float depth = _SAMPLEOFF(sceneDepth, uv, int2(0, r)).x;
  161. dmin = depth < dmin.z ? float3(0, r, depth) : dmin;
  162. }
  163. return float3(uv + dmin.xy * _PIXSIZE(sceneDepth), dmin.z);
  164. }
  165. float3 clipAABB(float3 boxMin, float3 boxMax, float3 history, float3 current)
  166. {
  167. // Note: Is this necessary? Will "current" always be in the box?
  168. boxMin = min(current, boxMin);
  169. boxMax = max(current, boxMax);
  170. float3 center = (boxMax + boxMin) * 0.5f;
  171. float3 extents = boxMax - center;
  172. float3 origin = history - center; // Relative to box
  173. float3 dir = current - history;
  174. float3 rDir = rcp(dir);
  175. float3 tNeg = (extents - origin) * rDir;
  176. float3 tPos = (-extents - origin) * rDir;
  177. float t = saturate(max(max(min(tNeg.x, tPos.x), min(tNeg.y, tPos.y)), min(tNeg.z, tPos.z)));
  178. return history + t * dir;
  179. }
  180. // Encodes velocity into a format suitable for storing in a 16-bit SNORM texture.
  181. // Velocity range of [-2, 2] is supported (full NDC).
  182. float2 encodeVelocity16SNORM(float2 velocity)
  183. {
  184. return velocity * 0.5f;
  185. }
  186. // Decodes velocity from an encoded 16-bit SNORM format. See encodeVelocity16SNORM().
  187. // Velocity range of [-2, 2] is supported (full NDC).
  188. float2 decodeVelocity16SNORM(float2 val)
  189. {
  190. return val * 2.0f;
  191. }
  192. ////////////////////// HELPER TONEMAP/COLOR SPACE DEFINES /////////////////////
  193. // Automatically scale HDR values based on luminance, if enabled
  194. #if TEMPORAL_TONEMAP
  195. #if TEMPORAL_YCOCG
  196. #define _TONEMAP_COLOR(v) HDRScaleY(v, exposureScale)
  197. #elif TEMPORAL_GREEN_AS_LUMA
  198. #define _TONEMAP_COLOR(v) HDRScaleG(v, exposureScale)
  199. #else
  200. #define _TONEMAP_COLOR(v) HDRScaleRGB(v, exposureScale)
  201. #endif
  202. #else // TEMPORAL_TONEMAP
  203. #define _TONEMAP_COLOR(v) v
  204. #endif // TEMPORAL_TONEMAP
  205. // Automatically convert from/to YCoCg space, if enabled
  206. #if TEMPORAL_YCOCG
  207. #define _SAMPLE_COLOR(n, uv, offset) _TONEMAP_COLOR(RGBToYCoCg(_SAMPLECOL(n, uv, offset).rgb))
  208. #else // TEMPORAL_YCOCG
  209. #define _SAMPLE_COLOR(n, uv, offset) _TONEMAP_COLOR(_SAMPLECOL(n, uv, offset).rgb)
  210. #endif // TEMPORAL_YCOCG
  211. ///////////////////////////// MAIN /////////////////////////////////
  212. [internal]
  213. cbuffer TemporalInput
  214. {
  215. float gSampleWeights[9];
  216. float gSampleWeightsLowpass[9];
  217. }
  218. float3 temporalResolve(
  219. _TEX2D(sceneDepth),
  220. _TEXCOLOR(sceneColor),
  221. _TEXCOLOR(prevColor),
  222. #if TEMPORAL_LOCAL_VELOCITY
  223. _TEX2D(velocityBuffer),
  224. #endif // TEMPORAL_LOCAL_VELOCITY
  225. #if TEMPORAL_TONEMAP
  226. float exposureScale,
  227. #endif // TEMPORAL_TONEMAP
  228. float2 uv,
  229. float2 ndcPos, // Can be derived from UV, but we usually have it for free, so pass it directly
  230. int sampleIdx)
  231. {
  232. ///////////// DETERMINE PER-PIXEL VELOCITY & CURRENT DEPTH ///////////////////
  233. float curDepth;
  234. float2 velocity;
  235. #if TEMPORAL_LOCAL_VELOCITY
  236. #if TEMPORAL_SEARCH_NEAREST == 1
  237. float3 nearest = findNearestCross(_PTEX2D(sceneDepth), uv, sampleIdx);
  238. velocity = _SAMPLE(velocityBuffer, nearest.xy);
  239. curDepth = nearest.z;
  240. #elif TEMPORAL_SEARCH_NEAREST == 2
  241. float3 nearest = findNearest3x3(_PTEX2D(sceneDepth), uv, sampleIdx);
  242. velocity = _SAMPLE(velocityBuffer, nearest.xy);
  243. curDepth = nearest.z;
  244. #else // TEMPORAL_SEARCH_NEAREST
  245. velocity = _SAMPLE(velocityBuffer, uv);
  246. curDepth = _SAMPLE(sceneDepth, uv).x;
  247. #endif // TEMPORAL_SEARCH_NEAREST
  248. #else // TEMPORAL_LOCAL_VELOCITY
  249. velocity = 0;
  250. curDepth = _SAMPLE(sceneDepth, uv).x;
  251. #endif // TEMPORAL_LOCAL_VELOCITY
  252. ///////////////////// DETERMINE PREV. FRAME UV //////////////////////////////
  253. float2 prevNdcPos;
  254. bool hasLocalVelocity = (abs(velocity.x) + abs(velocity.y)) > 0;
  255. if(hasLocalVelocity)
  256. {
  257. velocity = decodeVelocity16SNORM(velocity);
  258. prevNdcPos = float2(ndcPos - velocity);
  259. }
  260. else
  261. {
  262. // Assumes velocity due to camera movement
  263. float4 currentNDC = float4(ndcPos, curDepth, 1);
  264. float4 prevClip = mul(gNDCToPrevNDC, currentNDC);
  265. prevNdcPos = prevClip.xy / prevClip.w;
  266. }
  267. #if MSAA && MSAA_COLOR
  268. float2 prevUV = NDCToScreen(prevNdcPos);
  269. #else
  270. float2 prevUV = NDCToUV(prevNdcPos);
  271. #endif
  272. /////////////// GET FILTERED COLOR VALUE AND NEIGHBORHOOD MIN/MAX /////////////
  273. #if MSAA && !MSAA_COLOR
  274. float2 uvColor = uv * sceneColorTexelSize;
  275. #else
  276. float2 uvColor = uv;
  277. #endif
  278. #if TEMPORAL_YCOCG
  279. // YCOCG only requires a + pattern for good quality
  280. float3 neighbor[5];
  281. neighbor[0] = _SAMPLE_COLOR(sceneColor, uvColor, int2(-1, 0));
  282. neighbor[1] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, -1));
  283. neighbor[2] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, 0));
  284. neighbor[3] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 1, 0));
  285. neighbor[4] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, 1));
  286. float3 filtered = 0;
  287. [unroll]
  288. for(uint i = 0; i < 5; ++i)
  289. filtered += neighbor[i] * gSampleWeights[i];
  290. float3 filteredLow = filtered;
  291. float3 neighborMin = min(min(min(neighbor[0], neighbor[1]), min(neighbor[2], neighbor[3])),
  292. neighbor[4]);
  293. float3 neighborMax = max(max(max(neighbor[0], neighbor[1]), max(neighbor[2], neighbor[3])),
  294. neighbor[4]);
  295. #else // TEMPORAL_YCOCG
  296. float3 neighbor[9];
  297. neighbor[0] = _SAMPLE_COLOR(sceneColor, uvColor, int2(-1, -1));
  298. neighbor[1] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, -1));
  299. neighbor[2] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 1, -1));
  300. neighbor[3] = _SAMPLE_COLOR(sceneColor, uvColor, int2(-1, 0));
  301. neighbor[4] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, 0));
  302. neighbor[5] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 1, 0));
  303. neighbor[6] = _SAMPLE_COLOR(sceneColor, uvColor, int2(-1, 1));
  304. neighbor[7] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 0, 1));
  305. neighbor[8] = _SAMPLE_COLOR(sceneColor, uvColor, int2( 1, 1));
  306. float3 filtered = 0;
  307. [unroll]
  308. for(uint i = 0; i < 9; ++i)
  309. filtered += neighbor[i] * gSampleWeights[i];
  310. #if TEMPORAL_LOWPASS
  311. float3 filteredLow = 0;
  312. [unroll]
  313. for(uint i = 0; i < 9; ++i)
  314. filteredLow += neighbor[i] * gSampleWeightsLowpass[i];
  315. #else
  316. float3 filteredLow = filtered;
  317. #endif // TEMPORAL_LOWPASS
  318. #if TEMPORAL_SMOOTH_NEIGHBORHOOD
  319. // Calculate standard deviation and determine neighborhood min/max based on it
  320. float3 mean = 0;
  321. [unroll]
  322. for(uint i = 0; i < 9; ++i)
  323. mean += neighbor[i];
  324. mean /= 9.0f;
  325. float3 meanSqrd = 0;
  326. [unroll]
  327. for(uint i = 0; i < 9; ++i)
  328. meanSqrd += neighbor[i] * neighbor[i];
  329. meanSqrd /= 9.0f;
  330. float3 stdDev = sqrt(abs(meanSqrd - mean * mean));
  331. float3 neighborMin = mean - stdDev;
  332. float3 neighborMax = mean + stdDev;
  333. #else // TEMPORAL_SMOOTH_NEIGHBORHOOD
  334. float3 neighborMin = min(min(
  335. min(min(neighbor[0], neighbor[1]), min(neighbor[2], neighbor[3])),
  336. min(min(neighbor[4], neighbor[5]), min(neighbor[6], neighbor[7]))),
  337. neighbor[8]);
  338. float3 neighborMax = max(max(
  339. max(max(neighbor[0], neighbor[1]), max(neighbor[2], neighbor[3])),
  340. max(max(neighbor[4], neighbor[5]), max(neighbor[6], neighbor[7]))),
  341. neighbor[8]);
  342. #endif // TEMPORAL_SMOOTH_NEIGHBORHOOD
  343. #endif // TEMPORAL_YCOCG
  344. /////////////////// GET PREVIOUS FRAME COLOR ///////////////////////
  345. float3 prevColorVal = _SAMPLE_COLOR(prevColor, prevUV, int2(0, 0));
  346. ///////////////////// CLAMP TO NEIGHBORHOOD ////////////////////////
  347. // Clamping to neighborhood ensures we don't blend with values that are too
  348. // different, which can happen when history data becomes invalid.
  349. #if TEMPORAL_YCOCG
  350. prevColorVal = clamp(prevColorVal, neighborMin, neighborMax);
  351. #else // TEMPORAL_YCOCG
  352. // Uses low-pass to reduce flickering
  353. #if TEMPORAL_CLIP_AABB
  354. prevColorVal = clipAABB(neighborMin, neighborMax, prevColorVal, filteredLow);
  355. #else // TEMPORAL_CLIP_AABB
  356. prevColorVal = clamp(prevColorVal, neighborMin, neighborMax);
  357. #endif // TEMPORAL_CLIP_AABB
  358. #endif // TEMPORAL_YCOCG
  359. //////////////// BLEND BETWEEN CURRENT AND HISTORY //////////////////
  360. // Find out how much impact should the previous frame's color have
  361. #if TEMPORAL_BLEND_FACTOR // Fixed blend factor
  362. float blendAmount = 1.0f / TEMPORAL_BLEND_FACTOR;
  363. float3 output = lerp(prevColorVal, filtered, blendAmount);
  364. #else // TEMPORAL_BLEND_FACTOR
  365. #if TEMPORAL_YCOCG
  366. float lumaCurrent = filtered.r;
  367. float lumaHistory = prevColorVal.r;
  368. #else // TEMPORAL_YCOCG
  369. #if TEMPORAL_GREEN_AS_LUMA
  370. float lumaCurrent = filtered.g;
  371. float lumaHistory = prevColorVal.g;
  372. #else // TEMPORAL_GREEN_AS_LUMA
  373. float lumaCurrent = LuminanceRGB(filtered);
  374. float lumaHistory = LuminanceRGB(prevColorVal);
  375. #endif // TEMPORAL_GREEN_AS_LUMA
  376. #endif // TEMPORAL_YCOCG
  377. // Based on T. Lottes: https://www.youtube.com/watch?v=WzpLWzGvFK4&t=18m
  378. float blendWeight = 1.0f - abs(lumaCurrent - lumaHistory) / max(max(lumaCurrent, lumaHistory), 0.001f);
  379. float weightBad = 1.0f - 1.0f / TEMPORAL_BAD_RETENTION;
  380. float weightGood = 1.0f - 1.0f / TEMPORAL_GOOD_RETENTION;
  381. float blendAmount = lerp(weightBad, weightGood, blendWeight * blendWeight);
  382. float3 output = lerp(filtered, prevColorVal, blendAmount);
  383. #endif // TEMPORAL_BLEND_FACTOR
  384. //////// UNDO TONEMAP & MOVE BACK TO RGB SPACE //////////////////////
  385. #if TEMPORAL_TONEMAP
  386. #if TEMPORAL_YCOCG
  387. output = HDRScaleYInv(output, exposureScale);
  388. #elif TEMPORAL_GREEN_AS_LUMA
  389. output = HDRScaleGInv(output, exposureScale);
  390. #else
  391. output = HDRScaleRGBInv(output, exposureScale);
  392. #endif
  393. #endif // TEMPORAL_TONEMAP
  394. #if TEMPORAL_YCOCG
  395. output = YCoCgToRGB(output);
  396. #endif // TEMPORAL_YCOCG
  397. // Note: Potential improvements:
  398. // - Add a sharpen step
  399. // - Use filtering when sampling history
  400. // - Properly handle borders when sampling neighbors
  401. // - Better blend amount calculation? (Needs experimentation)
  402. return output;
  403. }
  404. #undef _TEX2D
  405. #undef _PTEX2D
  406. #undef _SAMPLE
  407. #undef _PIXSIZE
  408. #undef _TONEMAP_COLOR
  409. #undef _TONEMAP_COLOR_INV
  410. #undef _SAMPLE_COLOR
  411. #undef _RESOLVE_COLOR
  412. };
  413. };