{"id":133,"date":"2021-02-27T15:28:46","date_gmt":"2021-02-27T15:28:46","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=133"},"modified":"2021-03-08T19:14:16","modified_gmt":"2021-03-08T19:14:16","slug":"particle-metaballs-in-unity-using-urp-and-shader-graph-part-1","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/02\/27\/particle-metaballs-in-unity-using-urp-and-shader-graph-part-1\/","title":{"rendered":"Particle Metaballs in Unity using URP and Shader Graph Part 1"},"content":{"rendered":"\n<p>This series will explain how to draw Metaballs in Unity using the Universal Render Pipeline (URP) and Shader Graph. But that&#8217;s not all! It&#8217;ll also explain how to use a Particle System to control the size and position of our Metaballs. I wanted to fit everything into a single article, but unfortunately, it was too much. By the end of part 1 we&#8217;ll be able to sphere-trace an object using a custom Shader Graph node. Click <a href=\"https:\/\/bronsonzgeb.com\/index.php\/2021\/03\/06\/particle-metaballs-in-unity-using-urp-and-shader-graph-part-2\/\">here<\/a> to jump ahead to part 2.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What are Metaballs?<\/h2>\n\n\n\n<p>Many people have already explained Metaballs at great length, so rather than give a full technical explanation, I&#8217;ll jump right into why they&#8217;re interesting to me. However, if an in-depth technical overview interests you, then I&#8217;ll share some resources later in this post.<\/p>\n\n\n\n<p>What&#8217;s cool about Metaballs (also known as Blobbies) is they&#8217;re blobs that merge when they&#8217;re close to each other and split apart once they&#8217;re separated. Imagine two droplets of water pooling together to form a single entity and you&#8217;ll get the idea. What&#8217;s tricky about them is they&#8217;re an implicit surface, which is to say, they aren&#8217;t meshes. Instead, we use math functions to represent them and raymarching to unveil them. How this works is we use a mesh to define a mask, then in a shader, we trace a ray from each pixel in the mask until we find a surface.<\/p>\n\n\n\n<p>So with that explanation out of the way, let&#8217;s break down the different steps:&nbsp;<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>We need a Shader Graph that can display a Metaball.<\/li><li>We need to feed our shader with data from the Particle System.<\/li><li>We need to shade our Metaball using our lighting and PBR.<\/li><\/ol>\n\n\n\n<p>In this first part of the series we&#8217;ll cover just the first step. So let&#8217;s get started!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Metaball Shader Graph<\/h2>\n\n\n\n<p>So first thing&#8217;s first, let&#8217;s create a Shader Graph to display a single Metaball. Doing so will validate our Sphere-Tracing code and Metaball inside of Shader Graph. We&#8217;ll do this using a Custom Function Node.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Shader Graph Custom Function Nodes<\/h3>\n\n\n\n<p>Shader Graph supports custom function nodes. These nodes allow you to specify the inputs and outputs and write any arbitrary shader code. In my first implementation, I wrote the entire shader inside a single custom function and plugged that into the shader output. You might be asking, &#8220;In that case, what&#8217;s the point of using Shader Graph?&#8221; which is a valid question. However, I&#8217;ve learned that Shader Graph&#8217;s power is the ability to break up my custom nodes and allow users to recompose them in a way that suits them. For me, the flexibility to combine custom low-level shader code with pre-existing high-level nodes singlehandedly proved Shader Graph&#8217;s value as a single solution to&nbsp;<strong><em>all my future shader development<\/em><\/strong>. So from now on, it&#8217;s Shader Graph 4 life.<\/p>\n\n\n\n<p>Custom function nodes can take straight text input, or they can read from a file. In our case, we&#8217;re just going to create a&nbsp;<em>Metaball.hlsl<\/em>&nbsp;file and go from there. At this time, we want to validate our work. That is, is the sphere-tracer working? Can we see a Metaball? So, the only input we need is the mesh&#8217;s world space position, and our sole output will be an alpha value. The alpha value will either be 0 if we didn&#8217;t find a surface or 1 otherwise.&nbsp;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void SphereTraceMetaballs_float(float3 WorldPosition, out float Alpha)\n{\n    #if defined(SHADERGRAPH_PREVIEW)\n    Alpha = 1;\n    #else\n    Alpha = 1;\n    #endif\n}<\/code><\/pre>\n\n\n\n<p><em><strong>Sidenote<\/strong><\/em>: I always add <strong><em>#if defined(SHADERGRAPH_PREVIEW)<\/em><\/strong>&nbsp;block right away because I often take advantage of functions built-in to URP. Those functions appear to be inaccessible from inside the Shader Graph Preview and so using them throws errors. That said, if somebody knows how to include them, I&#8217;d love to know.<\/p>\n\n\n\n<p>I&#8217;ll breakdown the structure of this custom function. <strong><em>SphereTraceMetaballs<\/em><\/strong> is the name, and we use&nbsp;<strong><em>_float&nbsp;<\/em><\/strong>to specify the precision. Shader Graph allows users to switch precision, so it&#8217;s good practice to include another version called&nbsp;<strong><em>SphereTraceMetaballs_half<\/em><\/strong>&nbsp;that uses&nbsp;<em>halfs (halves?)<\/em>&nbsp;instead of floats, but I don&#8217;t worry about that until the end. The first argument&nbsp;<strong><em>WorldPosition<\/em><\/strong>&nbsp;is our input, and any arguments prefixed by&nbsp;<strong><em>out<\/em><\/strong>&nbsp;are our outputs, so&nbsp;<strong><em>out float Alpha<\/em><\/strong>. Now in Shader Graph, you can create a Custom Function node, point it to the&nbsp;<em>Metaball.hlsl<\/em>&nbsp;file and fill out the inputs, outputs and function name. It should look like this:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"851\" height=\"429\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image.png\" alt=\"\" class=\"wp-image-158\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image.png 851w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-300x151.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-768x387.png 768w\" sizes=\"auto, (max-width: 851px) 100vw, 851px\" \/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">SphereTraceMetaballs function<\/h3>\n\n\n\n<p>I&#8217;m not going to go over the Sphere-Tracer code. Why? Because later in the article, I&#8217;m going to share with you where I read about it and found the code that I ported to hlsl. Here&#8217;s the code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void SphereTraceMetaballs_float(float3 WorldPosition, out float Alpha)\n{\n    #if defined(SHADERGRAPH_PREVIEW)\n    Alpha = 0;\n    #else\n    float maxDistance = 100;\n    float threshold = 0.00001;\n    float t = 0;\n    int numSteps = 0;\n\n    float outAlpha = 0;\n\n    float3 viewPosition = GetCurrentViewPosition();\n    half3 viewDir = SafeNormalize(WorldPosition - viewPosition);\n    while (t &lt; maxDistance)\n    {\n        float minDistance = 1000000;\n        float3 from = viewPosition + t * viewDir;\n        float d = GetDistanceSphere(from, float3(0, 0, 0), 0.3);\n        if (d &lt; minDistance)\n        {\n            minDistance = d;\n        }\n\n        if (minDistance &lt;= threshold * t)\n        {\n            outAlpha = 1;\n            break;\n        }\n\n        t += minDistance;\n        ++numSteps;\n    }\n    \n    Alpha = outAlpha;\n    #endif\n}<\/code><\/pre>\n\n\n\n<p>All that&#8217;s missing from this code is the&nbsp;<strong><em>GetDistanceSphere<\/em><\/strong>&nbsp;function that we&#8217;re about to write. By the way, I know that spheres aren&#8217;t the same as Metaballs. We&#8217;ll get to that. The other functions <strong><em>GetCurrentViewPosition<\/em><\/strong> and&nbsp;<strong><em>SafeNormalize<\/em><\/strong>, come from URP&#8217;s built-in shaders. How did I know about them? I didn&#8217;t. I dug through URPs shaders and found them. Being able to explore URPs code is invaluable. Later, when we start adding PBR support to the Metaballs, I&#8217;ll show you how I mine for diamonds in the URP code so that you can do it as well. The gist of the <strong><em>SphereTraceMetaballs <\/em><\/strong>function is: check each pixel to see if it contains a sphere&#8217;s surface.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">GetDistanceSphere function<\/h3>\n\n\n\n<p>When I first wrote this shader, I didn&#8217;t start with a metaball function. I wanted to validate that the Sphere-Tracer was working before worrying about how Metaballs work. I&#8217;m telling you so you know that you could replace the&nbsp;<strong><em>GetDistanceSphere<\/em><\/strong>&nbsp;function with any SDF function. To test, I started with a simple SDF Sphere:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>float GetDistanceSphere(float3 p, float3 center, float radius)\n{\n    return length(p - center) - radius;\n}<\/code><\/pre>\n\n\n\n<p>This function will return the distance from the point&nbsp;<strong>p<\/strong>&nbsp;and the surface of a sphere with a given&nbsp;<strong>center<\/strong>&nbsp;and&nbsp;<strong>radius<\/strong>. A Metaball is different because it also has a density. The calculation is a bit more complicated, but they still ultimately return a distance from a point. Like with the Sphere-Tracer, I&#8217;ll share the explanation and source from where I learned about them, rather than re-explaining it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Putting it together<\/h2>\n\n\n\n<p>Let&#8217;s take a break and put together what we currently have:&nbsp;<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Create a Lit Shader Graph.<\/li><li>Set the Surface type to Transparent.<\/li><li>Add a Custom Function node:<ul><li>Connect it to the Metaball.hlsl include file.<\/li><li>Add the name&nbsp;<strong><em>SphereTraceMetaballs<\/em><\/strong>.<\/li><li>Add a Vector3 input called World Position.<\/li><li>Add a Float output called Alpha.<\/li><\/ul><\/li><li>Connect a world position node to the input.<\/li><li>Connect the Alpha output to the Fragment Alpha slot.<\/li><li>Give it an emission color (we don&#8217;t have any lighting yet).<\/li><li>Set the albedo to black (so lighting calculations don&#8217;t interfere).<\/li><li>Create a material from this Shader Graph.<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"885\" height=\"425\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-1.png\" alt=\"\" class=\"wp-image-161\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-1.png 885w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-1-300x144.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-1-768x369.png 768w\" sizes=\"auto, (max-width: 885px) 100vw, 885px\" \/><\/figure>\n\n\n\n<p>Now in the scene:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Create a Sphere game object<\/li><li>Attach our material to the Sphere.<\/li><li>Set the Sphere to position (0, 0, 0) because we hard-coded that as the center of our SDF sphere.<\/li><\/ul>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"430\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2-1024x430.png\" alt=\"\" class=\"wp-image-162\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2-1024x430.png 1024w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2-300x126.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2-768x323.png 768w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2-1536x645.png 1536w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/02\/image-2.png 1917w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Now you should see an unlit sphere in your scene. The sphere mesh acts as a kind of mask through which we can see our Sphere-Traced objects, so make sure it&#8217;s in the right place.<\/p>\n\n\n\n<p>We made it to the end of part 1! That&#8217;s a lot of work to render a simple unlit sphere, but it&#8217;ll all be worth it once we reach the end of the series.<\/p>\n\n\n\n<p><strong>You can find the completed part 1 of the series&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/UnityURPMetaballParticlesPart1\"><strong>here on Github<\/strong><\/a><strong>. As promised,&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/www.scratchapixel.com\/lessons\/advanced-rendering\/rendering-distance-fields\/introduction\"><strong>here&#8217;s<\/strong><\/a><strong>&nbsp;a link to the series where I learned the details of Sphere-Tracing and Metaballs. Sign up to my mailing list <a href=\"https:\/\/bronsonzgeb.com\/index.php\/join-my-mailing-list\/\" data-type=\"page\" data-id=\"105\">here <\/a>to be notified when the next part of the series comes out.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>This series will explain how to draw Metaballs in Unity using the Universal Render Pipeline (URP) and Shader Graph. But that&#8217;s not all! It&#8217;ll also explain how to use a Particle System to control the size and position of our Metaballs. I wanted to fit everything into a single article, but unfortunately, it was too [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":173,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[17,1],"tags":[15,16,14,5,13,12],"class_list":["post-133","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-graphics","category-unity-programming","tag-metaball","tag-sdf","tag-shader-graph","tag-unity","tag-universal-render-pipeline","tag-urp"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/133","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=133"}],"version-history":[{"count":23,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/133\/revisions"}],"predecessor-version":[{"id":212,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/133\/revisions\/212"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/173"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}