{"id":240,"date":"2021-03-20T15:59:16","date_gmt":"2021-03-20T15:59:16","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=240"},"modified":"2021-03-20T15:59:17","modified_gmt":"2021-03-20T15:59:17","slug":"pseudo-metaballs-with-scriptable-renderer-features-in-unitys-urp","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/03\/20\/pseudo-metaballs-with-scriptable-renderer-features-in-unitys-urp\/","title":{"rendered":"Pseudo-metaballs with Scriptable Renderer Features in Unity&#8217;s URP"},"content":{"rendered":"\n<p>This article shows my approach to drawing Metaballs in an optimized way using Unity, the <em>Universal Render Pipeline<\/em> (URP) and <em>Scriptable Renderer Features<\/em>.<\/p>\n\n\n\n<p>So, the overall approach is to draw our Metaball objects to a <em>RenderTexture<\/em>, blur them, apply a <em>Smoothstep<\/em>, and draw them to the screen. The optimization is that our Metaball objects will be standard sphere meshes. I developed this technique while working on a Splatoon-like ink system for the Mix and Jam YouTube channel. That video describes the approach visually by using Photoshop, so I encourage you to check it out. The video is linked later in this post. To do all this, we&#8217;re going to use a <em>Scriptable Renderer Feature<\/em>, so let me explain how they work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scriptable Renderer Features<\/h2>\n\n\n\n<p>Scriptable Renderer Features are part of Unity&#8217;s Universal Render Pipeline. They allow you to augment the existing render pipeline with custom features. In this context, a feature is a collection of render passes. So every Scriptable Renderer Feature comes with at least one Scriptable Render Pass. I found this confusing at first, so I&#8217;ll repeat: Scriptable Renderer Features contain one or more Scriptable Render Passes.<\/p>\n\n\n\n<p>A Scriptable Render Feature has two main methods: <code>Create<\/code> and <code>AddRenderPasses<\/code>. <code>Create<\/code> is where you initialize your Scriptable Render Passes, and <code>AddRenderPasses<\/code> is where you add them to the render queue. Additionally, if you want to expose values in the Inspector, the Scriptable Renderer Feature is the place to do it. A stripped-down Scriptable Renderer Feature looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>CustomRenderPass m_ScriptablePass;\n\npublic override void Create()\n{\n    m_ScriptablePass = new CustomRenderPass();\n    m_ScriptablePass.renderPassEvent = RenderPassEvent.AfterRenderingOpaques;\n}\n\npublic override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)\n{\n    renderer.EnqueuePass(m_ScriptablePass);\n}<\/code><\/pre>\n\n\n\n<p>It&#8217;s not a lot of code because the real meat is in the <em>Scriptable Render Passes<\/em>. <em>Scriptable Render Passes<\/em> are where you provide instructions to the Rendering Pipeline, so let&#8217;s look at that next.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Scriptable Render Passes<\/h2>\n\n\n\n<p>A barebones Scriptable Render Pass contains three methods: <code>OnCameraSetup<\/code>, <code>Execute<\/code>, and <code>OnCameraCleanup<\/code>. As you may have guessed, <code>OnCameraSetup<\/code> is where you set up the render pass, <code>Execute<\/code> contains the rendering logic, and <code>OnCameraCleanup<\/code> is where you clean up any resources. In a simple example, you&#8217;d set up your render targets, draw into them, and release them at the end. We perform most of the work using <em>Command Buffers<\/em>. A <em>Command Buffer<\/em> is a Unity class that holds a list of rendering commands.<\/p>\n\n\n\n<p>Now I have to be honest, the documentation on Scriptable Render Passes is sparse. I examined URP&#8217;s code and example code to learn enough to write my solution, but there are still certain aspects I can&#8217;t explain. For example, the documentation recommends calling <code>ConfigureTarget<\/code> on your render targets inside the <code>Configure<\/code> function instead of using the Command Buffer command <code>SetRenderTarget<\/code>. However, it doesn&#8217;t explain what to do if you have to ping pong between multiple render targets like you would when blurring. In the official UniversalRenderingExamples Github repository, there&#8217;s an example blur in which <code>ConfigureTarget<\/code> is called twice: once for each temporary render target. Upon inspecting the <code>ConfigureTarget<\/code> code, it looks like calling it a second time will invalidate any state changes made by calling it the first time. So is this the correct way to do it? Who knows. Then there&#8217;s the issue of resource clean-up. Calling <code>ConfigureTarget<\/code> ensures that your render textures are automatically cleaned up. However, given that calling <code>ConfigureTarget<\/code> a second time invalidates the reference to the first render target, I believe it&#8217;s safe to assume it won&#8217;t be able to clean it up either. Finally, there&#8217;s a <code>Blit<\/code> function that also changes the current render target. However, it&#8217;s unclear whether this also invalidates the reference to the render target set through <code>ConfigureTarget<\/code>. So my point is that I tried my best to reach a working solution. If you can provide answers or feedback, please do it. Seriously, I&#8217;m begging you.<\/p>\n\n\n\n<p>Anyway, let&#8217;s start on our pseudo-metaballs.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Pseudo-Metaballs<\/h2>\n\n\n\n<p>As mentioned, the approach is:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Draw regular mesh particles to a render texture.<\/li><li>Blur them.<\/li><li>Smoothstep the color.<\/li><li>Draw the render texture to the screen.<\/li><\/ul>\n\n\n\n<p>If we do this is Photoshop it looks like this:<\/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=\"260\" height=\"343\" 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: 260px) 100vw, 260px\" \/><\/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=\"260\" height=\"340\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2.png 604w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/MetaballStep2-230x300.png 230w\" sizes=\"auto, (max-width: 260px) 100vw, 260px\" \/><\/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=\"260\" height=\"359\" 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: 260px) 100vw, 260px\" \/><\/figure>\n\n\n\n<p>So, let&#8217;s start drawing the particles. URP has a built-in Renderer Feature to render objects. Unfortunately, it doesn&#8217;t support rendering to a custom render target. However, we can still use the code to write a version that does. At the core of the built-in <code>RenderObjectsPass<\/code> is the method <code>ScriptableRenderContext.DrawRenderers<\/code>. This method does what it sounds like: it renders stuff. Most of the code I&#8217;m about to share is boilerplate setting the state so that <code>DrawRenderers<\/code> knows what to render. Later I&#8217;ll share the entire <em>RenderObjectsCustomRenderTarget<\/em> file, but first, I&#8217;ll break it down. Let&#8217;s analyze the <code>Execute<\/code> method of the <code>RenderObjectsPass<\/code> (where the rendering happens):<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void Execute(ScriptableRenderContext context, ref RenderingData renderingData)\n{\n    SortingCriteria sortingCriteria = SortingCriteria.CommonOpaque;\n    DrawingSettings drawingSettings =\n        CreateDrawingSettings(_shaderTagIds, ref renderingData, sortingCriteria);\n\n    CommandBuffer cmd = CommandBufferPool.Get();\n    using (new ProfilingScope(cmd, _profilingSampler))\n    {\n        context.DrawRenderers(renderingData.cullResults, ref drawingSettings, ref _filteringSettings,\n            ref _renderStateBlock);\n    }\n\n    context.ExecuteCommandBuffer(cmd);\n    cmd.Clear();\n    CommandBufferPool.Release(cmd);\n}<\/code><\/pre>\n\n\n\n<p>Like I mentioned before, we set up the state of our renderer and call <code>DrawRenderers<\/code>. So let&#8217;s work our way backwards. <code>DrawRenderers<\/code> takes the results of our culling step, some drawing settings, some filtering settings, and a <code>RenderStateBlock<\/code>. Let&#8217;s break down each of these arguments.<\/p>\n\n\n\n<p>Cull results are part of the <code>renderingData<\/code>, which is automatically passed into the <code>Execute<\/code> method. The cull results are essentially the renderers left after removing those that are offscreen. Drawing settings take a list of <code>ShaderTagIds<\/code>, <code>RenderingData<\/code> and <code>SortingCriteria<\/code>. <code>ShaderTagIds<\/code> filter our which shader passes to run; we&#8217;ll set that up later. <code>SortingCriteria<\/code> determines how to sort the renderers. There are several options, but for simplicity, we use <code>CommonOpaque<\/code>. This setting will sort our objects in the way typical to other opaque things. That means if you use a transparent material, it&#8217;s going to look funny. Next, we have the <code>FilteringSettings<\/code> and <code>RenderStateBlock<\/code>. We&#8217;ll set up the <code>ShaderTagIds<\/code>, <code>FilteringSettings<\/code> and <code>RenderStateBlock<\/code> in our pass&#8217; constructor because we only need to do it once. The rest of <code>Execute<\/code> is straightforward. We request a <code>CommandBuffer<\/code>, set up our Profiling Scope (this allows us to see our pass in the profiler), execute our command buffer and finally release it.<\/p>\n\n\n\n<p>So now for the constructor.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public RenderObjectsPass(string profilerTag, int renderTargetId, LayerMask layerMask)\n{\n    _profilingSampler = new ProfilingSampler(profilerTag);\n    _renderTargetId = renderTargetId;\n\n    _filteringSettings = new FilteringSettings(null, layerMask);\n\n    _shaderTagIds.Add(new ShaderTagId(\"SRPDefaultUnlit\"));\n    _shaderTagIds.Add(new ShaderTagId(\"UniversalForward\"));\n    _shaderTagIds.Add(new ShaderTagId(\"UniversalForwardOnly\"));\n    _shaderTagIds.Add(new ShaderTagId(\"LightweightForward\"));\n\n    _renderStateBlock = new RenderStateBlock(RenderStateMask.Nothing);\n}<\/code><\/pre>\n\n\n\n<p>Let&#8217;s work through this. We pass a layer mask to the filtering settings, so the <code>DrawRenderers<\/code> method will only draw a given layer&#8217;s objects. As for <code>ShaderTagIds<\/code>, we add all the default URP tags. In other words, we won&#8217;t filter out any shader passes. And finally, set the <code>RenderStateBlock<\/code> to a default value of Nothing. The <code>RenderStateBlock<\/code> allows you to override parts of the render state (such as <code>BlendState<\/code>, <code>DepthState<\/code>, etc.) with your own values.<\/p>\n\n\n\n<p>Now we&#8217;ll look at the <code>OnCameraSetup<\/code> function.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public override void OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)\n{\n    RenderTextureDescriptor blitTargetDescriptor = renderingData.cameraData.cameraTargetDescriptor;\n    cmd.GetTemporaryRT(_renderTargetId, blitTargetDescriptor);\n    _renderTargetIdentifier = new RenderTargetIdentifier(_renderTargetId);\n    ConfigureTarget(_renderTargetIdentifier);\n    ConfigureClear(ClearFlag.All, Color.clear);\n}<\/code><\/pre>\n\n\n\n<p>The purpose of this method is to get a <code>RenderTexture<\/code> and set it as our render target. So, we copy the camera&#8217;s <code>RenderTextureDescriptor<\/code>, request a <code>RenderTexture<\/code> with a given id and descriptor, and set it as our target. This includes, for example, information like pixel format, number of depth buffer bits, width and height, etc.<\/p>\n\n\n\n<p>That was a lot of code to go over! The <code>ScriptableRendererFeature<\/code> is much smaller:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>const string PassTag = \"RenderObjectsCustomRenderTarget\";\n&#91;SerializeField] string _renderTargetId;\n&#91;SerializeField] LayerMask _layerMask;\n\npublic override void Create()\n{\n    int renderTargetId = Shader.PropertyToID(_renderTargetId);\n    _renderObjectsPass = new RenderObjectsPass(PassTag, renderTargetId, _layerMask);\n}\n\npublic override void AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)\n{\n    renderer.EnqueuePass(_renderObjectsPass);\n}<\/code><\/pre>\n\n\n\n<p>All we do is pass values from the Inspector into our render pass and enqueue that pass. Putting it all together, we have a barebones renderer feature that draws objects of a given layer into a custom render target. If you add this renderer feature to your URP Renderer asset, you can verify the result in the frame debugger. It looks like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"696\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/FrameDebuggerExample-1024x696.png\" alt=\"\" class=\"wp-image-279\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/FrameDebuggerExample-1024x696.png 1024w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/FrameDebuggerExample-300x204.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/FrameDebuggerExample-768x522.png 768w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/FrameDebuggerExample.png 1193w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>By the way, my URP Renderer Asset is set up like this, but it would probably be easier for you to inspect it in the completed project:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"656\" height=\"859\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/URPAssetExample.png\" alt=\"\" class=\"wp-image-280\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/URPAssetExample.png 656w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/03\/URPAssetExample-229x300.png 229w\" sizes=\"auto, (max-width: 656px) 100vw, 656px\" \/><\/figure>\n\n\n\n<p>I&#8217;ll briefly go over my particle system. This approach&#8217;s strength is it works for any standard meshes, so to demonstrate, I created a simple particle system that renders sphere meshes. It has a standard&nbsp;<em>URP Lit<\/em>&nbsp;Material, and it exists on its own layer. I disabled that layer in the URP Renderer asset Layer Mask and assigned it to our Renderer Feature.<\/p>\n\n\n\n<p>At this point, we still haven&#8217;t covered the blur pass or drawing our <code>RenderTexture<\/code> back to the screen. However, we&#8217;ve done a lot. So rather than try to cram everything into one post, I&#8217;ve saved the blur pass for next time. I&#8217;ve posted the entire project (blur pass included) on Github for you to explore, and linked it at the end of the post.<\/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=\"\" 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>The final result is admittedly not as lovely as the real Metaballs from the previous series. However, in the context of a game, it can fit in well. Plus, we can now have a lot more of them without killing performance. If you want to see that, check out the Splatoon Ink System video linked at the bottom.<\/p>\n\n\n\n<p><strong>If you appreciate my blog, consider signing up to 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>; so you never miss a post. 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 see this approach 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 shows my approach to drawing Metaballs in an optimized way using Unity, the Universal Render Pipeline (URP) and Scriptable Renderer Features. So, the overall approach is to draw our Metaball objects to a RenderTexture, blur them, apply a Smoothstep, and draw them to the screen. The optimization is that our Metaball objects will [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":288,"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-240","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\/240","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=240"}],"version-history":[{"count":42,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/240\/revisions"}],"predecessor-version":[{"id":290,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/240\/revisions\/290"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/288"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=240"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=240"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=240"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}