<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2026-03-28T06:52:17+00:00</updated><id>/feed.xml</id><title type="html">GiantBoar</title><subtitle>a website of sorts</subtitle><entry><title type="html">Idk</title><link href="/blog/2026/idk" rel="alternate" type="text/html" title="Idk" /><published>2026-03-27T00:00:00+00:00</published><updated>2026-03-27T00:00:00+00:00</updated><id>/blog/2026/idk</id><content type="html" xml:base="/blog/2026/idk"><![CDATA[<p>sometimes I get lonely and I’m afraid even when surrounded by people I love its how I will always feel</p>]]></content><author><name>some guy</name></author><summary type="html"><![CDATA[just a test post idk what you want from me]]></summary></entry><entry><title type="html">Testpost</title><link href="/blog/2026/testpost" rel="alternate" type="text/html" title="Testpost" /><published>2026-03-26T00:00:00+00:00</published><updated>2026-03-26T00:00:00+00:00</updated><id>/blog/2026/testpost</id><content type="html" xml:base="/blog/2026/testpost"><![CDATA[<p>sometimes I get lonely and I’m afraid even when surrounded by people I love its how I will always feel</p>]]></content><author><name>some guy</name></author><summary type="html"><![CDATA[just a test post idk what you want from me]]></summary></entry><entry><title type="html">Variable Input Device System</title><link href="/blog/2025/Input-Manager" rel="alternate" type="text/html" title="Variable Input Device System" /><published>2025-11-08T23:15:30+00:00</published><updated>2025-11-08T23:15:30+00:00</updated><id>/blog/2025/Input-Manager</id><content type="html" xml:base="/blog/2025/Input-Manager"><![CDATA[<h1 id="input-device-manager">Input Device Manager</h1>
<p>Recently, a friend of mine posed an idea for a game they’re developing for a local game dev showcase: a platform fighter where each selectable character is associated with a unique controller. For this project, I needed to create a system that could handle inputs from multiple unique controllers to play as different characters, including character selection and pairing input devices to controller types.</p>

<p>The controllers used in the project are N64, GameCube, WiiMote and DK Bongos. These devices posed a notable issue, since three of the four use the same hardware to interface with the PC, meaning they are read as the same controller <em>type</em>. Since we couldn’t tell the difference between individual pieces of hardware, creative solutions had to be made to associate controllers with their type.</p>

<h2 id="gameplay">Gameplay</h2>
<p>As a platform fighter, the fundamental gameplay revolves around <strong>choosing a character</strong> using their associated input device, having your opponent do the same, and then using a range of <strong>attacks and special moves</strong> to damage your opponent. The gameplay is inspired by games like Super Smash Brothers, so the health is a percentage that increases with each hit, ranging from 0% (undamaged) up, with the amount of knock back the enemy takes exponentially growing along side their health. When a player is finally knocked out of bounds, they lose a stock, and when they have no stocks left, they have lost the fight.</p>

<p>When developing this system, I had to consider its purpose in the game, specifically that not all devices will control a character in a fight (with only one-on-one battles planned for release), and that the <em>type</em> of device determined the character’s sprites and moves.</p>

<h2 id="overview">Overview</h2>
<p>This system comes in three parts, the <strong>InputMapHandler</strong>, <strong>CharacterControllers</strong>, and <strong>PlayableCharacters</strong>. Each of these parts controls a different aspect of binding unique controllers to characters.
Because part of this system was developed to be independent to the game itself, specifically the InputMapHandler, <strong>generic types</strong> were used to have it fit into projects outside of this specific game.</p>

<h3 id="input-map-handler">Input Map Handler</h3>
<p>The <strong>Input Map Handler</strong> is the beating heart of this system, it creates input maps from Unity’s InputSystem for each device connected to the user’s PC, from keyboards to controllers; the system pairs created input maps with each device, setting it as the only input device read by that map. Using this, individual devices can be enabled, disabled, or read from, allowing for a range of different devices to be plugged in and read from at will.</p>

<p>The <strong>Input Map Handler</strong> is generically typed, allowing a system to create one with any type created by an input map asset. In Unity’s Input System, assets can generate a C# class so input maps can better interface with C# code. The class generated by the asset will always inherit from <code class="language-plaintext highlighter-rouge">IInputActionCollection2</code>, which we can use in our generic type class to specify that the type used in arguments should be generated from one of these assets.
// insert declaration of InputMapHandler</p>

<h3 id="character-controller">Character Controller</h3>

<h3 id="playable-character">Playable Character</h3>]]></content><author><name></name></author><category term="Programming" /><category term="Unity" /><category term="C#" /><summary type="html"><![CDATA[Working on a friend's project to create a system that supports mapping various unique input devices to characters in a smash-style fighter.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/postassets/InputManager/devices-image.png" /><media:content medium="image" url="/assets/postassets/InputManager/devices-image.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Oslo RPG Starting Development</title><link href="/blog/2025/Oslo-FirstPost" rel="alternate" type="text/html" title="Oslo RPG Starting Development" /><published>2025-06-24T23:11:36+00:00</published><updated>2025-06-24T23:11:36+00:00</updated><id>/blog/2025/Oslo-FirstPost</id><content type="html" xml:base="/blog/2025/Oslo-FirstPost"><![CDATA[<h1 id="oslo">Oslo</h1>
<p><i class="subtitle">A new game project RPG inspired by Fear and Hunger (mechanically), Pathologic, as well as Dark Souls and similar Dark Fantasy games</i></p>

<p>Oslo (working title) is a game I’ve relatively recently started work on to expand my knowledge of GDScript as well as just make a playable project by myself. Since RPGs are an overwhelmingly common indie game genre I’ve been trying to fill in some of the gaps caused by other games adherence to the traditional genre conventions, as well as add some unique mechanics (to the detriment of my development speed).</p>

<p><img src="../assets/postassets/OsloFirstPost/oslo-forest-2.png" alt="Oslo Development Screenshot" /></p>

<p>This game will be developed in Godot, an engine thats fairly new to me but one that I have a boundless amount of personal control over, being able to edit the code for the engine as its open source.</p>

<p>When making anything in an existing genre you have to appreciate the titles that came before it, but I think so many games (specifically in the 2D turn-based RPG subgenre) think that the games that came before are teplates to be replicated and barely iterated on, especially mechanically. Some games make minor adjustments to the formula, such as the enemy moods in <a href="https://store.steampowered.com/app/1150690/OMORI/">Omori</a>, but some of the best indie darlings are ones that really shake things up, such as <a href="https://store.steampowered.com/app/391540/Undertale/">Undertale</a>’s talk and pacify system.</p>

<h2 id="plot">Plot</h2>

<p>You are Elias, a warrior from a small town sent in holy pilgrimage to Oslo (a heavily fictional take on the real town of Oslo, Norway) in search of ascention to ruler. You are an emissary for <strong>The Moon</strong>, an otherworldly entity presiding over obscurity and knowledge, to which your small hometown has become loyal to. During the modern era, all external deities are suffering as their strength wanes, causing a deadly disease to spread through your town called <strong>Moon Blight</strong>. Emissaries of an array of major entities have also come to Oslo in what is almost a tournament to give the throne of the world to their gods, settling the battle for power with only a single external entity ruling. Elias’ pilgrimage through Oslo will have him facing agains the many forces on the same journey as him, growing a party of travellers to accomplish your goals.</p>

<h2 id="mechanics">Mechanics</h2>

<p>While all things in development are ultimately subject to change in the lengthy process, there are a handful of core mechanics I have in mind going forward.</p>

<h3 id="exploration">Exploration</h3>

<p>My goal with the world of Oslo is to make a single contained map with a sequence of areas (e.g. town districts, a forest, encampments outside the town) that the player can learn intimately during their playtime. I really enjoy playing games that rely on the player learning their environment, such as the <a href="https://store.steampowered.com/sale/DarkSoulsFranchise">Dark Souls</a> series, or the russian RPG <a href="https://store.steampowered.com/app/384110/Pathologic_Classic_HD/">Pathologic</a>. Both of these games rely on traversing areas, sometimes multiple times, and getting a deeper connection with the world these areas exist in. A huge point of inspiration is the art of having the player return to older areas that are now changed by the events of the game, such as the spread of infection in <a href="https://store.steampowered.com/app/367520/Hollow_Knight/">Hollow Knight</a>.</p>

<div style="display:flex">
    <img src="../assets/postassets/OsloFirstPost/hk-forgotten-crossroads.png" title="A lighthouse rendered using the dithering effect" alt="A low-poly 3D lighthouse rendered with a ps1 dithering filter" height="20%" />
    <img src="../assets/postassets/OsloFirstPost/hk-infected-crossroads.png" title="A snowy city scene rendered with dithering" alt="A low-poly 3D snowy city rendered with a ps1 dithering filter" height="20%" />
</div>

<p>I want the world of my game to <strong>change and decay</strong> as the story progresses, breathing new life into old areas and adding a ludonarrative harmony between the way the world is changing in the story and the way the environment is changing around the player.</p>

<h3 id="turn-based-combat">Turn Based Combat</h3>

<p>Combat is one of the most active gameplay features in an RPG, and traditional turn-based combat can become dragged down by its simplicity, which is why most games of the genre try to add some unique element to it. I want to take this in a completely crazy direction by taking inspiration from a turn based fighting game called <a href="https://store.steampowered.com/app/2212330/Your_Only_Move_Is_HUSTLE/">YOMI Hustle</a>.</p>

<p><img src="../assets/postassets/OsloFirstPost/yomihustle-combat.gif" alt="Yomi Hustle Combat Gif" /></p>
<div style="text-align: center;">
<i class="subtitle">Image source <a href="https://steamdb.info/patchnotes/12485106/">SteamDB</a></i>
</div>

<p>You will choose each character’s moves in a <strong>2D top-down arena</strong>, which each have their own duration; selected moves will play out in real-time after you’ve selected what each character is to do. The goal of this becomes predicting your enemies moves and acting accordingly, similar to any real-time fighting mechanic, however, with the added feature of being able to carefully plot out and consider your moves in a turn-based system.
You can benefit by choosing shorter moves that end quickly, hoping to catch the enemy mid-way through a move they can’t just cancel out of. This way you have an assured hit, dodge, or generally a definitive knowledge of what the enemy is about to do. The predicted moves for the enemies will be simulated, as will the currently chosen moves for the characters, so you can see the next ‘stage’ of the battle play out before you decide on your actions.</p>

<h3 id="skills-and-world-interactions">Skills and World Interactions</h3>

<p>I want the player’s skills to affect their progression in dialogue as well as their interactions with the world, the same way that those skills would affect a real person. This feature is heavily inspired by <a href="https://store.steampowered.com/app/632470/Disco_Elysium__The_Final_Cut/">Disco Elysium</a>, which is a major mechanical and narrative inspiration for this project.</p>

<p>Dialogue options will be locked off or available depending on certain skills, as well as the level of detail you are able to interact with or experience the environment. This will help add depth to each of the player’s skills beyond simple combat interaction and give them a deeper connection with the world of the game too.</p>]]></content><author><name></name></author><category term="Devlog" /><category term="Oslo" /><category term="Oslo" /><category term="Pixelart" /><category term="Godot" /><summary type="html"><![CDATA[Starting development on a new RPG in Godot with the working title Oslo]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/postassets/OsloFirstPost/oslo-forest-1.png" /><media:content medium="image" url="/assets/postassets/OsloFirstPost/oslo-forest-1.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Dithered Rendering</title><link href="/blog/2025/Dithered-Rendering" rel="alternate" type="text/html" title="Dithered Rendering" /><published>2025-06-24T04:28:00+00:00</published><updated>2025-06-24T04:28:00+00:00</updated><id>/blog/2025/Dithered-Rendering</id><content type="html" xml:base="/blog/2025/Dithered-Rendering"><![CDATA[<p><em>this post was initiallly written in 2024, but has been reworked</em></p>

<h2 id="summary">Summary</h2>

<p>Visually standing out in the modern era of indie games is almost impossible with the sheer creativity many people are now striving towards when it comes to unconventional graphics, one growing field being the realm of PSX-style games, stylised after the PS1-PS2 era of video game consoles.
An interesting way of achieving this effect visually is through the use of <strong>dithered rendering</strong>, a technique that crunches down the number of colours displayed on-screen while preserving the shading and depth. This tutorial goes over the basics of rendering using dithering, specifically using the bayer matrix. (as well as how to use shadergraph and a custom render feature in unity’s URP)</p>

<div style="display:flex">
    <img src="../assets/postassets/DitheredRendering/lighthouse-screenshot.png" title="A lighthouse rendered using the dithering effect" alt="A low-poly 3D lighthouse rendered with a ps1 dithering filter" height="20%" />
    <img src="../assets/postassets/DitheredRendering/city-screenshot.png" title="A snowy city scene rendered with dithering" alt="A low-poly 3D snowy city rendered with a ps1 dithering filter" height="20%" />
</div>

<p><em>these are two scenes from a very work in progress game, currently called Twin Angels</em></p>

<hr />

<h1 id="dithered-rendering-in-urp">Dithered Rendering in URP</h1>
<h2 id="intro">Intro</h2>

<p>Some time in early 2022 I got really into visual shaders for games, particularly <a href="https://store.steampowered.com/app/653530/Return_of_the_Obra_Dinn/"><strong>Return of the Obra Dinn</strong></a>’s one-bit dithering technique, and tried recreating it for some smaller projects.</p>

<p><img src="../assets/postassets/DitheredRendering/obra-dinn.jpeg" alt="Return of the Obra Dinn dithering example" /></p>

<div style="text-align: center;">
    [Image Source <a href="https://www.engadget.com/2019-10-04-return-obra-dinn-consoles-october-18-release.html?guccounter=1&amp;guce_referrer=aHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS8&amp;guce_referrer_sig=AQAAANNYDDcLgwJbxMAxXu1k2TlPO-sKIrzUoqd991-1Q2jY0nvSmDAPyN3b3Uh_flUQ98yHGlyhnPe_3Xd2Q-TWW1q_oZLAh1RGOv2RoJ93gh28dHk5gbZbr_Sak9Lp6vze1VAEXslOGcEcg6Xl-xXtr_okITBRMYxOd9o5DFCkUkSe">Engadget</a>]
</div>
<p><br /></p>

<p>Flash forward to a few years later and I’ve continued to work on this dithering shader and found it really interesting and useful, <em>particularly</em> for a PSX style game.
Most of what this post covers can be applied in a variety of different ways depending on what you specifically want to do, and can even be adapted for use as a surface shader.</p>

<p><em>if you’re interested in learning more about how Obra Dinn achieves its visuals <a href="https://www.youtube.com/watch?v=Ap4fXGTOb7I" target="_none">here’s a really useful resource</a></em></p>

<h2 id="the-basics">The Basics</h2>

<p>Before we dig into things its probably helpful to have at least a basic understanding of Unity’s <strong>Universal Render Pipeline</strong> and <strong>Shadergraph</strong>, particularly the latter as you’ll need to understand how to make a graph and the basics behind creating and connecting nodes (<em>there are plenty of youtube tutorials on the subject if you’re uninitiated</em>).</p>

<h3 id="dithering">Dithering</h3>

<p><img src="../assets/postassets/DitheredRendering/dithering-example.png" alt="Wikipedia dithering example" width="200" class="right" /></p>

<p><a href="https://en.wikipedia.org/wiki/Dither" target="_none">Dithering</a> is an old process of smoothly transitioning between two values in an image, essentially ‘faking’ more data than really exists. Its primary function was to avoid colour banding in compressed images (the big ugly flat surfaces of colour in low-quality pictures) by faking a smooth gradient using clusters of noisy pixels.</p>

<p>It’s a pretty simple effect, but by chequerboarding pixels of two different colours together, at a distance (or rendered as tiny pixels on a screen) they blend into one mid-point colour. Using this, all you do is reduce or increase the density of this chequerboard pattern depending on how far between the two colours a point is.</p>

<p>Using the <strong>Bayer Matrix</strong> dithering texture we can easily dither between two values. Using this ‘edge’ value sliding up and down in the example we can see which pixels in the matrix are darker than this value, which will be black, and which are lighter, which will be white. In graphics programming this is called a <code class="language-plaintext highlighter-rouge">step(edge, value)</code> function, which takes an edge and a value, returning 1 if the value is greater than the edge, and 0 if the value is lower.</p>

<p><img src="../assets/postassets/DitheredRendering/bayer-example.gif" alt="Bayer dithering example gif" width="200px" /></p>

<p>We can use the result of this (the little dither square on the right) to interpolate between two values. The easiest example of this is a one-bit shader that interpolates between a background colour and a highlight colour depending on the brightness of each pixel. The basic idea is that this dithering texture is tiled across the entire screen, and then for every pixel in the rendered image we check if the luminocity is above or below that same pixel in the tiled bayer matrix texture, returning either black or white.</p>

<blockquote class="prompt-tip">
  <p>The luminosity of a pixel <code class="language-plaintext highlighter-rouge">p</code> is calculated in a few different ways depending on your specific purpose, but I went with <code class="language-plaintext highlighter-rouge">(0.2126 * p.r) + (0.7152 * p.g) + (0.0722 * p.b)</code>. The reason the three colours are weighted differently is because of how the human eyeball percieves colour, with a weaker detection of red and a stronger detection of green and blue.</p>
</blockquote>

<p>Here I have a shot of the lighthouse rendered normally, no dithering, against one rendered using a one-bit dithering filter:</p>

<div style="display:flex">
    <img src="../assets/postassets/DitheredRendering/multiple-bit-lighthouse.png" title="The Lighthouse rendered normally" alt="The Lighthouse rendered normally" height="20%" />
    <img src="../assets/postassets/DitheredRendering/one-bit-lighthouse.png" title="The Lighthouse rendered with a one-bit filter" alt="The Lighthouse rendered with a one-bit filter" height="20%" />
</div>

<p>(these can look weird on some screens, click to enhance the one-bit picture to see it better)</p>

<p>This effect has been popularised by games like <a href="https://store.steampowered.com/app/913740/WORLD_OF_HORROR/">World of Horror</a> by Paweł Koźmiński and <a href="https://store.steampowered.com/app/1697700/Whos_Lila/">Who’s Lila</a> by Garage Heathen (one of my favourite games ever), although both these games also include hand-drawn assets or additional details like edge-detection to enhance the visual appeal of the shader.</p>

<p>now that you get the basics behind dithering we can move onto the render feature.</p>

<h3 id="custom-render-feature">Custom Render Feature</h3>

<p>In URP in order to add our own shader to the render pipeline we need to create a <strong>custom render feature</strong>, an additional step to the rendering process we can program ourselves. You can refer to the github<sup id="fnref:gitfooter" role="doc-noteref"><a href="#fn:gitfooter" class="footnote" rel="footnote">1</a></sup> files to see the basic structure for a custom render pass and custom render feature, which I will briefly explain here.</p>

<blockquote class="prompt-danger">
  <p>This is a <strong>complicated subject</strong>, and my work could definitely be optimised. However, it functions well, and is at least the best baseline way to introduce yourself to the render pipeline and custom render features.</p>
</blockquote>

<p>I’m definitely not an expert in URP or render features, but I do understand the basics of it. Our custom little render process comes in <strong>two</strong> major parts:</p>
<ol>
  <li>the render <strong>pass</strong></li>
  <li>the render <strong>feature</strong></li>
</ol>

<p>You can skip right to setting things up if you’re following as a tutorial and don’t care about what these two scripts actually do, but I think its interesting.</p>

<h4 id="render-pass">Render Pass</h4>

<p>The render pass is the little bit of code handled by the URP that tells it what to render at this stage in the pipeline. For our purposes (squashing the resolution down and then applying our custom filter) this is done in <strong>two</strong> Blit<sup id="fnref:blitfooter" role="doc-noteref"><a href="#fn:blitfooter" class="footnote" rel="footnote">2</a></sup> passes:</p>
<ol>
  <li>the first renders the <strong>screen</strong> to a <strong>render texture</strong>, squashing the screen down to our desired resolution.</li>
  <li>the second renders from that <strong>render texture</strong> to the screen again, passing it through our custom material, and adding the dithering effect.
If these steps were performed out of order, the GPU would render a complicated shader across an entire, potentially 4k, screen size, and then crunch it down to our desired resolution. By squashing our image down first, we save rendering time for more complicated shaders, and maintain artistic vision by knowing exactly what the image will look like passing through the filter and onto the screen.</li>
</ol>

<p>The <code class="language-plaintext highlighter-rouge">DitherPass</code> class inherits from <code class="language-plaintext highlighter-rouge">ScriptableRenderPass</code>, and we’ll use the override function <code class="language-plaintext highlighter-rouge">OnCameraSetup(CommandBuffer cmd, ref RenderingData renderingData)</code> to retrieve the <code class="language-plaintext highlighter-rouge">RenderTargetIdentifier</code> for the colorBuffer, which is the identifier for the render texture that the game will display on the players screen, and is what the scene is originally rendered to, and then use the override function <code class="language-plaintext highlighter-rouge">Execute(ScriptableRenderContext context, ref RenderingData renderingData)</code> to add our two blits, one from the colorBuffer to our pixelBuffer (the custom render texture with our squashed down size, that we will give to the render pass using our render feature), and the other from our pixelBuffer <strong>BACK</strong> to our colourBuffer, passing it through the dithering material which we will make in a moment.</p>

<p>If that was a little confusing it can be summarised as</p>
<ul>
  <li>The screen is drawn to the colorBuffer by the game</li>
  <li>colorBuffer copied across and squashed down into our custom render texture (the pixel buffer)</li>
  <li>this squashed down image is then <em>redrawn</em> to the screen using our material, which applies the dithering filter</li>
</ul>

<p><em>remember all these files are accessible on the github</em><sup id="fnref:gitfooter:1" role="doc-noteref"><a href="#fn:gitfooter" class="footnote" rel="footnote">1</a></sup></p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">UnityEngine.Rendering</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">UnityEngine.Rendering.Universal</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">DitherPass</span> <span class="p">:</span> <span class="n">ScriptableRenderPass</span>
<span class="p">{</span>
    <span class="c1">// the settings made in the RenderFeature</span>
    <span class="k">private</span> <span class="n">DitherPassFeature</span><span class="p">.</span><span class="n">DitherSettings</span> <span class="n">settings</span><span class="p">;</span>

    <span class="c1">// the two render textures that will be written to and from</span>
    <span class="k">private</span> <span class="n">RenderTargetIdentifier</span> <span class="n">colorBuffer</span><span class="p">,</span> <span class="n">pixelBuffer</span><span class="p">;</span>

    <span class="c1">// construct the render </span>
    <span class="k">public</span> <span class="nf">DitherPass</span><span class="p">(</span><span class="n">DitherPassFeature</span><span class="p">.</span><span class="n">DitherSettings</span> <span class="n">settings</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">this</span><span class="p">.</span><span class="n">settings</span> <span class="p">=</span> <span class="n">settings</span><span class="p">;</span>
        <span class="n">renderPassEvent</span> <span class="p">=</span> <span class="n">settings</span><span class="p">.</span><span class="n">renderPassEvent</span><span class="p">;</span>

        <span class="n">pixelBuffer</span> <span class="p">=</span> <span class="n">settings</span><span class="p">.</span><span class="n">renderTex</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// retrieve the colorBuffer </span>
    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">OnCameraSetup</span><span class="p">(</span><span class="n">CommandBuffer</span> <span class="n">cmd</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">colorBuffer</span> <span class="p">=</span> <span class="n">renderingData</span><span class="p">.</span><span class="n">cameraData</span><span class="p">.</span><span class="n">renderer</span><span class="p">.</span><span class="n">cameraColorTargetHandle</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// execute the two blits and apply the material</span>
    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Execute</span><span class="p">(</span><span class="n">ScriptableRenderContext</span> <span class="n">context</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// get a command buffer</span>
        <span class="n">CommandBuffer</span> <span class="n">cmd</span> <span class="p">=</span> <span class="n">CommandBufferPool</span><span class="p">.</span><span class="nf">Get</span><span class="p">();</span>

        <span class="c1">// queue render from colour buffer to our pixel buffer</span>
        <span class="n">cmd</span><span class="p">.</span><span class="nf">Blit</span><span class="p">(</span><span class="n">colorBuffer</span><span class="p">,</span> <span class="n">pixelBuffer</span><span class="p">);</span>
        <span class="c1">// queue render back from the pixel buffer to the screen, using our material</span>
        <span class="n">cmd</span><span class="p">.</span><span class="nf">Blit</span><span class="p">(</span><span class="n">pixelBuffer</span><span class="p">,</span> <span class="n">colorBuffer</span><span class="p">,</span> <span class="n">settings</span><span class="p">.</span><span class="n">ditherMaterial</span><span class="p">);</span>

        <span class="n">context</span><span class="p">.</span><span class="nf">ExecuteCommandBuffer</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
        <span class="n">CommandBufferPool</span><span class="p">.</span><span class="nf">Release</span><span class="p">(</span><span class="n">cmd</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<h4 id="render-feature">Render Feature</h4>

<p>The <strong>render feature</strong> is the part of the code that sets up our render pass, and handles both actually adding the render pass to the pipeline and its settings.</p>

<p>The <code class="language-plaintext highlighter-rouge">DitherRenderFeature</code> class inherets from <code class="language-plaintext highlighter-rouge">ScriptableRenderFeature</code> which lets us override two important functions: <code class="language-plaintext highlighter-rouge">Create()</code> and <code class="language-plaintext highlighter-rouge">AddRenderPasses(ScriptableRenderer renderer, ref RenderingData renderingData)</code>.</p>

<p>We will use a struct called <code class="language-plaintext highlighter-rouge">DitherSettings</code> to hold all of the settings and necessary data for our render, including the <code class="language-plaintext highlighter-rouge">RenderPassEvent</code>, which tells the pipeline what stage this render occurs (e.g. after rendering transparent objects, before post processing, etc.), the <strong>material</strong> we will assign our shadergraph shader to, and the <strong>RenderTexture</strong> in the project files that has your desired resolution. This struct will be serialized in the inspector, and can be seen in inspector for your URP render asset once its added.</p>

<p>In the <code class="language-plaintext highlighter-rouge">Create()</code> function we’ll create our <code class="language-plaintext highlighter-rouge">DitherPass</code> using its constructor, and passing it our settings, as well as setting the renderPassEvent to the one in the settings.</p>

<p><em>once again you can find these files on the github</em><sup id="fnref:gitfooter:2" role="doc-noteref"><a href="#fn:gitfooter" class="footnote" rel="footnote">1</a></sup></p>
<div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">using</span> <span class="nn">UnityEngine</span><span class="p">;</span>
<span class="k">using</span> <span class="nn">UnityEngine.Rendering.Universal</span><span class="p">;</span>

<span class="k">public</span> <span class="k">class</span> <span class="nc">DitherPassFeature</span> <span class="p">:</span> <span class="n">ScriptableRendererFeature</span>
<span class="p">{</span>
    <span class="c1">// the settings for our render pass that will be passed in the constructor</span>
    <span class="p">[</span><span class="n">System</span><span class="p">.</span><span class="n">Serializable</span><span class="p">]</span>
    <span class="k">public</span> <span class="k">struct</span> <span class="nc">DitherSettings</span>
    <span class="p">{</span>
        <span class="c1">// when during the rendering process this pass will happen</span>
        <span class="k">public</span> <span class="n">RenderPassEvent</span> <span class="n">renderPassEvent</span><span class="p">;</span>
        <span class="c1">// the material that the render will use</span>
        <span class="k">public</span> <span class="n">Material</span> <span class="n">ditherMaterial</span><span class="p">;</span>
        <span class="c1">// the render texture of our squashed-down resolution</span>
        <span class="k">public</span> <span class="n">RenderTexture</span> <span class="n">renderTex</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">private</span> <span class="n">DitherPass</span> <span class="n">ditherPass</span><span class="p">;</span>
    <span class="k">public</span> <span class="n">DitherSettings</span> <span class="n">ditherSettings</span><span class="p">;</span>

    <span class="c1">// creating the render pass</span>
    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">Create</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="n">ditherPass</span> <span class="p">=</span> <span class="k">new</span> <span class="nf">DitherPass</span><span class="p">(</span><span class="n">ditherSettings</span><span class="p">);</span>

        <span class="n">ditherPass</span><span class="p">.</span><span class="n">renderPassEvent</span> <span class="p">=</span> <span class="n">ditherSettings</span><span class="p">.</span><span class="n">renderPassEvent</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="c1">// adding the render pass to the renderer</span>
    <span class="k">public</span> <span class="k">override</span> <span class="k">void</span> <span class="nf">AddRenderPasses</span><span class="p">(</span><span class="n">ScriptableRenderer</span> <span class="n">renderer</span><span class="p">,</span> <span class="k">ref</span> <span class="n">RenderingData</span> <span class="n">renderingData</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="c1">// only adding it if the material AND render texture are set up, so that there are no errors</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">ditherSettings</span><span class="p">.</span><span class="n">ditherMaterial</span> <span class="p">!=</span> <span class="k">null</span> <span class="p">&amp;&amp;</span> <span class="n">ditherSettings</span><span class="p">.</span><span class="n">renderTex</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="c1">// adding our pass to the render pipeline</span>
            <span class="n">renderer</span><span class="p">.</span><span class="nf">EnqueuePass</span><span class="p">(</span><span class="n">ditherPass</span><span class="p">);</span>
        <span class="p">}</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<blockquote class="prompt-info">
  <p>making custom render passes is how you do any custom post processing in URP, so its useful information even if you don’t intend to use dithering in every project</p>
</blockquote>

<h3 id="basic-setup">Basic Setup</h3>

<p><img src="../assets/postassets/DitheredRendering/urp-assets.png" alt="Universal Render Pipeline Assets" width="125px" class="right" />
Once you have these two scripts in your project, and hopefully have set up URP so that you have a <strong>Universal Render Pipeline Asset</strong> and <strong>Unviersal Renderer Data</strong> objects, you can add your render pass to the Universal Renderer Data object.</p>

<p>By clicking <strong>‘Add Render Feature’</strong> and selecting your custom render feature from the dropdown, you’ve added it to your render pipeline. Hooray! but unfortunately you haven’t actually set anything else up yet, so either nothing has happened or your project has started throwing errors.</p>

<p><img src="../assets/postassets/DitheredRendering/dither-pass-add.png" alt="Add Render Feature Context Menu" width="100%" /></p>

<p>The next thing to do is to create a Render Texture asset to assign to this render feature in the inspector, as well as set up your <strong>Render Pass Event</strong> (which should preferably be <em>before rendering post processing</em>).</p>

<p><img src="../assets/postassets/DitheredRendering/render-feature-inspector.png" alt="Render Feature Inspector" width="100%" /></p>

<p>You can create a Render Texture by doing <strong>Assets &gt; Create &gt; Custom Render Texture</strong>. Since this will be the squashed down resolution we want, its probably good to set it to something like <strong>640 x 360</strong>, or <strong>480 x 270</strong>, basically some multiple of 16 x 9 (the standard screen ratio). If you want to try some different ratios, like 4x3 for that retro aesthetic, thats probably better done by adding black bars either side of the screen than by actually messing with the output resolution, since on a full-screen application it will warp and stretch otherwise.
Its <strong>very important</strong> that you make sure your Render Texture has <strong>no anti-aliasing</strong> and <strong>Filter Mode is set to Point</strong>, since if either of these things aren’t true it will look strange.</p>

<blockquote class="prompt-warning">
  <p>Warning: if you want your application to be resizable, or in a resolution smaller than 640 x 360, you’ll notice some visual disturbances. Hopefully you’re smarter than me and can fix those, since I’ve chosen to live with them. God save us all</p>
</blockquote>

<blockquote class="prompt-danger">
  <p>Take Heed: For some arcane reason, setting your <strong>Render Pass Event</strong> to ‘after post processing’ makes the whole thing not work; this means that post-processing wont be applied through the filter and will instead be overlayed on-top. God save us all.</p>
</blockquote>

<p>now you hopefully have a working render feature! In the next little section we’ll make a test shader using Shadergraph to make sure its all working good.</p>

<hr />

<h2 id="the-shader">The Shader</h2>

<p>Although we probably could write the shader out in HLSL, its more fun to play around with using unity’s Shadergraph, since it helps to visualise the shader as it progresses through each node.</p>

<p>The basic idea behind our dithering shader is <strong>4 steps</strong>:</p>
<ol>
  <li>Round all the colours down to the nearest multiple of our ColourDepth (higher ColourDepth means fewer colours, ironically).</li>
  <li>Get the remainder of this division, which serves as how far along the gradient between the current and next multiple of ColourDepth is.</li>
  <li>Apply the dithering texture to this remainder, using it to smoothly interpolate between each step of the ColourDepth multiples.</li>
  <li>add this back to the result from step 1.</li>
</ol>

<p><img src="../assets/postassets/DitheredRendering/shader-step-0.png" alt="PSX style 3D lighthouse rendered without any filters" width="350px" class="right" /></p>

<p>Here’s the lighthouse scene again, this time showcasing each step of our shader, first without any effects applied. As you can see, the second and third stages are really dark, this is because we’re using tiny number values, which are visually represented as very very dark colours. Games like <a href="https://store.steampowered.com/app/2835570/Buckshot_Roulette/">Buckshot Roulette</a> just use the result of the first step, creating cool colour banding (callback to how dithering was originally used to alleviate colour banding in compressed images)</p>

<div style="display:flex">
    <img src="../assets/postassets/DitheredRendering/shader-step-1.png" title="The first step, featuring heavy colour banding" alt="A 3D low-poly lighthouse rendered with heavy colour banding" height="20%" />
    <img src="../assets/postassets/DitheredRendering/shader-step-2.png" title="The second step, just the remainder of the inital rounding of colours" alt="A 3D low-poly lighthouse with very dark colours, almost like a ghost imprint of the missing aspects of the lighthouse after step 1" height="20%" />
    <img src="../assets/postassets/DitheredRendering/shader-step-3.png" title="The third step, this remainder with dithering applied" alt="A 3D low-poly lighthouse ghost imprint rendered with a dithered filter" height="20%" />
    <img src="../assets/postassets/DitheredRendering/shader-step-4.png" title="The fourth step, and the compilation of our inital rounded image with the additional dithering" alt="A 3D low-poly psx style lighthouse rendered with a dithering filter" height="20%" />
</div>

<h3 id="shadergraph-setup">Shadergraph Setup</h3>

<p>Shadergraph is unity’s node-based shader editor. If you have installed and set up URP, you should be able to make a <strong>Fullscreen Shader Graph</strong> by doing <strong>Assets &gt; Create &gt; Shader Graph &gt; URP &gt; Fullscreen Shader Graph</strong>.</p>

<p>In order to make this shader work with our custom render pass, we need to first give it a Texture2D parameter called <code class="language-plaintext highlighter-rouge">MainTex</code>. This name is really important, as the reference name for this variable, _MainTex, needs to be accurate in order for the render pipeline to give our material the correct information.
<img src="../assets/postassets/DitheredRendering/main-tex-parameter.png" alt="MainTex parameter in shadergraph" width="600px" /></p>

<p>This <strong>MainTex</strong> parameter is the texture for our render, and contains the result of the screen’s render. To create a simple test shader, just put this node into a <strong>One-Minus</strong> node, and then have that output to the base colour out value.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-inverse-colours-example.png" alt="Inverse Colours Shadergraph" width="600px" /></p>

<p>Then, you can make a material using <strong>Assets &gt; Create &gt; Material</strong> and drag the Shadergraph asset onto that material. You can then assign this material in the render feature settings on your URP asset, which should hopefully output this inverted colours render. (without, and then with the shader active)</p>

<div style="display:flex">
    <img src="../assets/postassets/DitheredRendering/shader-step-0.png" title="The lighthouse without any effects" alt="A 3D low-poly lighthouse" height="20%" />
    <img src="../assets/postassets/DitheredRendering/inverted-colours-render.png" title="The lighthouse with inverted colours" alt="A 3D low-poly lighthouse showcasing inverted colours" height="20%" />
</div>

<h3 id="constructing-the-shader">Constructing The Shader</h3>

<p>For our shader to work we need to setup several parameters. Firstly, as mentioned before, a <strong>MainTex</strong> texture2D parameter, but also a <strong>DitherTex</strong> texture2D. We will have to assign this texture in the inspector for our material, and we’ll assign the mayer dither matrix image.</p>

<p>In addition to these two textures, we need a <strong>ColourDepth</strong> float, which determines how heavy the colour rounding will be, and a <strong>ColourClamp</strong> float, which cuts off the very top and bottom of the dither texture, making it flatten out faster. I wouldn’t recommend setting the ColourClamp to anything higher than .1, and setting it to anything higher than .5 breaks it all.</p>

<p>Here’s the graph in its entirety, but I will break down each part of it in a moment.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-full-dither.png" alt="Dithering Shader Full Shadergraph" /></p>

<p>lets start with the dithering texture itself</p>

<h4 id="working-with-the-dither-texture">Working With The Dither Texture</h4>

<p>This little bit of the graph works by dividing the size of the <strong>MainTex</strong> texture (and therefore the size of the screen) by the size of the <strong>DitherTex</strong> texture. Using this value as the <strong>tiling</strong> input in a <strong>Tiling and Offset</strong> node, we effectively repeat this image across the entire screen. This will be really useful when we need to use it to interpolate between stages of the rounded colours.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-dither-tiling.png" alt="Tiling the dither texture across the screen" width="600px" /></p>

<p>after this, we use the <strong>ColourClamp</strong> variable to clamp the upper and lower limits of the dither texture, making it flatten out quicker.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-dither-clamping.png" alt="Clamping the dither texture using a Clamp node" width="600px" /></p>

<p>now we can move onto handling the colours</p>

<h4 id="colour-magic">Colour Magic</h4>

<p>First, we round the colour of the pixel down to the nearest multiple of ColourDepth. The equation to round a value down to the nearest variable is <code class="language-plaintext highlighter-rouge">floor(value / multiple) * multple</code>, which we do to the sampled RGBA value from the <strong>Sample Texture 2D</strong> node.</p>

<blockquote class="prompt-tip">
  <p>also, if you replace <code class="language-plaintext highlighter-rouge">floor</code> in that equation with <code class="language-plaintext highlighter-rouge">round</code>, you’ll round to the nearest multiple, and <code class="language-plaintext highlighter-rouge">ceil</code> to round up to the nearest multiple. Don’t do that for this though, rounding down is important</p>
</blockquote>

<p>In our shadergraph, that equation looks like this:</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-colour-rounding.png" alt="Shadergraph colour rounding" width="600px" /></p>

<p>Ignore all the lines disappearing off the bottom of the screen, thats for stage 2.</p>

<p>Now that we have the rounded colours, we need the remainder of this division, which we can calculate using a <strong>modulo</strong> node. In order to sample our dither matrix, we need a value between 0 and 1 that represents the percentage of the way through the gradient each point is, which is calculated using <code class="language-plaintext highlighter-rouge">value / maxvalue = percent</code>. In our shadergraph, this means dividing the result of our modulo operation by the <strong>ColourDepth</strong> parameter.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-colour-remainder.png" alt="Remainder of colour division" width="600px" /></p>

<p>Now that we have our percentage, we can run the result though the <strong>step</strong> node, and then multiply the result by the colour steps, which is our parameter <strong>ColourDepth</strong>.</p>

<p><img src="../assets/postassets/DitheredRendering/shadergraph-dithering-remainder.png" alt="dithering the remainder" width="600px" /></p>

<p>Now all thats left is to add this final result to the result of our colour rounding, and we have a finished graph.</p>

<p>You can now happily enjoy the many fruits of your labour. I hope you make some cool scenes using this shader, and learned how to create a custom post processing path in unity!</p>

<div style="display:flex">
    <img src="../assets/postassets/DitheredRendering/lighthouse-screenshot.png" title="A lighthouse rendered using the dithering effect" alt="A low-poly 3D lighthouse rendered with a ps1 dithering filter" height="20%" />
    <img src="../assets/postassets/DitheredRendering/city-screenshot.png" title="A snowy city scene rendered with dithering" alt="A low-poly 3D snowy city rendered with a ps1 dithering filter" height="20%" />
</div>

<h2 id="footnotes">Footnotes</h2>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:gitfooter" role="doc-endnote">
      <p><a href="https://github.com/GiantBoar/URPDitheredRendering">The GitHub Repository</a> <a href="#fnref:gitfooter" class="reversefootnote" role="doc-backlink">&#8617;</a> <a href="#fnref:gitfooter:1" class="reversefootnote" role="doc-backlink">&#8617;<sup>2</sup></a> <a href="#fnref:gitfooter:2" class="reversefootnote" role="doc-backlink">&#8617;<sup>3</sup></a></p>
    </li>
    <li id="fn:blitfooter" role="doc-endnote">
      <p>A blit is basically rendering something from one texture to another, optionally using a material. In unity’s built-in render pipleine you can add a custom blit using a script, but with URP you need to blit in a custom render feature. <a href="#fnref:blitfooter" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name></name></author><category term="Tutorial" /><category term="Unity" /><category term="Rendering" /><category term="Unity" /><category term="C#" /><summary type="html"><![CDATA[Creating stylised 3D graphics using dithering in Unity's Universal Render Pipeline]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="/assets/postassets/DitheredRendering/lighthouse-screenshot.png" /><media:content medium="image" url="/assets/postassets/DitheredRendering/lighthouse-screenshot.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>