Motion Blur.cpp 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534
  1. /******************************************************************************/
  2. #include "!Header.h"
  3. /******************************************************************************
  4. TexCoord.x=0 -> Left, TexCoord.x=1 -> Right
  5. TexCoord.y=0 -> Up , TexCoord.y=1 -> Down
  6. Warning: because max_dir_length is encoded in Signed RT Z channel without MAD range adjustment, it loses 1 bit precision
  7. /******************************************************************************/
  8. #define MAX_BLUR_SAMPLES 7
  9. #define VARIABLE_BLUR_SAMPLES 0 // 0=is much faster for all 3 cases (no/small/full blur), default=0
  10. #define TEST_BLUR_PIXELS 0 // test what pixels actually get blurred (they will be set to RED color) use only for debugging
  11. #define ALWAYS_DILATE_LENGTH 0 // 0=gave better results (when camera was rotated slightly), default=0
  12. #define ROUND 1 // if apply rounding (makes smaller res buffers look more similar to full res), default=1
  13. /******************************************************************************/
  14. BUFFER(MotionBlur)
  15. Vec4 MotionUVMulAdd;
  16. Vec4 MotionVelScaleLimit;
  17. Vec2 MotionPixelSize;
  18. BUFFER_END
  19. /******************************************************************************/
  20. #define DEPTH_TOLERANCE 0.1f // 10 cm
  21. inline Flt DepthBlend(Flt z_test, Flt z_base)
  22. {
  23. return Sat((z_base-z_test)/DEPTH_TOLERANCE+1); // we can apply overlap only if tested depth is smaller
  24. }
  25. inline Bool InFront(Flt z_test, Flt z_base)
  26. {
  27. return z_test<=z_base+DEPTH_TOLERANCE; // if 'z_test' is in front of 'z_base' with DEPTH_TOLERANCE
  28. }
  29. /******************************************************************************/
  30. void Explosion_VS(VtxInput vtx,
  31. out Vec outPos:TEXCOORD0,
  32. out Vec outVel:TEXCOORD1,
  33. out Vec4 outVtx:POSITION )
  34. {
  35. outVel=mul((Matrix3)CamMatrix, Normalize(vtx.pos())*Step);
  36. outPos=TransformPos(vtx.pos());
  37. outVtx=Project ( outPos );
  38. }
  39. void Explosion_PS(Vec inPos:TEXCOORD0,
  40. Vec inVel:TEXCOORD1,
  41. #if MODEL==SM_3
  42. out Vec4 outCol:COLOR0,
  43. #endif
  44. out Vec4 outVel:COLOR1) // #BlendRT
  45. {
  46. // there's no NaN because inPos.z is always >0 in PixelShader
  47. inVel/=inPos.z;
  48. #if !SIGNED_VEL_RT
  49. inVel=inVel*0.5f+0.5f;
  50. #endif
  51. outVel.xyz=inVel.xyz;
  52. outVel.w =0;
  53. #if MODEL==SM_3
  54. outCol=0; // must write on DX9 otherwise compilation will fail
  55. #endif
  56. }
  57. /******************************************************************************/
  58. void ClearSkyVel_VS(VtxInput vtx,
  59. out Vec4 outVel:TEXCOORD,
  60. out Vec4 outVtx:POSITION)
  61. {
  62. Vec pos=Vec(ScreenToPosXY(vtx.tex()), 1); // we shouldn't normalize this vector, instead, we should keep it at Z=1 so we don't have to divide by Z later
  63. outVel.xyz=Cross(pos, CamAngVel);
  64. #if !SIGNED_VEL_RT
  65. outVel.xyz=outVel.xyz*0.5f+0.5f;
  66. #endif
  67. outVel.w=0;
  68. outVtx =Vec4(vtx.pos2(), !REVERSE_DEPTH, 1); // set Z to be at the end of the viewport, this enables optimizations by optional applying lighting only on solid pixels (no sky/background)
  69. }
  70. Vec4 ClearSkyVel_PS(Vec4 inVel:TEXCOORD):COLOR {return inVel;} // yes, per-vertex precision is enough, as it generates the same results as if drawing a half sky ball mesh (results with the half ball mesh were the same as the one from this pixel shader)
  71. TECHNIQUE(ClearSkyVel, ClearSkyVel_VS(), ClearSkyVel_PS());
  72. /******************************************************************************/
  73. void Convert_VS(VtxInput vtx,
  74. out Vec2 outTex :TEXCOORD0,
  75. out Vec2 outPos :TEXCOORD1, // position relative to viewport center scaled from UV to ScreenPos
  76. out Vec2 outPosXY:TEXCOORD2,
  77. out Vec4 outVtx :POSITION ,
  78. uniform Int mode )
  79. {
  80. outTex =vtx.tex();
  81. outPos =outTex*MotionUVMulAdd.xy+MotionUVMulAdd.zw;
  82. if(mode==0)outPosXY=ScreenToPosXY(outTex);
  83. outVtx =vtx.pos4(); AdjustPixelCenter(outVtx);
  84. }
  85. Vec4 Convert_PS(NOPERSP Vec2 inTex :TEXCOORD0,
  86. NOPERSP Vec2 inPos :TEXCOORD1, // position relative to viewport center scaled from UV to ScreenPos
  87. NOPERSP Vec2 inPosXY:TEXCOORD2,
  88. uniform Int mode ,
  89. uniform Bool do_clamp ,
  90. uniform Int pixels=MAX_MOTION_BLUR_PIXEL_RANGE):COLOR
  91. {
  92. Vec blur;
  93. if(mode==0)
  94. {
  95. Vec pos=(do_clamp ? GetPosLinear(UVClamp(inTex, do_clamp)) : GetPosLinear(inTex, inPosXY));
  96. blur=GetVelocitiesCameraOnly(pos);
  97. }else
  98. if(mode==1)
  99. {
  100. blur=TexLod(Col, UVClamp(inTex, do_clamp)).xyz; // have to use linear filtering because we may draw to smaller RT
  101. #if !SIGNED_VEL_RT // convert 0..1 -> -1..1 (*2-1) and fix zero, unsigned texture formats don't have a precise zero when converted to signed, because both 127/255*2-1 and 128/255*2-1 aren't zeros, 127: (127/255-0.5)==-0.5/255, 128: (128/255-0.5)==0.5/255, 129: (129/255-0.5)==1.5/255, so let's compare <=1.0/255
  102. blur=((Abs(blur-0.5f)<=1.0f/255) ? Vec(0, 0, 0) : blur*2-1); // this performs comparisons for all channels separately, force 0 when source value is close to 0.5, otherwise scale to -1..1
  103. #endif
  104. }
  105. // see "C:\Users\Greg\SkyDrive\Code\Tests\Motion Blur.cpp"
  106. // the following generates 2 delta vectors in screen space, they have different lengths (when blur.z!=0) due to perspective correction, one points towards center, and one away from center
  107. blur*=MotionVelScaleLimit.xyz;
  108. Vec4 delta=Vec4((inPos+blur.xy)/(1+blur.z)-inPos, // #1 is along blur vector (delta.xy)
  109. (inPos-blur.xy)/(1-blur.z)-inPos); // #2 is against blur vector (delta.zw)
  110. // store only one direction and its length
  111. // delta.xy.setLength(Avg(delta.xy.length(), delta.zw.length()))
  112. Flt len0=Length(delta.xy), len1=Length(delta.zw), len=Min(Avg(len0, len1), MotionVelScaleLimit.w); // alternatively we could use "Max" instead of "Avg"
  113. if( len0)delta.xy*=(len + ROUND*0.5f/pixels)/len0; // NaN, add half pixel length which will trigger rounding effect
  114. delta.z = len; // here don't include rounding
  115. #if !SIGNED_VEL_RT
  116. delta.xy=delta.xy*0.5f+0.5f; // scale XY -1..1 -> 0..1, but leave Z in 0..1
  117. #endif
  118. return delta;
  119. }
  120. /******************************************************************************/
  121. Vec4 Dilate_PS(NOPERSP Vec2 inTex:TEXCOORD ,
  122. uniform Int range=1 ,
  123. uniform Int pixels=MAX_MOTION_BLUR_PIXEL_RANGE,
  124. uniform Bool depth=false ):COLOR
  125. {
  126. Vec4 blur;
  127. blur.xyz=TexPoint(Col, inTex).xyz;
  128. blur.w=0;
  129. #if !SIGNED_VEL_RT
  130. blur.xy=blur.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  131. #endif
  132. Flt len=Length(blur.xy), // can't use 'blur.z' as length here, because it's max length of all nearby pixels and not just this pixel
  133. z_base; if(depth)z_base=TexDepthLinear(inTex); // use linear filtering because RT can be smaller
  134. // this function iterates nearby pixels and calculates how much the should blur over this one (including angle between pixels, distances, and their blur velocities)
  135. UNROLL for(Int y=-range; y<=range; y++)
  136. UNROLL for(Int x=-range; x<=range; x++)if(x || y)
  137. {
  138. Vec2 t=inTex+Vec2(x, y)*ColSize.xy;
  139. Vec b=TexPoint(Col, t).xyz;
  140. #if !SIGNED_VEL_RT
  141. b.xy=b.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  142. #endif
  143. #if 1 // version that multiplies length by cos between pixels (faster and more accurate for diagonal blurs, however bloats slightly)
  144. Flt l=Abs(Dot(b.xy, Normalize(Vec2(x, y)))); // we want this to be proportional to 'b.xy' length
  145. if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  146. l-=Dist(x, y)/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  147. if(l>len){blur.xy=b.xy*(l/Length(b.xy)); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;}
  148. #else // version that does line intersection tests (slower)
  149. Flt b_len=Length(b.xy), // can't use 'b.z' as length here, because it's max length of all nearby pixels and not just this pixel
  150. l=b_len; if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  151. l-=Dist(x, y)/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  152. if(l>len)
  153. {
  154. const Flt eps=0.7f; // 1.0f would include neighbors too, and with each call to this function we would bloat by 1 pixel, this needs to be slightly below SQRT2_2 0.7071067811865475 to avoid diagonal bloating too
  155. Flt line_dist=Dot(b.xy, Vec2(y, -x)); if(Abs(line_dist)<=b_len*eps) // Vec2 perp_n=Vec2(b.y, -b.x)/b_len; Flt line_dist=Dot(perp_n, Vec2(x, y)); if(Abs(line_dist)<=eps).. (don't try to do "Vec2(y, -x)/eps" instead of "b_len*eps", because compiler can optimize the Dot better without it)
  156. {blur.xy=b.xy*(l/b_len); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;}
  157. }
  158. #endif
  159. if(ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z);
  160. }
  161. #if !SIGNED_VEL_RT
  162. blur.xy=blur.xy*0.5f+0.5f; // scale XY -1..1 -> 0..1, but leave Z in 0..1
  163. #endif
  164. return blur;
  165. }
  166. /******************************************************************************/
  167. Vec4 DilateX_PS(NOPERSP Vec2 inTex:TEXCOORD ,
  168. uniform Int range ,
  169. uniform Bool diagonal=false ,
  170. uniform Int pixels=MAX_MOTION_BLUR_PIXEL_RANGE,
  171. uniform Bool depth=false ):COLOR
  172. {
  173. Vec4 blur=TexPoint(Col, inTex); // XY=Dir, Z=Max Dir length of all nearby pixels
  174. #if !SIGNED_VEL_RT
  175. blur.xy=blur.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  176. #endif
  177. Flt len=Length(blur.xy), // can't use 'blur.z' as length here, because it's max length of all nearby pixels and not just this pixel
  178. z_base; if(depth)z_base=TexDepthLinear(inTex); // use linear filtering because RT can be smaller
  179. Vec2 t; t.y=inTex.y;
  180. UNROLL for(Int i=-range; i<=range; i++)if(i)
  181. {
  182. t.x=inTex.x+ColSize.x*i;
  183. Vec b=TexPoint(Col, t).xyz;
  184. #if !SIGNED_VEL_RT
  185. b.xy=b.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  186. #endif
  187. Flt ll=Abs(b.x), l=ll;
  188. if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  189. l-=Abs(Flt(i))/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  190. if(l>len){blur.xy=b.xy*(l/(diagonal ? Length(b.xy) : ll)); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;} // when not doing diagonal then use 'll' to artificially boost intensity, because it helps in making it look more like "Dilate"
  191. if(ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z);
  192. }
  193. if(diagonal)
  194. {
  195. range=Round(range*SQRT2_2);
  196. UNROLL for(Int i=-range; i<=range; i++)if(i)
  197. {
  198. t=inTex+ColSize.xy*i;
  199. Vec b=TexPoint(Col, t).xyz;
  200. #if !SIGNED_VEL_RT
  201. b.xy=b.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  202. #endif
  203. Flt l=Abs(Dot(b.xy, Vec2(SQRT2_2, SQRT2_2)));
  204. if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  205. l-=Abs(i*SQRT2)/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  206. if(l>len){blur.xy=b.xy*(l/Length(b.xy)); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;}
  207. if(ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z);
  208. }
  209. }
  210. #if !SIGNED_VEL_RT
  211. blur.xy=blur.xy*0.5f+0.5f; // scale XY -1..1 -> 0..1, but leave Z in 0..1
  212. #endif
  213. return blur;
  214. }
  215. /******************************************************************************/
  216. Vec4 DilateY_PS(NOPERSP Vec2 inTex:TEXCOORD ,
  217. uniform Int range ,
  218. uniform Bool diagonal=false ,
  219. uniform Int pixels=MAX_MOTION_BLUR_PIXEL_RANGE,
  220. uniform Bool depth=false ):COLOR
  221. {
  222. Vec4 blur=TexPoint(Col, inTex); // XY=Dir, Z=Max Dir length of all nearby pixels
  223. #if !SIGNED_VEL_RT
  224. blur.xy=blur.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  225. #endif
  226. Flt len=Length(blur.xy), // can't use 'blur.z' as length here, because it's max length of all nearby pixels and not just this pixel
  227. z_base; if(depth)z_base=TexDepthLinear(inTex); // use linear filtering because RT can be smaller
  228. Vec2 t; t.x=inTex.x;
  229. UNROLL for(Int i=-range; i<=range; i++)if(i)
  230. {
  231. t.y=inTex.y+ColSize.y*i;
  232. Vec b=TexPoint(Col, t).xyz;
  233. #if !SIGNED_VEL_RT
  234. b.xy=b.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  235. #endif
  236. Flt ll=Abs(b.y), l=ll;
  237. if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  238. l-=Abs(Flt(i))/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  239. if(l>len){blur.xy=b.xy*(l/(diagonal ? Length(b.xy) : ll)); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;} // when not doing diagonal then use 'll' to artificially boost intensity, because it helps in making it look more like "Dilate"
  240. if(ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z);
  241. }
  242. if(diagonal)
  243. {
  244. range=Round(range*SQRT2_2);
  245. UNROLL for(Int i=-range; i<=range; i++)if(i)
  246. {
  247. t=inTex+ColSize.xy*Vec2(i, -i);
  248. Vec b=TexPoint(Col, t).xyz;
  249. #if !SIGNED_VEL_RT
  250. b.xy=b.xy*2-1; // scale XY 0..1 -> -1..1, but leave Z in 0..1
  251. #endif
  252. Flt l=Abs(Dot(b.xy, Vec2(SQRT2_2, -SQRT2_2)));
  253. if(depth)l*=DepthBlend(TexDepthLinear(t), z_base); // use linear filtering because RT can be smaller
  254. l-=Abs(i*SQRT2)/pixels; // when loop is unrolled and 'pixels' is uniform then it can be evaluated to a constant expression
  255. if(l>len){blur.xy=b.xy*(l/Length(b.xy)); if(!ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z); len=l;}
  256. if(ALWAYS_DILATE_LENGTH)blur.z=Max(blur.z, b.z);
  257. }
  258. }
  259. #if !SIGNED_VEL_RT
  260. blur.xy=blur.xy*0.5f+0.5f; // scale XY -1..1 -> 0..1, but leave Z in 0..1
  261. #endif
  262. return blur;
  263. }
  264. /******************************************************************************/
  265. Vec4 SetDirs_PS(NOPERSP Vec2 inTex:TEXCOORD, // goes simultaneously in both ways from starting point and notices how far it can go, travelled distance is put into texture
  266. uniform Bool do_clamp ,
  267. uniform Int pixels=MAX_MOTION_BLUR_PIXEL_RANGE):COLOR
  268. {
  269. // Input: Col - pixel velocity
  270. // Col1 - dilated velocity (main blur direction)
  271. Vec blur_dir=TexPoint(Col1, inTex).xyz; // XY=Dir, Z=Max Dir length of all nearby pixels
  272. #if !SIGNED_VEL_RT
  273. blur_dir.xy=((Abs(blur_dir.xy-0.5f)<=1.0f/255) ? Vec2(0, 0) : blur_dir.xy*2-1); // this performs comparisons for all channels separately, force 0 when source value is close to 0.5, otherwise scale to -1..1
  274. #endif
  275. Vec4 dirs=0;
  276. #if 0 && !ALWAYS_DILATE_LENGTH // when we don't always dilate the length, then we could check it here instead of 'blur_dir.xy' length, performance results were similar, at the moment it's not sure which version is better
  277. BRANCH if(blur_dir.z>0)
  278. {
  279. Flt blur_dir_length2=Length2(blur_dir.xy);
  280. #else
  281. Flt blur_dir_length2=Length2(blur_dir.xy);
  282. BRANCH if(blur_dir_length2>Sqr(0.5f/pixels)) // Length(blur_dir.xy)*pixels>0.5f. Check 'blur_dir.xy' instead of 'blur_dir.z' because can be big (because of nearby pixels moving) but 'blur_dir.xy' can be zero. Always check for 0.5 regardless of ROUND (this was tested and it looks much better)
  283. {
  284. #endif
  285. /*
  286. This algorithm works by going in 2 directions, starting from the center, directions are opposite:
  287. left <--- center ---> right
  288. Left and Right are just examples, because directions can go in any way (up/down, diagonal, etc)
  289. This function needs to calculate the length of those directions - how much we want to travel in each direction.
  290. Those vectors will be used in the final blur, inside it, all pixels along the way will be included.
  291. So we need to choose the lengths carefully, to avoid blurring with non-moving objects that are in front of us.
  292. At the start we take the movement velocity of the starting center pixel, to see how much it should blur.
  293. Along the way we test if there are other moving pixels, and if they overlap with the starting center position,
  294. if so, we extend blur length up to that pixel, but not further. We also check depth values of encountered pixels,
  295. and calculate the minimum of them in each direction, to know if they will form an obstacle, that will block next pixels.
  296. We also allow a special case when all pixels move in the same direction (this happens when rotating the camera).
  297. In this loop we can't break, because even if we encounter some obstacles blocking pixels, we have to keep going,
  298. because we may find a pixel later, that is not blocked by any obstacles and overlaps the center.
  299. */
  300. // calculate both directions
  301. dirs.xy= blur_dir.xy*MotionPixelSize;
  302. dirs.zw=-blur_dir.xy*MotionPixelSize;
  303. #if 0 // no need to do clamping here because we do it anyway below for the final dirs
  304. if(do_clamp)
  305. {
  306. dirs.xy=UVClamp(inTex+dirs.xy, do_clamp)-inTex; // first calculate target and clamp it
  307. dirs.zw=UVClamp(inTex+dirs.zw, do_clamp)-inTex; // first calculate target and clamp it
  308. }
  309. #endif
  310. Flt blur_dir_length=Sqrt(blur_dir_length2);
  311. Vec2 blur_dir_normalized=blur_dir.xy/blur_dir_length;
  312. Flt pixel_range=blur_dir_length*pixels; dirs/=pixel_range; // 'dirs' is now 1 pixel length (we need this length, because we compare blur lengths/distances to "Int i" step)
  313. Int range0=0, range1=0;
  314. Vec2 pixel_vel=TexPoint(Col, inTex).xy;
  315. #if !SIGNED_VEL_RT
  316. pixel_vel=pixel_vel*2-1;
  317. #endif
  318. Flt length0=Abs(Dot(pixel_vel, blur_dir_normalized))*pixels, length1=length0, // how many pixels in left and right this pixel wants to move starting from the center. We want this to be proportional to 'pixel_vel' length, so don't normalize. This formula gives us how much this pixel wants to travel along the main blur direction
  319. depth0=TexDepthLinear(inTex), depth1=depth0, // depth values of left and right pixels with most movement, use linear filtering because RT can be smaller
  320. depth_min0=depth0, depth_min1=depth1, // minimum of encountered left and right depth values
  321. same_vel0=true, same_vel1=true; // if all encountered so far in this direction have the same velocity (this is to support cases where pixels move in the same direction but have diffent positions, for example when rotating the camera)
  322. Int allowed0=0, allowed1=0;
  323. Vec2 t0=inTex, t1=inTex;
  324. #if 0 // slower
  325. Int samples=pixels; UNROLL for(Int i=1; i<=samples; i++)
  326. #elif MODEL==SM_GL
  327. Int samples=Round(blur_dir.z*pixels); LOOP for(Int i=1; i<=samples; i++) // GL version breaks when 'Ceil' is used so for GL we always have to use 'Round'
  328. #else
  329. Int samples=Round(blur_dir.z*pixels); LOOP for(Int i=1; i<=samples; i++)
  330. #endif
  331. {
  332. t0+=dirs.xy;
  333. t1+=dirs.zw;
  334. Vec2 c0=TexLod(Col, t0).xy, c1=TexLod(Col, t1).xy; // use linear filtering because texcoords are not rounded
  335. #if !SIGNED_VEL_RT
  336. c0=c0*2-1;
  337. c1=c1*2-1;
  338. #endif
  339. Flt z0=TexDepthLinear(t0), z1=TexDepthLinear(t1); // use linear filtering because RT can be smaller and because texcoords are not rounded
  340. #if 0
  341. Flt l0=Length(c0)*pixels, l1=Length(c1)*pixels;
  342. #else
  343. Flt l0=Abs(Dot(c0, blur_dir_normalized))*pixels, l1=Abs(Dot(c1, blur_dir_normalized))*pixels;
  344. #endif
  345. //if(InFront(z0, depth_min0)) // if this sample is in front of all encountered so far in this direction, this check isn't needed because we do depth tests either way below
  346. if(l0>=i // this sample movement reaches the center
  347. && i>length0) // and it extends blurring to the left, compared to what we already have
  348. {
  349. length0= i; // extend possible blurring only up to this sample, but not any further
  350. depth0=z0;
  351. }
  352. //if(InFront(z1, depth_min1)) // if this sample is in front of all encountered so far in this direction, this check isn't needed because we do depth tests either way below
  353. if(l1>=i // this sample movement reaches the center
  354. && i>length1) // and it extends blurring to the right, compared to what we already have
  355. {
  356. length1= i; // extend possible blurring only up to this sample, but not any further
  357. depth1=z1;
  358. }
  359. depth_min0=Min(depth_min0, z0);
  360. depth_min1=Min(depth_min1, z1);
  361. // TODO: can this be improved?
  362. same_vel0*=(Dist2(pixel_vel, c0)<=Sqr(1.5f/pixels));
  363. same_vel1*=(Dist2(pixel_vel, c1)<=Sqr(1.5f/pixels));
  364. Bool allow0=(InFront(depth0, depth_min0) || same_vel0),
  365. allow1=(InFront(depth1, depth_min1) || same_vel1);
  366. allowed0+=allow0;
  367. allowed1+=allow1;
  368. if(length0>=i && allow0)range0=i;
  369. if(length1>=i && allow1)range1=i;
  370. }
  371. Flt dir_length0=range0/Flt(pixels),
  372. dir_length1=range1/Flt(pixels);
  373. #if 1
  374. // normally with the formula above, we can get only integer precision, 'range0' and 'range1' can only be set to integer steps, based on which we set vectors
  375. // we get multiples of pixel ranges, without fraction, this gets much worse with smaller resolutions, in which steps are bigger
  376. // to solve this problem, if there were no obstacles found, then maximize directions by original smooth value
  377. Flt actual_length=blur_dir_length - ROUND*0.5f/pixels, // we need to subtract what we've added before, no need to do Sat, because we're inside BRANCH if that tests for length
  378. test_length=blur_dir_length + 1.0f/128 ; // we have to use a bigger value than 'actual_length' and 'blur_dir_length' to improve chances of this going through (the epsilon was tested carefully it supports both small and big velocities)
  379. if(allowed0==samples && (range1>0 || samples<=1) && test_length>=dir_length0)dir_length0=actual_length; // if there were no obstacles in this direction (also check opposite direction because of linear filtering using neighbor sample which causes visible leaking, we check that we've blurred at least one pixel "range>0", however if the velocity is <1 less than 1 pixel then range will not get >0, to support very small smooth velocities, we have to do another check for "samples <= 1"), then try using original
  380. if(allowed1==samples && (range0>0 || samples<=1) && test_length>=dir_length1)dir_length1=actual_length; // if there were no obstacles in this direction (also check opposite direction because of linear filtering using neighbor sample which causes visible leaking, we check that we've blurred at least one pixel "range>0", however if the velocity is <1 less than 1 pixel then range will not get >0, to support very small smooth velocities, we have to do another check for "samples <= 1"), then try using original
  381. #endif
  382. dirs.xy= blur_dir_normalized.xy*dir_length0;
  383. dirs.zw=-blur_dir_normalized.xy*dir_length1;
  384. if(do_clamp) // clamp final dirs
  385. {
  386. dirs.xy=(UVClamp(inTex+dirs.xy*MotionPixelSize, do_clamp)-inTex)/MotionPixelSize; // first calculate target and clamp it
  387. dirs.zw=(UVClamp(inTex+dirs.zw*MotionPixelSize, do_clamp)-inTex)/MotionPixelSize; // first calculate target and clamp it
  388. }
  389. }
  390. #if !SIGNED_VEL_RT
  391. dirs=dirs*0.5f+0.5f; // scale -1..1 -> 0..1
  392. #endif
  393. return dirs;
  394. }
  395. /******************************************************************************/
  396. Vec4 Blur_PS(NOPERSP Vec2 inTex:TEXCOORD,
  397. NOPERSP PIXEL ,
  398. uniform Bool dither ,
  399. uniform Bool do_clamp=false):COLOR // no need to do clamp because we've already done that in 'SetDirs'
  400. {
  401. // Input: Col - color
  402. // Col1 - 2 blur ranges (XY, ZW)
  403. Vec4 blur=TexLod(Col1, inTex); // use linear filtering because 'Col1' may be smaller
  404. #if !SIGNED_VEL_RT
  405. blur=((Abs(blur-0.5f)<=1.0f/255) ? Vec4(0, 0, 0, 0) : blur*2-1); // this performs comparisons for all channels separately, force 0 when source value is close to 0.5, otherwise scale to -1..1
  406. #endif
  407. Vec4 color=Vec4(TexLod(Col, inTex).rgb, 1); // force full alpha so back buffer effects can work ok, can't use 'TexPoint' because 'Col' can be supersampled
  408. BRANCH if(any(blur)) // we can use 'any' here because small values got clipped out already in 'SetDirs'
  409. {
  410. Vec2 dir0=blur.xy*MotionPixelSize,
  411. dir1=blur.zw*MotionPixelSize;
  412. if(do_clamp)
  413. {
  414. dir0=UVClamp(inTex+dir0, do_clamp)-inTex; // first calculate target and clamp it
  415. dir1=UVClamp(inTex+dir1, do_clamp)-inTex; // first calculate target and clamp it
  416. }
  417. Int samples=MAX_BLUR_SAMPLES;
  418. #if VARIABLE_BLUR_SAMPLES
  419. Flt eps=0.15f;
  420. #if 1
  421. samples=Ceil(Sat(Max(Abs(blur))/MotionVelScaleLimit.w+eps)*samples);
  422. #else
  423. samples=Ceil(Sat(Sqrt(Max(Length2(blur.xy), Length2(blur.zw)))/MotionVelScaleLimit.w+eps)*samples); // have to calculate max of 2 lengths, because one can be clipped due to an obstacle
  424. #endif
  425. #endif
  426. Vec2 t1=inTex;
  427. dir0/=samples;
  428. dir1/=samples;
  429. #if VARIABLE_BLUR_SAMPLES
  430. LOOP
  431. #else
  432. UNROLL
  433. #endif
  434. for(Int i=1; i<=samples; i++) // start from 1 because we've already got #0 before
  435. {
  436. // TODO: implement new high quality mode that doesn't use 'SetDirs' but calculates per-sample weights based on velocity and depth (for this have to use 'do_clamp')
  437. color.rgb+=TexLod(Col, inTex+=dir0).rgb; // use linear filtering
  438. color.rgb+=TexLod(Col, t1 +=dir1).rgb; // use linear filtering
  439. }
  440. color.rgb/=samples*2+1;
  441. #if TEST_BLUR_PIXELS
  442. color.r=1;
  443. #endif
  444. #if 0 // test how many samples were used for blurring
  445. color.rgb=samples/16.0f;
  446. #endif
  447. }
  448. if(dither)color.rgb+=DitherValueColor(pixel);
  449. return color;
  450. }
  451. /******************************************************************************/
  452. // TECHNIQUES
  453. /******************************************************************************/
  454. TECHNIQUE(Explosion, Explosion_VS(), Explosion_PS());
  455. TECHNIQUE(Convert , Convert_VS(0), Convert_PS(0, false));
  456. TECHNIQUE(ConvertC , Convert_VS(0), Convert_PS(0, true ));
  457. TECHNIQUE(ConvertH , Convert_VS(1), Convert_PS(1, false));
  458. TECHNIQUE(ConvertHC, Convert_VS(1), Convert_PS(1, true ));
  459. TECHNIQUE(Dilate, Draw_VS(), Dilate_PS());
  460. TECHNIQUE(SetDirs , Draw_VS(), SetDirs_PS(false));
  461. TECHNIQUE(SetDirsC, Draw_VS(), SetDirs_PS(true ));
  462. TECHNIQUE(Blur , Draw_VS(), Blur_PS(false));
  463. TECHNIQUE(BlurD, Draw_VS(), Blur_PS(true ));
  464. #define DILATE(range) \
  465. TECHNIQUE(DilateX##range , Draw_VS(), DilateX_PS(range )); \
  466. TECHNIQUE(DilateY##range , Draw_VS(), DilateY_PS(range )); \
  467. TECHNIQUE(DilateXD##range, Draw_VS(), DilateX_PS(range, true)); \
  468. TECHNIQUE(DilateYD##range, Draw_VS(), DilateY_PS(range, true));
  469. DILATE(1)
  470. DILATE(2)
  471. DILATE(4)
  472. DILATE(6)
  473. DILATE(8)
  474. DILATE(12)
  475. DILATE(16)
  476. DILATE(24)
  477. DILATE(32)
  478. /******************************************************************************/