{"id":291,"date":"2021-03-27T14:58:27","date_gmt":"2021-03-27T14:58:27","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=291"},"modified":"2021-03-27T14:58:29","modified_gmt":"2021-03-27T14:58:29","slug":"optimized-metaballs-blur-pass-with-scriptable-renderer-features","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/03\/27\/optimized-metaballs-blur-pass-with-scriptable-renderer-features\/","title":{"rendered":"Optimized Metaballs: Blur Pass with Scriptable Renderer Features"},"content":{"rendered":"\n<p>This article completes my approach to drawing pseudo-Metaballs in an optimized way using Unity, the Universal Render Pipeline (URP) and Scriptable Renderer Features. In the previous article, we covered the basics of Scriptable Renderer Features by drawing meshes to a custom render target. In this article, I cover the blur pass, our &#8220;step and clip&#8221; shader and finally drawing our <code>RenderTexture<\/code> back to the screen.<\/p>\n\n\n\n<p>If you need a refresher, you can read the previous article\u00a0<a href=\"https:\/\/bronsonzgeb.com\/?p=240\" target=\"_blank\" rel=\"noreferrer noopener\">here<\/a>. The approach is to draw some objects to a custom render texture, blur them, apply a <code>Smoothstep<\/code> function and draw them back to the screen. Then, we call the result a pseudo-Metaball and pat ourselves on the back for doing a great job. If you&#8217;re looking for &#8220;real&#8221; Metaballs, check out\u00a0<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/2021\/02\/27\/particle-metaballs-in-unity-using-urp-and-shader-graph-part-1\/\">this other series<\/a>\u00a0I wrote.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Structure of a Blur Pass<\/h2>\n\n\n\n<p>Before we start, I&#8217;m going to explain the basic structure of a blur. Blurring is a topic in and of itself. However, it&#8217;s often performed by examining an image&#8217;s pixels and averaging the colour with those around it. The more repetitions you complete, the blurrier the image becomes. This operation sounds straightforward, but there are several ways you can do this, each with its name and resulting look. In our case, we use a Kawase Blur. Why this type of blur? First, it&#8217;s relatively cheap to perform. Second, there was an example of a Kawase Blur in the URP examples Github repository, so it&#8217;s less work. I&#8217;ve linked an article about the Kawase Blur later in this post if you&#8217;re interested in learning more.<\/p>\n\n\n\n<p>So let&#8217;s get started!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Kawase Blur Renderer Feature<\/h2>\n\n\n\n<p>As I mentioned, there&#8217;s an example Kawase Blur available in the URP Rendering examples repository. We&#8217;ll use this as our starting point. The plan is to keep the blur as a separate <code>ScriptableRenderPass<\/code> and enqueue it after our <code>RenderObjectsPass<\/code>. Then we&#8217;ll modify the blur pass to read from the <code>RenderTexture<\/code> created in the <code>RenderObjectsPass<\/code> and finally output the result to the screen with a custom material.<\/p>\n\n\n\n<p>So our blur needs to know:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>The <code>RenderTexture<\/code> to read<\/li><li>How many blur passes to execute<\/li><li>What material to use to perform the blur<\/li><li>What material to use to blit back to the screen<\/li><\/ul>\n\n\n\n<p>Additionally, the sample blur can perform a downsample. By the way, downsampling means crushing a larger image into a smaller image. Downsampling serves two purposes: it&#8217;s a cheap way to blur, and it makes the subsequent blur passes more efficient because there are fewer pixels to blur. So we&#8217;ll expose all these values in the Inspector and set them in the Scriptable Renderer Feature&#8217;s Create method. The complete Create method looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>RenderObjectsPass _renderObjectsPass;\nKawaseBlurRenderPass _blurPass;\n\nconst string PassTag = \"RenderObjectsCustomRenderTarget\";\n&#91;SerializeField] string _renderTargetId;\n&#91;SerializeField] LayerMask _layerMask;\n&#91;SerializeField] Material _blurMaterial;\n&#91;SerializeField] Material _blitMaterial;\n&#91;SerializeField, Range(1, 16)] int _blurPasses = 1;\n\npublic override void Create()\n{\n    int renderTargetId = Shader.PropertyToID(_renderTargetId);\n    _renderObjectsPass = new RenderObjectsPass(PassTag, renderTargetId, _layerMask);\n\n    _blurPass = new KawaseBlurRenderPass(\"KawaseBlur\", renderTargetId)\n    {\n        Downsample = 1,\n        Passes = _blurPasses,\n        BlitMaterial = _blitMaterial,\n        BlurMaterial = _blurMaterial\n    };\n}\n```\nYou can see the Inspector properties in there as well. We also modify the AddRenderPasses method to add our blur pass:\n\n```\npublic override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)\n{\n    renderer.EnqueuePass(_renderObjectsPass);\n    renderer.EnqueuePass(_blurPass);\n}<\/code><\/pre>\n\n\n\n<p>Now that our Scriptable Renderer Feature is complete, we&#8217;ll work on the <code>KawaseBlurRenderPass<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Kawase Blur Render Pass<\/h2>\n\n\n\n<p>So as previously mentioned, I used the example Kawase Blur as a starting point. I copied the class into my file and modified it to suit our needs. Let&#8217;s start with the constructor. We&#8217;ll receive and store the id of our source <code>RenderTexture<\/code>. Then, in the <code>OnCameraSetup<\/code> method, we&#8217;ll use that to get a <code>RenderTargetIdentifier<\/code>. Together that looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>readonly int _blurSourceId;\nRenderTargetIdentifier _blurSourceIdentifier;\n\nreadonly ProfilingSampler _profilingSampler;\n\npublic KawaseBlurRenderPass(string profilerTag, int blurSourceId)\n{\n    _profilingSampler = new ProfilingSampler(profilerTag);\n    _blurSourceId = blurSourceId;\n}\n\npublic override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)\n{\n    _blurSourceIdentifier = new RenderTargetIdentifier(_blurSourceId);\n}<\/code><\/pre>\n\n\n\n<p>Next, in the <code>Configure<\/code> method, we&#8217;ll request two <code>RenderTextures<\/code>. To perform the blur, we render back and forth between two <code>RenderTextures<\/code>, blurring with each iteration. Hence the two <code>RenderTextures<\/code>. We also have the option of downsampling our <code>RenderTextures<\/code>, that is to say, requesting RTs that are smaller than our screen resolution. I chose not to downsample, but the choice is there if you need it. So our <code>Configure<\/code> method looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void Configure(CommandBuffer cmd, RenderTextureDescriptor cameraTextureDescriptor)\n{\n    var width = cameraTextureDescriptor.width \/ Downsample;\n    var height = cameraTextureDescriptor.height \/ Downsample;\n\n    _tmpId1 = Shader.PropertyToID(\"tmpBlurRT1\");\n    _tmpId2 = Shader.PropertyToID(\"tmpBlurRT2\");\n    cmd.GetTemporaryRT(_tmpId1, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32);\n    cmd.GetTemporaryRT(_tmpId2, width, height, 0, FilterMode.Bilinear, RenderTextureFormat.ARGB32);\n\n    _tmpRT1 = new RenderTargetIdentifier(_tmpId1);\n    _tmpRT2 = new RenderTargetIdentifier(_tmpId2);\n\n    ConfigureTarget(_tmpRT1);\n    ConfigureTarget(_tmpRT2);\n}<\/code><\/pre>\n\n\n\n<p>So now we&#8217;re left with the <code>Execute<\/code> method. This method is straightforward. We blit our source <code>RenderTexture<\/code> into one of our blurring <code>RenderTextures<\/code>. Then, we ping pong back and forth between our two <code>RenderTextures<\/code> for the number of passes we specified. Then, we draw it back to the camera&#8217;s render target. By the way, the original example blur allowed the user to choose whether or not it should draw the result to the camera&#8217;s render target, but I removed that option. So this is how the complete method looks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)\n{\n    RenderTextureDescriptor opaqueDesc = renderingData.cameraData.cameraTargetDescriptor;\n    opaqueDesc.depthBufferBits = 0;\n\n    CommandBuffer cmd = CommandBufferPool.Get();\n    using (new ProfilingScope(cmd, _profilingSampler))\n    {\n        \/\/ first pass\n        cmd.SetGlobalFloat(\"_offset\", 1.5f);\n        cmd.Blit(_blurSourceIdentifier, _tmpRT1, BlurMaterial);\n\n        for (var i = 1; i &lt; Passes - 1; i++)\n        {\n            cmd.SetGlobalFloat(\"_offset\", 0.5f + i);\n            cmd.Blit(_tmpRT1, _tmpRT2, BlurMaterial);\n\n            \/\/ pingpong\n            var rttmp = _tmpRT1;\n            _tmpRT1 = _tmpRT2;\n            _tmpRT2 = rttmp;\n        }\n\n        \/\/ final pass\n        cmd.SetGlobalFloat(\"_offset\", 0.5f + Passes - 1f);\n        cmd.Blit(_tmpRT1, renderingData.cameraData.renderer.cameraColorTarget, BlitMaterial);\n    }\n\n    context.ExecuteCommandBuffer(cmd);\n    cmd.Clear();\n\n    CommandBufferPool.Release(cmd);\n}<\/code><\/pre>\n\n\n\n<p>All that&#8217;s left now is to talk about the blur shader and the final &#8220;step and clip&#8221; shader.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Blur Shader<\/h2>\n\n\n\n<p>The blur shader is nearly identical to the one from the example. The only modification is I chose to blur the alpha values as well as the colour values. If you want to learn more about how the Kawase blur works, I linked a detailed article further down. So with that, this is the final frag function in the blur shader:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>fixed4 frag (v2f input) : SV_Target\n{\n    float2 res = _MainTex_TexelSize.xy;\n    float i = _offset;\n\n    fixed4 col;                \n    col = tex2D( _MainTex, input.uv );\n    col += tex2D( _MainTex, input.uv + float2( i, i ) * res );\n    col += tex2D( _MainTex, input.uv + float2( i, -i ) * res );\n    col += tex2D( _MainTex, input.uv + float2( -i, i ) * res );\n    col += tex2D( _MainTex, input.uv + float2( -i, -i ) * res );\n    col \/= 5.0f;\n    \n    return col;\n}<\/code><\/pre>\n\n\n\n<p>The rest of the shader is unchanged from the example. You can see the entire file in the complete Github repository that&#8217;s linked later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step and Clip Shader<\/h2>\n\n\n\n<p>If you recall, a vital piece of this approach is to <code>Smoothstep<\/code> the final values. Doing so helps everything smush together into a nice blob. At the same time, we perform an alpha clip while drawing to the screen. As a reminder, this is what the approach looks like using Photoshop:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep1.png\" alt=\"\" class=\"wp-image-248\" width=\"173\" height=\"229\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep1.png 545w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep1-227x300.png 227w\" sizes=\"auto, (max-width: 173px) 100vw, 173px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2.png\" alt=\"\" class=\"wp-image-249\" width=\"173\" height=\"227\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2.png 604w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2-230x300.png 230w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2-600x789.png 600w\" sizes=\"auto, (max-width: 173px) 100vw, 173px\" \/><\/figure>\n\n\n\n<figure class=\"wp-block-image size-large is-resized\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep3.png\" alt=\"\" class=\"wp-image-250\" width=\"173\" height=\"239\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep3.png 519w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep3-217x300.png 217w\" sizes=\"auto, (max-width: 173px) 100vw, 173px\" \/><\/figure>\n\n\n\n<p>I used Shader Graph for this. The process is straightforward, sample each pixel of our texture, run a <code>Smoothstep<\/code> on both the colour and the alpha values, then output. Because it&#8217;s Shader Graph, there&#8217;s room for anyone to experiment with different approaches. Here&#8217;s an image of the graph:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"550\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader-1024x550.png\" alt=\"\" class=\"wp-image-294\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader-1024x550.png 1024w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader-300x161.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader-768x412.png 768w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader-1536x825.png 1536w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipShader.png 1820w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>All that&#8217;s left now is to put it together. Create a new material with the <code>StepAndClip<\/code> shader. The values you set will depend on your use case, but this is what I ended up on in this example:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"485\" height=\"343\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipMaterial.png\" alt=\"\" class=\"wp-image-295\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipMaterial.png 485w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/StepAndClipMaterial-300x212.png 300w\" sizes=\"auto, (max-width: 485px) 100vw, 485px\" \/><\/figure>\n\n\n\n<p>Then it&#8217;s time to set up the Render Pipeline Asset. You may have already done this if you followed the previous post, but I&#8217;ll go over it again. Ensure the Metaball renderers are on a separate layer. Remove that layer from the Render Pipeline Asset&#8217;s layer masks. Add our Renderer Feature, set the layer mask to the Metaball layer, and finally, add a blur material and blit material. The blur material is the one it&#8217;ll use to ping pong back and forth between render targets, so a material with the Kawase Blur shader will do. The blit material is the one it&#8217;ll use to draw to the screen at the end, so we&#8217;ll use our<code> StepAndClip<\/code> material. With that, we&#8217;re all done! You should have a result like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"764\" height=\"764\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000.jpg\" alt=\"Final optimized metaballs\" class=\"wp-image-289\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000.jpg 764w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000-300x300.jpg 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000-150x150.jpg 150w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000-238x238.jpg 238w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/image_003_0000-100x100.jpg 100w\" sizes=\"auto, (max-width: 764px) 100vw, 764px\" \/><\/figure>\n\n\n\n<p>They&#8217;re obviously not as attractive as &#8220;real&#8221; Metaballs, but they&#8217;re more optimized and work well under the right circumstances.<\/p>\n\n\n\n<p><strong>If you like my blog, why not sign up for my mailing list&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/join-my-mailing-list\/\"><strong>here<\/strong><\/a><strong>? The complete project is available&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/UnityScreenSpaceMetaballs\"><strong>here<\/strong><\/a><strong>&nbsp;on Github. If you want to learn about the Kawase blur, check out&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/software.intel.com\/content\/www\/us\/en\/develop\/blogs\/an-investigation-of-fast-real-time-gpu-based-image-blur-algorithms.html\"><strong>this article<\/strong><\/a><strong>. If you&#8217;d like to see this approach to Metaballs in action, check out this&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/www.youtube.com\/watch?v=FR618z5xEiM\"><strong>Mix And Jam video<\/strong><\/a><strong>&nbsp;on Splatoon&#8217;s Ink System.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This article completes my approach to drawing pseudo-Metaballs in an optimized way using Unity, the Universal Render Pipeline (URP) and Scriptable Renderer Features. In the previous article, we covered the basics of Scriptable Renderer Features by drawing meshes to a custom render target. In this article, I cover the blur pass, our &#8220;step and clip&#8221; [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":284,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17,1],"tags":[15,24,23,5,13,12],"class_list":["post-291","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-graphics","category-unity-programming","tag-metaball","tag-scriptable-render-pass","tag-scriptable-renderer-feature","tag-unity","tag-universal-render-pipeline","tag-urp"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/291","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/comments?post=291"}],"version-history":[{"count":8,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/291\/revisions"}],"predecessor-version":[{"id":301,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/291\/revisions\/301"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/284"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=291"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=291"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=291"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}