<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Programming on Bug Band</title><link>https://bug.band/tags/programming/</link><description>Recent content in Programming on Bug Band</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>fiona@bug.band (fiona &amp; friends)</managingEditor><webMaster>fiona@bug.band (fiona &amp; friends)</webMaster><lastBuildDate>Thu, 29 Oct 2026 19:48:00 +0000</lastBuildDate><atom:link href="https://bug.band/tags/programming/index.xml" rel="self" type="application/rss+xml"/><item><title>how to prevent water from appearing inside a boat's hull</title><link>https://bug.band/posts/2026-10-29-how-to-prevent-water-from-appearing-inside-a-boat-s-hull/</link><pubDate>Thu, 29 Oct 2026 19:48:00 +0000</pubDate><author>fiona@bug.band (fiona &amp; friends)</author><guid>https://bug.band/posts/2026-10-29-how-to-prevent-water-from-appearing-inside-a-boat-s-hull/</guid><description>&lt;p&gt;have you ever tried to put a boating mechanic in your Unity game, but upon placing the boat in the water, the water appeared inside the boat?&lt;/p&gt;&#10;&lt;p&gt;&lt;img src="https://bug.band/img/Screenshot%202026-10-29%20at%208.01.38%20PM.png" alt="a bug stands in a leaf boat filled with water" title="this boat does not inspire confidence"&gt;&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;this boat does not inspire confidence &amp;ndash; and not just because it&amp;rsquo;s a leaf&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;if so, you may be interested to know that there&amp;rsquo;s a simple solution to this using shaders &amp;ndash; specifically, leveraging the &amp;ldquo;depth buffer&amp;rdquo; and &amp;ldquo;render queues&amp;rdquo; in a clever way.&lt;/p&gt;</description><content:encoded><![CDATA[<p>have you ever tried to put a boating mechanic in your Unity game, but upon placing the boat in the water, the water appeared inside the boat?</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.01.38%20PM.png" alt="a bug stands in a leaf boat filled with water" title="this boat does not inspire confidence"></p>
<p><em>this boat does not inspire confidence &ndash; and not just because it&rsquo;s a leaf</em></p>
<p>if so, you may be interested to know that there&rsquo;s a simple solution to this using shaders &ndash; specifically, leveraging the &ldquo;depth buffer&rdquo; and &ldquo;render queues&rdquo; in a clever way.</p>
<p><strong><em>please note:</em></strong> I&rsquo;m no shader expert, but I&rsquo;ve just enjoyed learning about shaders through the development of Bug Band. this blog post mostly expounds on the idea originally outlined in <a href="https://web.archive.org/web/20210831213650/http://wiki.unity3d.com:80/index.php/DepthMask">a 2012 post</a> on a Unity community forum (now hosted only on the Wayback Machine, all hail). shout out to the authors Neil Carter and Daniel Brauer, wherever they are now! and shout out also to <a href="https://www.youtube.com/watch?v=z2uFaBoYhaY">Philip Herlitz&rsquo;s video on this very topic</a>, which is how I found that 2012 post, and learned a few more tips and tricks. (I&rsquo;ll be sure to shout them out again as their takeaways come up)</p>
<h2 id="important-shader-terms">important shader terms</h2>
<h4 id="depth-buffer">depth buffer</h4>
<p>the GPU leverages the <a href="https://web.archive.org/web/20210827221624/https://en.wikipedia.org/wiki/Z-buffering">depth buffer</a> (also called &ldquo;z buffer&rdquo;) to optimize drawing objects: if one pixel on one object is behind another pixel on another object, there&rsquo;s no point in drawing the first pixel. we would say in that case the second object &ldquo;occludes&rdquo; the first.</p>
<p>the depth buffer is typically written to in an early in each object&rsquo;s shader, with built-in Unity <a href="https://docs.unity.cn/Documentation/Manual/SL-Reference.html">ShaderLab</a> feature <code>ZWrite On</code>.</p>
<h4 id="render-queues">render queues</h4>
<p>the GPU is able to run shader code largely in parallel, but needs to render for example opaque objects before transparent objects.</p>
<p>the object rendering order is largely controlled with the <a href="https://web.archive.org/web/20200614235301/https://docs.unity3d.com/Manual/SL-SubShaderTags.html"><code>Queue</code> tag in Unity ShaderLab</a>, and the predefined values include <code>&quot;Geometry&quot;</code> (equivalent to 2000; opaque geometry uses this queue), and <code>&quot;Transparent&quot;</code> (equivalent to 3000; reserved for anything alpha-blended).</p>
<p>we can also queue objects to be rendered <em>between</em> all objects rendered in <code>&quot;Geometry&quot;</code> and <code>&quot;Transparent&quot;</code> by assigning any render queue between the two, for example <code>&quot;Geometry+10&quot;</code> (equivalent to 2010).</p>
<h2 id="the-plan">the plan</h2>
<p>the plan is to use the above shader features to prevent the GPU from drawing the water in the boat at all.</p>
<p>we can do this by creating a &ldquo;depth mask object.&rdquo;</p>
<h4 id="1-create-our-depth-mask-shader">1. create our depth mask shader</h4>
<p>would you believe that the ShaderLab code from the aforementioned 2012 post works exactly as is?</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Shader &#34;Masked/Mask&#34; {
</span></span><span style="display:flex;"><span>	SubShader {
</span></span><span style="display:flex;"><span>		// Render the mask after regular geometry, but before masked geometry and
</span></span><span style="display:flex;"><span>		// transparent things.
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		Tags {&#34;Queue&#34; = &#34;Geometry+10&#34; }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		// Don&#39;t draw in the RGBA channels; just the depth buffer
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		ColorMask 0
</span></span><span style="display:flex;"><span>		ZWrite On
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		// Do nothing specific in the pass:
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>		Pass {}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><p>for what it&rsquo;s worth, Bug Band is on Unity 6+ and URP, but neither Unity version nor Render Pipeline should matter. those familiar with the nuances may notice there&rsquo;s neither CG nor HLSL code to convert, because we don&rsquo;t do anything in the <code>Pass</code> at all.</p>
<blockquote>
<p>if you&rsquo;ve never created a shader in Unity before, you can create this <code>.shader</code> file in your Unity project by right-clicking in the project view and select Create &gt; Shader &gt; Unlit Shader, naming the file <code>DepthMask.shader</code> or whatever you want, opening the file in any text editor, and replace the contents with the above.</p>
</blockquote>
<h4 id="2-create-our-depth-mask-material">2. create our depth mask material</h4>
<p>for those not familiar with Unity shaders &ndash; in order to apply a shader to an object, we&rsquo;ll need to create a material, which sort of &ldquo;instances&rdquo; (to use a code-y term) the shader.</p>
<blockquote>
<p>if you&rsquo;ve never created a material before &ndash; right-click in the project view and select Create &gt; Material. all that&rsquo;s left to do is apply our shader, which you can do in the dropdown labeled &ldquo;shader&rdquo;, and navigate to Masked &gt; Mask.</p>
</blockquote>
<h4 id="3-create-our-depth-mask-object">3. create our depth mask object</h4>
<p>last but not least, create our depth mask object. to start, we can create a simple cube as a child of our boat.</p>
<blockquote>
<p>right-click our boat object in the hierarchy, select 3D Object &gt; Cube), adjust the size so the bottom of the cube overlaps with the boat&rsquo;s hull, and the top of the cube is above the water level.</p>
</blockquote>
<p><img src="/img/Screenshot%202026-10-29%20at%207.59.16%20PM.png" alt="a gray box sits inside a leaf boat" title="default cube meets leaf boat"></p>
<p><em>default cube meets leaf boat</em></p>
<p>finally, apply our depth mask material to our cube&rsquo;s mesh renderer in the inspector &ndash; and it disappears! (or does it?)</p>
<p><img src="/img/Screenshot%202026-10-29%20at%207.59.27%20PM.png" alt="an invisible box sits inside a leaf boat" title="invisible cube meets leaf boat"></p>
<p>most importantly, the water is (mostly) no longer appearing in the bottom of our boat, but there are also some new weird visual behavior wherever our cube occludes the water.</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.00.20%20PM.png" alt="a bug stands in a boat with no water in the hull, but even the water outside the boat is not drawn" title="our depth mask object is too powerful"></p>
<h2 id="whats-happening">what&rsquo;s happening?</h2>
<p>let&rsquo;s break down the shader code that we&rsquo;re using above, using what we know about shaders.</p>
<h4 id="our-depth-mask-is-rendered-after-opaque-objects">our depth mask is rendered after opaque objects</h4>
<p>notice that  <code>&quot;Queue&quot; = &quot;Geometry+10&quot;</code>  specifies that our depth mask object is rendered <em>after</em> opaque objects, which includes our boat, our player character, and also whatever is below the water.</p>
<h4 id="our-water-is-rendered-after-our-depth-mask">our water is rendered after our depth mask</h4>
<p>the unspoken assumption here is that our water is rendered with <code>&quot;Transparent&quot;</code> objects, which are rendered after all <code>&quot;Geometry&quot;</code> objects and any masks (like ours) that come after them. if the water is rendered with opaque objects, our depth mask will not have the intended effect.</p>
<p>how can we tell what our water&rsquo;s render queue is? below are a few indicators:</p>
<ul>
<li>
<p>if our water looks &ldquo;alpha-blended&rdquo; (or see-through), then it&rsquo;s certainly rendered with <code>&quot;Transparent&quot;</code> objects.</p>
</li>
<li>
<p>the material on our water in the Unity inspector should have an integer input labeled Render Queue &ndash; if that integer is 2000, it&rsquo;s <code>&quot;Geometry&quot;</code>; if it&rsquo;s 3000, it&rsquo;s <code>&quot;Transparent&quot;</code>.</p>
</li>
<li>
<p>the shader code of the water&rsquo;s material should have a <code>&quot;Queue&quot;</code> tag, or lack thereof &ndash; if it&rsquo;s specified, there&rsquo;s your answer; otherwise,<code>&quot;Geometry&quot;</code> is the default value.</p>
</li>
</ul>
<h4 id="how-to-ensure-our-water-is-rendered-after-opaque-objects">how to ensure our water is rendered after opaque objects</h4>
<p>the question remains: what do we do if our water is rendered with opaque objects in <code>&quot;Geometry&quot;</code>?</p>
<p>well since a material &ldquo;instances&rdquo; a shader, we can just modify the Render Queue in the water&rsquo;s material inspector to be just above your depth mask shader&rsquo;s &ndash; say, 2020, which is equivalent to <code>&quot;Geometry+20&quot;</code>; or we can just tag the water&rsquo;s shader to be rendered with <code>&quot;Transparent&quot;</code> objects, if that makes sense for us.</p>
<p>or, we can create and apply the script offered in the aforementioned 2012 post, where the default queue value specified in the script is 3000, and otherwise we can override the value in the script&rsquo;s inspector on the water object (I can imagine this being really helpful if you have a water prefab for many water objects):</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>/*
</span></span><span style="display:flex;"><span>	SetRenderQueue.cs
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>	Sets the RenderQueue of an object&#39;s materials on Awake. This will instance
</span></span><span style="display:flex;"><span>	the materials, so the script won&#39;t interfere with other renderers that
</span></span><span style="display:flex;"><span>	reference the same materials.
</span></span><span style="display:flex;"><span>*/
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>using UnityEngine;
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>[AddComponentMenu(&#34;Rendering/SetRenderQueue&#34;)]
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>public class SetRenderQueue : MonoBehaviour {
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>	[SerializeField]
</span></span><span style="display:flex;"><span>	protected int[] m_queues = new int[]{3000};
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>	protected void Awake() {
</span></span><span style="display:flex;"><span>		Material[] materials = renderer.materials;
</span></span><span style="display:flex;"><span>		for (int i = 0; i &lt; materials.Length &amp;&amp; i &lt; m_queues.Length; ++i) {
</span></span><span style="display:flex;"><span>			materials[i].renderQueue = m_queues[i];
</span></span><span style="display:flex;"><span>		}
</span></span><span style="display:flex;"><span>	}
</span></span><span style="display:flex;"><span>}</span></span></code></pre></div><h4 id="our-depth-mask-just-writes-to-the-depth-buffer">our depth mask just writes to the depth buffer</h4>
<p>next in the shader code are two flags, and the only function of this shader:</p>





<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-plain" data-lang="plain"><span style="display:flex;"><span>ColorMask 0
</span></span><span style="display:flex;"><span>ZWrite On</span></span></code></pre></div><p>here we skip writing to any color channels so the object appears transparent (see: <a href="https://docs.unity.com/en-us/engine/6000.0/manual/materials-and-shaders/shaders/reference/sl-reference/sl-commands/sl-color-mask"><code>ColorMask</code> reference</a>), and we’re also writing to the depth buffer (see: <a href="https://docs.unity.com/en-us/engine/6000.0/manual/materials-and-shaders/shaders/reference/sl-reference/sl-commands/sl-zwrite"><code>ZWrite</code> reference</a>).</p>
<p>in other words, we specify that the GPU should not actually draw our depth mask object over objects that have already been drawn, but we do want the GPU to know how close our object is to the camera relative to other objects &ndash; specifically, that it&rsquo;s closer to the camera than the water is.</p>
<p>this leverages the fact that the GPU will not draw pixels that are occluded by other objects.</p>
<h4 id="in-conclusion">in conclusion,</h4>
<p>the GPU draws the leaf boat and then notes that our mask is closer to the camera than the water, so it does not draw the water in the leaf boat.</p>
<h4 id="bonus-why-cant-we-render-the-depth-mask-in-geometry">bonus: why can&rsquo;t we render the depth mask in &ldquo;Geometry&rdquo;?</h4>
<p>honestly, I don&rsquo;t know! I thought <code>&quot;Geometry&quot;</code> was just an un-opinionated ordering, but I tried to render our depth mask in <code>&quot;Geometry&quot;</code> without adding 10, and Unity seemed to add some default coloring. (spoiler: by the end of this walkthrough, we&rsquo;re not using a cube anymore)</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.37.50%20PM.png" alt="a bug stands in a leaf boat with no water in the hull" title="with depth mask object rendered at &quot;Geometry+10&quot;"></p>
<p><em>with depth mask object rendered at &ldquo;Geometry+10&rdquo;</em></p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.37.58%20PM.png" alt="a bug stands in a leaf boat with a gray block in the hull" title="with depth mask object rendered at &quot;Geometry&quot;"></p>
<p><em>with depth mask object rendered at &ldquo;Geometry&rdquo;</em></p>
<p>if you are reading this and you know why the GPU draws our depth mask object even though we&rsquo;ve disabled color writes, I would love to hear it! this section is begging for an addendum credited to you. : )</p>
<h2 id="refining-our-solution">refining our solution</h2>
<p>now that we have our plan and know how it works, all that&rsquo;s left to do is create an object that fits the requirements of our depth mask &ndash; namely, it is higher than the water level, but not higher than the sides of the boat, so players don&rsquo;t see water disappear even beyond the hull of the boat wherever the depth mask may occlude the water.</p>
<p>if our boat had a separate mesh for the floor, we could just duplicate the floor, re-position it to be above the water but still within the boat&rsquo;s hull, and use that as our depth mask object.</p>
<p>our leaf boat is a solid mesh though, so we will turn to everyone&rsquo;s favorite open source 3D modeling software: Blender.</p>
<h4 id="using-blender-to-create-our-depth-mask-object">using Blender to create our depth mask object</h4>
<p>I want to give another shout out to the aforementioned video by Philip Herlitz on this topic, because it walks through the exact strategy for doing what I describe below.</p>
<p>let&rsquo;s import out boat model into Blender and remove any unnecessary meshes.</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.41.25%20PM.png" alt="boat mesh imported into blender" title="boat mesh imported into blender"></p>
<p>select our boat&rsquo;s mesh in the hierarchy, and Tab into Edit Mode. since the top of our boat is irregular (the ends of our boat are higher than the sides of our boat), we need to delete vertices that come above the sides of your boat before moving on.</p>
<blockquote>
<p>enable X-ray mode, lock an axis so we look at the boat from one side, and then select all the vertices we want to delete with the drag select tool.</p>
</blockquote>
<p><img src="/img/Screenshot%202026-10-29%20at%208.43.57%20PM.png" alt="with axis locked, drag tool selects the vertices above the sides of the boat" title="select vertices higher than the sides of your boat"></p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.44.09%20PM.png" alt="with axis locked, vertices higher than the sides of the boat are selected and highlighted" title="delete those selected vertices"></p>
<p>next, select all vertices in the mesh by pressing A, and then in the Mesh menu, select &ldquo;Convex Hull&rdquo;.
the result is a version of our boat that looks like someone stuffed it with clay.</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.48.43%20PM.png" alt="remainder of boat hull is selected and highlighted, and &ldquo;Mesh&rdquo; menu is expanded, which includes &ldquo;Convex Hull&rdquo; option" title="select all vertices, and navigate to Mesh &gt; Convex Hull"></p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.45.45%20PM.png" alt="the remainder of the boat hull is made convex" title="the hull is officially convex"></p>
<p><em>behold: the platonic form of a leaf boat&rsquo;s depth mask object</em></p>
<h2 id="the-final-product">the final product</h2>
<p>finally, with our refined depth mask object pulled into our Unity project, we can make that a child of our boat object, and apply our depth mask material to the depth mask object. at long last: a boat as dry as bone.</p>
<p><img src="/img/Screenshot%202026-10-29%20at%208.51.46%20PM.png" alt="a bug stands in a leaf boat with not a trace of water in the hull" title="a boat as dry as bone"></p>
]]></content:encoded></item></channel></rss>