CameraMotionBlur.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. using System;
  2. using UnityEngine;
  3. namespace UnityStandardAssets.ImageEffects
  4. {
  5. [ExecuteInEditMode]
  6. [RequireComponent (typeof(Camera))]
  7. [AddComponentMenu ("Image Effects/Camera/Camera Motion Blur") ]
  8. public class CameraMotionBlur : PostEffectsBase
  9. {
  10. // make sure to match this to MAX_RADIUS in shader ('k' in paper)
  11. static float MAX_RADIUS = 10.0f;
  12. public enum MotionBlurFilter {
  13. CameraMotion = 0, // global screen blur based on cam motion
  14. LocalBlur = 1, // cheap blur, no dilation or scattering
  15. Reconstruction = 2, // advanced filter (simulates scattering) as in plausible motion blur paper
  16. ReconstructionDX11 = 3, // advanced filter (simulates scattering) as in plausible motion blur paper
  17. ReconstructionDisc = 4, // advanced filter using scaled poisson disc sampling
  18. }
  19. // settings
  20. public MotionBlurFilter filterType = MotionBlurFilter.Reconstruction;
  21. public bool preview = false; // show how blur would look like in action ...
  22. public Vector3 previewScale = Vector3.one; // ... given this movement vector
  23. // params
  24. public float movementScale = 0.0f;
  25. public float rotationScale = 1.0f;
  26. public float maxVelocity = 8.0f; // maximum velocity in pixels
  27. public float minVelocity = 0.1f; // minimum velocity in pixels
  28. public float velocityScale = 0.375f; // global velocity scale
  29. public float softZDistance = 0.005f; // for z overlap check softness (reconstruction filter only)
  30. public int velocityDownsample = 1; // low resolution velocity buffer? (optimization)
  31. public LayerMask excludeLayers = 0;
  32. private GameObject tmpCam = null;
  33. // resources
  34. public Shader shader;
  35. public Shader dx11MotionBlurShader;
  36. public Shader replacementClear;
  37. private Material motionBlurMaterial = null;
  38. private Material dx11MotionBlurMaterial = null;
  39. public Texture2D noiseTexture = null;
  40. public float jitter = 0.05f;
  41. // (internal) debug
  42. public bool showVelocity = false;
  43. public float showVelocityScale = 1.0f;
  44. // camera transforms
  45. private Matrix4x4 currentViewProjMat;
  46. private Matrix4x4 prevViewProjMat;
  47. private int prevFrameCount;
  48. private bool wasActive;
  49. // shortcuts to calculate global blur direction when using 'CameraMotion'
  50. private Vector3 prevFrameForward = Vector3.forward;
  51. private Vector3 prevFrameUp = Vector3.up;
  52. private Vector3 prevFramePos = Vector3.zero;
  53. private Camera _camera;
  54. private void CalculateViewProjection () {
  55. Matrix4x4 viewMat = _camera.worldToCameraMatrix;
  56. Matrix4x4 projMat = GL.GetGPUProjectionMatrix (_camera.projectionMatrix, true);
  57. currentViewProjMat = projMat * viewMat;
  58. }
  59. new void Start () {
  60. CheckResources ();
  61. if (_camera == null)
  62. _camera = GetComponent<Camera>();
  63. wasActive = gameObject.activeInHierarchy;
  64. CalculateViewProjection ();
  65. Remember ();
  66. wasActive = false; // hack to fake position/rotation update and prevent bad blurs
  67. }
  68. void OnEnable () {
  69. if (_camera == null)
  70. _camera = GetComponent<Camera>();
  71. _camera.depthTextureMode |= DepthTextureMode.Depth;
  72. }
  73. void OnDisable () {
  74. if (null != motionBlurMaterial) {
  75. DestroyImmediate (motionBlurMaterial);
  76. motionBlurMaterial = null;
  77. }
  78. if (null != dx11MotionBlurMaterial) {
  79. DestroyImmediate (dx11MotionBlurMaterial);
  80. dx11MotionBlurMaterial = null;
  81. }
  82. if (null != tmpCam) {
  83. DestroyImmediate (tmpCam);
  84. tmpCam = null;
  85. }
  86. }
  87. public override bool CheckResources () {
  88. CheckSupport (true, true); // depth & hdr needed
  89. motionBlurMaterial = CheckShaderAndCreateMaterial (shader, motionBlurMaterial);
  90. if (supportDX11 && filterType == MotionBlurFilter.ReconstructionDX11) {
  91. dx11MotionBlurMaterial = CheckShaderAndCreateMaterial (dx11MotionBlurShader, dx11MotionBlurMaterial);
  92. }
  93. if (!isSupported)
  94. ReportAutoDisable ();
  95. return isSupported;
  96. }
  97. void OnRenderImage (RenderTexture source, RenderTexture destination) {
  98. if (false == CheckResources ()) {
  99. Graphics.Blit (source, destination);
  100. return;
  101. }
  102. if (filterType == MotionBlurFilter.CameraMotion)
  103. StartFrame ();
  104. // use if possible new RG format ... fallback to half otherwise
  105. var rtFormat= SystemInfo.SupportsRenderTextureFormat (RenderTextureFormat.RGHalf) ? RenderTextureFormat.RGHalf : RenderTextureFormat.ARGBHalf;
  106. // get temp textures
  107. RenderTexture velBuffer = RenderTexture.GetTemporary (divRoundUp (source.width, velocityDownsample), divRoundUp (source.height, velocityDownsample), 0, rtFormat);
  108. int tileWidth = 1;
  109. int tileHeight = 1;
  110. maxVelocity = Mathf.Max (2.0f, maxVelocity);
  111. float _maxVelocity = maxVelocity; // calculate 'k'
  112. // note: 's' is hardcoded in shaders except for DX11 path
  113. // auto DX11 fallback!
  114. bool fallbackFromDX11 = filterType == MotionBlurFilter.ReconstructionDX11 && dx11MotionBlurMaterial == null;
  115. if (filterType == MotionBlurFilter.Reconstruction || fallbackFromDX11 || filterType == MotionBlurFilter.ReconstructionDisc) {
  116. maxVelocity = Mathf.Min (maxVelocity, MAX_RADIUS);
  117. tileWidth = divRoundUp (velBuffer.width, (int) maxVelocity);
  118. tileHeight = divRoundUp (velBuffer.height, (int) maxVelocity);
  119. _maxVelocity = velBuffer.width/tileWidth;
  120. }
  121. else {
  122. tileWidth = divRoundUp (velBuffer.width, (int) maxVelocity);
  123. tileHeight = divRoundUp (velBuffer.height, (int) maxVelocity);
  124. _maxVelocity = velBuffer.width/tileWidth;
  125. }
  126. RenderTexture tileMax = RenderTexture.GetTemporary (tileWidth, tileHeight, 0, rtFormat);
  127. RenderTexture neighbourMax = RenderTexture.GetTemporary (tileWidth, tileHeight, 0, rtFormat);
  128. velBuffer.filterMode = FilterMode.Point;
  129. tileMax.filterMode = FilterMode.Point;
  130. neighbourMax.filterMode = FilterMode.Point;
  131. if (noiseTexture) noiseTexture.filterMode = FilterMode.Point;
  132. source.wrapMode = TextureWrapMode.Clamp;
  133. velBuffer.wrapMode = TextureWrapMode.Clamp;
  134. neighbourMax.wrapMode = TextureWrapMode.Clamp;
  135. tileMax.wrapMode = TextureWrapMode.Clamp;
  136. // calc correct viewprj matrix
  137. CalculateViewProjection ();
  138. // just started up?
  139. if (gameObject.activeInHierarchy && !wasActive) {
  140. Remember ();
  141. }
  142. wasActive = gameObject.activeInHierarchy;
  143. // matrices
  144. Matrix4x4 invViewPrj = Matrix4x4.Inverse (currentViewProjMat);
  145. motionBlurMaterial.SetMatrix ("_InvViewProj", invViewPrj);
  146. motionBlurMaterial.SetMatrix ("_PrevViewProj", prevViewProjMat);
  147. motionBlurMaterial.SetMatrix ("_ToPrevViewProjCombined", prevViewProjMat * invViewPrj);
  148. motionBlurMaterial.SetFloat ("_MaxVelocity", _maxVelocity);
  149. motionBlurMaterial.SetFloat ("_MaxRadiusOrKInPaper", _maxVelocity);
  150. motionBlurMaterial.SetFloat ("_MinVelocity", minVelocity);
  151. motionBlurMaterial.SetFloat ("_VelocityScale", velocityScale);
  152. motionBlurMaterial.SetFloat ("_Jitter", jitter);
  153. // texture samplers
  154. motionBlurMaterial.SetTexture ("_NoiseTex", noiseTexture);
  155. motionBlurMaterial.SetTexture ("_VelTex", velBuffer);
  156. motionBlurMaterial.SetTexture ("_NeighbourMaxTex", neighbourMax);
  157. motionBlurMaterial.SetTexture ("_TileTexDebug", tileMax);
  158. if (preview) {
  159. // generate an artifical 'previous' matrix to simulate blur look
  160. Matrix4x4 viewMat = _camera.worldToCameraMatrix;
  161. Matrix4x4 offset = Matrix4x4.identity;
  162. offset.SetTRS(previewScale * 0.3333f, Quaternion.identity, Vector3.one); // using only translation
  163. Matrix4x4 projMat = GL.GetGPUProjectionMatrix (_camera.projectionMatrix, true);
  164. prevViewProjMat = projMat * offset * viewMat;
  165. motionBlurMaterial.SetMatrix ("_PrevViewProj", prevViewProjMat);
  166. motionBlurMaterial.SetMatrix ("_ToPrevViewProjCombined", prevViewProjMat * invViewPrj);
  167. }
  168. if (filterType == MotionBlurFilter.CameraMotion)
  169. {
  170. // build blur vector to be used in shader to create a global blur direction
  171. Vector4 blurVector = Vector4.zero;
  172. float lookUpDown = Vector3.Dot (transform.up, Vector3.up);
  173. Vector3 distanceVector = prevFramePos-transform.position;
  174. float distMag = distanceVector.magnitude;
  175. float farHeur = 1.0f;
  176. // pitch (vertical)
  177. farHeur = (Vector3.Angle (transform.up, prevFrameUp) / _camera.fieldOfView) * (source.width * 0.75f);
  178. blurVector.x = rotationScale * farHeur;//Mathf.Clamp01((1.0ff-Vector3.Dot(transform.up, prevFrameUp)));
  179. // yaw #1 (horizontal, faded by pitch)
  180. farHeur = (Vector3.Angle (transform.forward, prevFrameForward) / _camera.fieldOfView) * (source.width * 0.75f);
  181. blurVector.y = rotationScale * lookUpDown * farHeur;//Mathf.Clamp01((1.0ff-Vector3.Dot(transform.forward, prevFrameForward)));
  182. // yaw #2 (when looking down, faded by 1-pitch)
  183. farHeur = (Vector3.Angle (transform.forward, prevFrameForward) / _camera.fieldOfView) * (source.width * 0.75f);
  184. blurVector.z = rotationScale * (1.0f- lookUpDown) * farHeur;//Mathf.Clamp01((1.0ff-Vector3.Dot(transform.forward, prevFrameForward)));
  185. if (distMag > Mathf.Epsilon && movementScale > Mathf.Epsilon) {
  186. // forward (probably most important)
  187. blurVector.w = movementScale * (Vector3.Dot (transform.forward, distanceVector) ) * (source.width * 0.5f);
  188. // jump (maybe scale down further)
  189. blurVector.x += movementScale * (Vector3.Dot (transform.up, distanceVector) ) * (source.width * 0.5f);
  190. // strafe (maybe scale down further)
  191. blurVector.y += movementScale * (Vector3.Dot (transform.right, distanceVector) ) * (source.width * 0.5f);
  192. }
  193. if (preview) // crude approximation
  194. motionBlurMaterial.SetVector ("_BlurDirectionPacked", new Vector4 (previewScale.y, previewScale.x, 0.0f, previewScale.z) * 0.5f * _camera.fieldOfView);
  195. else
  196. motionBlurMaterial.SetVector ("_BlurDirectionPacked", blurVector);
  197. }
  198. else {
  199. // generate velocity buffer
  200. Graphics.Blit (source, velBuffer, motionBlurMaterial, 0);
  201. // patch up velocity buffer:
  202. // exclude certain layers (e.g. skinned objects as we cant really support that atm)
  203. Camera cam = null;
  204. if (excludeLayers.value != 0)// || dynamicLayers.value)
  205. cam = GetTmpCam ();
  206. if (cam && excludeLayers.value != 0 && replacementClear && replacementClear.isSupported) {
  207. cam.targetTexture = velBuffer;
  208. cam.cullingMask = excludeLayers;
  209. cam.RenderWithShader (replacementClear, "");
  210. }
  211. }
  212. if (!preview && Time.frameCount != prevFrameCount) {
  213. // remember current transformation data for next frame
  214. prevFrameCount = Time.frameCount;
  215. Remember ();
  216. }
  217. source.filterMode = FilterMode.Bilinear;
  218. // debug vel buffer:
  219. if (showVelocity) {
  220. // generate tile max and neighbour max
  221. //Graphics.Blit (velBuffer, tileMax, motionBlurMaterial, 2);
  222. //Graphics.Blit (tileMax, neighbourMax, motionBlurMaterial, 3);
  223. motionBlurMaterial.SetFloat ("_DisplayVelocityScale", showVelocityScale);
  224. Graphics.Blit (velBuffer, destination, motionBlurMaterial, 1);
  225. }
  226. else {
  227. if (filterType == MotionBlurFilter.ReconstructionDX11 && !fallbackFromDX11) {
  228. // need to reset some parameters for dx11 shader
  229. dx11MotionBlurMaterial.SetFloat ("_MinVelocity", minVelocity);
  230. dx11MotionBlurMaterial.SetFloat ("_VelocityScale", velocityScale);
  231. dx11MotionBlurMaterial.SetFloat ("_Jitter", jitter);
  232. // texture samplers
  233. dx11MotionBlurMaterial.SetTexture ("_NoiseTex", noiseTexture);
  234. dx11MotionBlurMaterial.SetTexture ("_VelTex", velBuffer);
  235. dx11MotionBlurMaterial.SetTexture ("_NeighbourMaxTex", neighbourMax);
  236. dx11MotionBlurMaterial.SetFloat ("_SoftZDistance", Mathf.Max(0.00025f, softZDistance) );
  237. dx11MotionBlurMaterial.SetFloat ("_MaxRadiusOrKInPaper", _maxVelocity);
  238. // generate tile max and neighbour max
  239. Graphics.Blit (velBuffer, tileMax, dx11MotionBlurMaterial, 0);
  240. Graphics.Blit (tileMax, neighbourMax, dx11MotionBlurMaterial, 1);
  241. // final blur
  242. Graphics.Blit (source, destination, dx11MotionBlurMaterial, 2);
  243. }
  244. else if (filterType == MotionBlurFilter.Reconstruction || fallbackFromDX11) {
  245. // 'reconstructing' properly integrated color
  246. motionBlurMaterial.SetFloat ("_SoftZDistance", Mathf.Max(0.00025f, softZDistance) );
  247. // generate tile max and neighbour max
  248. Graphics.Blit (velBuffer, tileMax, motionBlurMaterial, 2);
  249. Graphics.Blit (tileMax, neighbourMax, motionBlurMaterial, 3);
  250. // final blur
  251. Graphics.Blit (source, destination, motionBlurMaterial, 4);
  252. }
  253. else if (filterType == MotionBlurFilter.CameraMotion) {
  254. // orange box style motion blur
  255. Graphics.Blit (source, destination, motionBlurMaterial, 6);
  256. }
  257. else if (filterType == MotionBlurFilter.ReconstructionDisc) {
  258. // dof style motion blur defocuing and ellipse around the princical blur direction
  259. // 'reconstructing' properly integrated color
  260. motionBlurMaterial.SetFloat ("_SoftZDistance", Mathf.Max(0.00025f, softZDistance) );
  261. // generate tile max and neighbour max
  262. Graphics.Blit (velBuffer, tileMax, motionBlurMaterial, 2);
  263. Graphics.Blit (tileMax, neighbourMax, motionBlurMaterial, 3);
  264. Graphics.Blit (source, destination, motionBlurMaterial, 7);
  265. }
  266. else {
  267. // simple & fast blur (low quality): just blurring along velocity
  268. Graphics.Blit (source, destination, motionBlurMaterial, 5);
  269. }
  270. }
  271. // cleanup
  272. RenderTexture.ReleaseTemporary (velBuffer);
  273. RenderTexture.ReleaseTemporary (tileMax);
  274. RenderTexture.ReleaseTemporary (neighbourMax);
  275. }
  276. void Remember () {
  277. prevViewProjMat = currentViewProjMat;
  278. prevFrameForward = transform.forward;
  279. prevFrameUp = transform.up;
  280. prevFramePos = transform.position;
  281. }
  282. Camera GetTmpCam () {
  283. if (tmpCam == null) {
  284. string name = "_" + _camera.name + "_MotionBlurTmpCam";
  285. GameObject go = GameObject.Find (name);
  286. if (null == go) // couldn't find, recreate
  287. tmpCam = new GameObject (name, typeof (Camera));
  288. else
  289. tmpCam = go;
  290. }
  291. tmpCam.hideFlags = HideFlags.DontSave;
  292. tmpCam.transform.position = _camera.transform.position;
  293. tmpCam.transform.rotation = _camera.transform.rotation;
  294. tmpCam.transform.localScale = _camera.transform.localScale;
  295. tmpCam.GetComponent<Camera>().CopyFrom(_camera);
  296. tmpCam.GetComponent<Camera>().enabled = false;
  297. tmpCam.GetComponent<Camera>().depthTextureMode = DepthTextureMode.None;
  298. tmpCam.GetComponent<Camera>().clearFlags = CameraClearFlags.Nothing;
  299. return tmpCam.GetComponent<Camera>();
  300. }
  301. void StartFrame () {
  302. // take only x% of positional changes into account (camera motion)
  303. // TODO: possibly do the same for rotational part
  304. prevFramePos = Vector3.Slerp(prevFramePos, transform.position, 0.75f);
  305. }
  306. static int divRoundUp (int x, int d)
  307. {
  308. return (x + d - 1) / d;
  309. }
  310. }
  311. }