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 }
make sure to match this to MAX_RADIUS in shader ('k' in paper)
CameraMotion = 0, global screen blur based on cam motion
LocalBlur = 1, cheap blur, no dilation or scattering
Reconstruction = 2, advanced filter (simulates scattering) as in plausible motion blur paper
ReconstructionDX11 = 3, advanced filter (simulates scattering) as in plausible motion blur paper
ReconstructionDisc = 4, advanced filter using scaled poisson disc sampling
settings
public bool preview = false; show how blur would look like in action ...
public Vector3 previewScale = Vector3.one; ... given this movement vector
params
public float maxVelocity = 8.0f; maximum velocity in pixels
public float minVelocity = 0.1f; minimum velocity in pixels
public float velocityScale = 0.375f; global velocity scale
public float softZDistance = 0.005f; for z overlap check softness (reconstruction filter only)
public int velocityDownsample = 1; low resolution velocity buffer? (optimization)
resources
(internal) debug
camera transforms
shortcuts to calculate global blur direction when using 'CameraMotion'
wasActive = false; hack to fake positionrotation update and prevent bad blurs
CheckSupport (true, true); depth & hdr needed
use if possible new RG format ... fallback to half otherwise
get temp textures
float _maxVelocity = maxVelocity; calculate 'k'
note: 's' is hardcoded in shaders except for DX11 path
auto DX11 fallback!
calc correct viewprj matrix
just started up?
matrices
texture samplers
generate an artifical 'previous' matrix to simulate blur look
offset.SetTRS(previewScale * 0.3333f, Quaternion.identity, Vector3.one); using only translation
build blur vector to be used in shader to create a global blur direction
pitch (vertical)
blurVector.x = rotationScale * farHeur;Mathf.Clamp01((1.0ff-Vector3.Dot(transform.up, prevFrameUp)));
yaw #1 (horizontal, faded by pitch)
blurVector.y = rotationScale * lookUpDown * farHeur;Mathf.Clamp01((1.0ff-Vector3.Dot(transform.forward, prevFrameForward)));
yaw #2 (when looking down, faded by 1-pitch)
blurVector.z = rotationScale * (1.0f- lookUpDown) * farHeur;Mathf.Clamp01((1.0ff-Vector3.Dot(transform.forward, prevFrameForward)));
forward (probably most important)
jump (maybe scale down further)
strafe (maybe scale down further)
if (preview) crude approximation
generate velocity buffer
patch up velocity buffer:
exclude certain layers (e.g. skinned objects as we cant really support that atm)
if (excludeLayers.value != 0) || dynamicLayers.value)
remember current transformation data for next frame
debug vel buffer:
generate tile max and neighbour max
Graphics.Blit (velBuffer, tileMax, motionBlurMaterial, 2);
Graphics.Blit (tileMax, neighbourMax, motionBlurMaterial, 3);
need to reset some parameters for dx11 shader
texture samplers
generate tile max and neighbour max
final blur
'reconstructing' properly integrated color
generate tile max and neighbour max
final blur
orange box style motion blur
dof style motion blur defocuing and ellipse around the princical blur direction
'reconstructing' properly integrated color
generate tile max and neighbour max
simple & fast blur (low quality): just blurring along velocity
cleanup
if (null == go) couldn't find, recreate
take only x% of positional changes into account (camera motion)
TODO: possibly do the same for rotational part