Custom motion blur effect in UnityURP using shader graph
#Unity #ShaderGraph #Unity tutorials #VFX #MotionBlur
Welcome! In this post, I'll guide you through the process of crafting a straightforward custom motion blur using Unity's Shader Graph within the Universal Render Pipeline (URP).
Motion blur stands as one of the most widely utilized visual effects in gaming, movies, anime, and the broader digital realm. The primary concept behind this effect is to enhance the sensation of speed for players or characters. While some players may find this effect overly aggressive at times, potentially hindering the enjoyment of gameplay, its absence can leave us in the dark about the player's speed—whether they're moving swiftly or at a leisurely pace. This is particularly crucial in genres like flight simulation, as exemplified by our game RENATURA. To address these considerations, I've tryed to develope a fully controllable motion blur shader, taking every aspect into careful account.
First and foremost, let's consider the components we should use to achieve the desired result. For this case, utilize the following setup:
1. Radial mask
2. Distortion UV effect
3. Fake motion blur
4. Code Time!
1. Radial mask
Start by creating a screenspace shader graph. To construct the mask, center the UV space by splitting the screen position node, taking a Vector 2 as the future UV. Then, subtract 0.5 from this vector, to center the UV pivot at the screen's center. Utilize the Length function to determine the distance between the UV pivot and the Vector2 coordinates. For a better understanding of Length {Length = r; Length = sqrt(U^2 + V^2)} refer to the Equation of a circle.
To show the result in screen space we should add Full Screen Pass Renderer Feature in our URP settings, and add our material to Pass Material field.
Now, we have a stretched circle in the screen center.
To address this issue, consider the Aspect Ratio: the proportional relationship between the width and height of an image.
Split UV and multiply U(R) component to Screen node with divided (Width/Height).
So now when we change window size our circle don't stretch
Add Blur Mask group to Change UV pivot postion group. To input of smoothstep node add negative value (or subtract) of BlurMaskSize parameter (circle radius). To Edge2 add BlurMaskSmoothnes parameter to control shade transition. Finally connect Smoothstep node with Saturate node to avoid negative value.
Controlled parameters: BlurMaskSize, BlurMaskSmoothness.
2. Distortion UV effect
Next, create the distortion UV effect using the URP sample buffer node.
The distortion UV effect can be split into two components:
UV Radial God rays (distortion effect)
To achieve this effect, centralize UV, then Split and normalize Vector 2. A normalized vector will have same direction as original vector and a length of 1 and is often referred to as the unit vector. In this example we see how we can achieve this effect using Normalize node and connect with Voronoi UV input.
Check it in desmos.
For the Voronoi noise, introduce an AngleOffset and integrate a time parameter for dynamic animation. Include the GodRaysDensity parameter to adjust the density of distortion rays. Additionally, introduce the GodRaysStrength parameter, which multiplies the BlurMask group output, influencing the strength of the distortion effect.
The sine function defaults to an amplitude ranging from -1 to 1. To prevent black artifacts, we must determine the appropriate coefficient. In this instance, it is -0.42 (referred to as SinePositionRatio henceforth).
How can we currently view our scene on the screen? Utilize the URP Sampler Buffer node in BitSource mode, and for the UV input, it's essential to set ScreenPosition in Default mode. The use of center mode or any other mode is not feasible since the URP Sample Buffer only retains screen space information. Introducing an offset to the UV results in black artifacts. To manipulate UV distortion effectively, connect the GodRaysDistortionOffset group to the offset input of Tiling And Offset node. Consequently, the screen position UV is distorted, leading to the achievement of a simple yet effective distortion effect!
Black artifacts happened because URP Sample Buffer does not store information out of visible screen space.
Controlled parameters: GodRaysStrength, BlurMaskSize, BlurMaskSmoothness, GodRaysDensity.
To avoid this issue we should zoom image, change Tiling value from 1 to 0.9 (TilingRays temporary parameter).
Controlled parameters: GodRaysStrength, BlurMaskSmoothnes, BlurMaskSize.
Radial rays of light
Now, let's generate Radial Rays of Light and apply color to them. Introduce a new mask for this effect, utilizing the same mask as before.
Connect MaskGodRays group output to Ramap node of RadialRayOfLight group. By remaping node we control amount of rays. Add GodRaysDistotrionOffset group and Reamp node of RadialRayOfLight group.
Controlled parameters: GodRaysAmount, GodRaysColor, GodRaysLightMaskSize, GodRaysLightMaskSmoothness.
领英推荐
Let's fix the screen space position of our effect. A new issue arises; in the previous step, we zoomed our effect by tiling to 0.9 (temporary parameter called TilingRays). Now, we need to center it.
Perform a linear interpolation (lerp) on the SampleBuffer, both without and with the distortion effect. Introduce the FXOpacity parameter to easily check the results.
Now, we see that it's tiling from the left bottom corner, which is the default UV screen pivot. We want to achieve a scale effect from the center of the screen to avoid the screen shift effect!
Controlled parameters: FXOpacity, TilingRays.
Using simple math, to link offset and tiling together to centralize scaling. Add a parameter, BlureZoneScale (BlurAmount in future), representing the distance in UV coordinate space between our screen border and the scaled Sample Buffer image with the distortion effect.
Now blur zone can scale at center point of the screen.
Controlled parameters: FXOpacity, BlurZoneScale (BlurAmount).
3. Fake motion blur
Time to create a fake motion blur based on a layering effect.
The main idea is add some scaled layers, and together, they can look like a motion blur effect.
In this case, we add two additional layers and create a slider [0 : 1] - BlurAmount (renamed BlurZoneScale). Remap this from [0 : 1] to [0 : 0.15]. Use math to parameterize the distance between each image. One image we can divide by 3, and the second multiply this result by 2.
Divide adding result by number of layers (3 layers) to return to normal intensity.
So, in result, we have a fake motion blur effect.
Controled parameters: BlurAmount, FXOpacity.
Create one more mask for area without blur effect, call it the NoBlurZoneMask group. Now we have 4 layers, 3 "with blur effect + distortion UV", and one layer "without effects"...
FXOpacity connect to OneMinus node to invert value, then add with NoBlurZoneMask group output. Saturate result to avoid negative values.
In result we should have this MotionBlurGaraph:
Currently, the FXOpacity parameter governs the overall impact of all effects. While we can use it to control our motion blur effect, it may not provide the precision we desire. Let's examine the outcome of our motion blur to better understand its effectiveness.
Controled parameters: NoBLurMaskSize, NoBlurMaskSmoothness, BlurAmount, GodRaysDensity.
Utilize the FXOpacity parameter to compare the original screen image with the image after applying our FX. In this scenario, we don't manipulate FXOpacity to initiate motion blur; instead, we control the following parameters:
To begin, let's prioritize selecting the trigger for the occurrence of motion blur. We need to identify a singular input value, and in our context, that value is the player's speed. As the speed increases, the visibility of the motion blur effect intensifies. To attain this desired outcome, we should employ linear interpolation (lerp) on our parameters, transitioning smoothly from 0 to the point where the motion blur impact reaches its maximum value.
Utilize new parameters, all of these are controlled by code:
Remap (from MinSpeedToShowBlur to MaxSpeedToShowBlur) to (from 0 to 1); and clamp CurrentSpeed from MinSpeedToShowBlur to MaxSpeedToShowBlur to limit and protect our input.
To each changeable parameter (BlurMaskSize, GodRaysAmount, BlurAmount), create a lerp node. Remap the output should connect to the T input in every lerp node.
Compare our previous method to control blur amount with current: FX Opacity vs CurrentSpeed.
Currently transition looks much better! So, we done all preparation to start coding!
4. CodeTime!
Create a script called ScreenMotionBlurBehavior to configure our material parameters: MinSpeedToShowBlur, MaxSpeedToShowBlur, GodRaysAmount, BlurMaskSize, and BlurAmount. In the FixedUpdate method, assign the rigidbody speed to the CurrentSpeed parameter of our MotionBlur material.
using UnityEngine;
public class ScreenMotionBlurBehavior : MonoBehaviour
{
public Material blurMaterial;
[SerializeField]
private float MinSpeedToShowBlur = 10f;
[SerializeField]
private float MaxSpeedToShowBlur = 15f;
[SerializeField]
[Range(0,1)] private float GodsRayAmount = 0.5f;
[SerializeField]
[Range(0,1)]private float BlurMaskSize = 0.4f;
[SerializeField]
[Range(0,1)]private float BlurAmount = 0.2f;
[SerializeField]
[Range(0,0.1f)]private float BlurZoneScale = 0.02f;
private Rigidbody rb;
private void Awake()
{
rb = GetComponent<Rigidbody>();
blurMaterial.SetFloat("_MinSpeedToShowBlur", MinSpeedToShowBlur);
blurMaterial.SetFloat("_MaxSpeedToShowBlur", MaxSpeedToShowBlur);
blurMaterial.SetFloat("_GodsRayAmount", GodsRayAmount);
blurMaterial.SetFloat("_BlurMaskSize", BlurMaskSize);
blurMaterial.SetFloat("_BlurAmount", BlurAmount);
}
private void FixedUpdate()
{
float speed = rb.velocity.magnitude;
//We can add condition to pass value of a current speed to shader
if(speed>=MinSpeedToShowBlur-1f) {
blurMaterial.SetFloat("_CurrentSpeed", speed);
}
}
}
Assign this script to our player object and enjoy result!
Conclusion:
In wrapping up, we've successfully crafted a custom motion blur effect for Unity's URP using Shader Graph, tailored for indie game development. Through manipulation of UV space, strategic use of the URP sample buffer, and creative layering, we've achieved an efficient and visually appealing result.
Our exploration covered the nuances of shader-based visual effects, from addressing challenges in UV space to dynamically spacing fake motion blur layers. The integration of parameter synchronization, particularly lerping within the shader, ensures real-time control based on factors like player speed, optimizing performance.
In essence, this tutorial not only provides a practical guide for implementing custom motion blur but also encourages a deeper understanding of shader programming concepts. As you apply these techniques to your indie game projects, may your creativity thrive, and your visual effects immerse players in captivating virtual worlds. Happy coding!
Freelance Software Engineer @ Pit Stop Games | Gaming Industry
4 个月Is this compatible with mobile devices? iOS and Android? And also is this available to buy?