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