<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en"><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://wejdell.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://wejdell.github.io/" rel="alternate" type="text/html" hreflang="en" /><updated>2026-01-18T20:33:48+00:00</updated><id>https://wejdell.github.io/feed.xml</id><title type="html">Nicolas Wejdell</title><subtitle>Live Portfolio</subtitle><entry><title type="html">Shader Hot Reloading</title><link href="https://wejdell.github.io/shader-hot-compile/" rel="alternate" type="text/html" title="Shader Hot Reloading" /><published>2026-01-01T00:00:00+00:00</published><updated>2026-01-01T00:00:00+00:00</updated><id>https://wejdell.github.io/shader-hot-compile</id><content type="html" xml:base="https://wejdell.github.io/shader-hot-compile/"><![CDATA[<!--excerpt-begin-->
<p>Reloading shaders while the engine is running vastly increases your iteration speed and simplifies the debugging of old and new graphics features. 
<!--excerpt-end-->
Granted we’re talking about explicitly named, persistent shaders here and not some material-based, automatically generated solution. As you’re 
starting out making an engine this is most likely the setup you’ll be using for a while, and while implementing features such as 
<a href="https://en.wikipedia.org/wiki/Screen_space_ambient_occlusion">SSAO</a> or <a href="https://en.wikipedia.org/wiki/Tone_mapping">tone mapping</a>, 
it will be incredibly helpful to be able to tweak parameters or formulas on the fly.</p>

<h2 id="file-watching">File Watching</h2>

<p>Havtorn has a simple, request-based file watching system running on a separate thread, which is also used to <a href="/asset-hot-reloading/">hot reload assets</a>. 
We set it up to run on a separate thread at the point of initializing all core engine systems, and <code class="language-plaintext highlighter-rouge">sleep</code> it intermittently.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">CFileWatcher</span><span class="o">::~</span><span class="n">CFileWatcher</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">ShouldEndThread</span> <span class="o">=</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">CFileWatcher</span><span class="o">::</span><span class="n">Init</span><span class="p">(</span><span class="n">CThreadManager</span><span class="o">*</span> <span class="n">threadManager</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">threadManager</span><span class="p">)</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="n">threadManager</span><span class="o">-&gt;</span><span class="n">PushJob</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">CFileWatcher</span><span class="o">::</span><span class="n">UpdateChanges</span><span class="p">,</span> <span class="k">this</span><span class="p">));</span>
    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">CFileWatcher</span><span class="o">::</span><span class="n">UpdateChanges</span><span class="p">()</span>
<span class="p">{</span>
    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">ShouldEndThread</span><span class="p">)</span>
    <span class="p">{</span>	
        <span class="p">{</span>
            <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">Mutex</span><span class="p">);</span>
            <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="k">auto</span><span class="o">&amp;</span> <span class="p">[</span><span class="n">path</span><span class="p">,</span> <span class="n">currentTimestamp</span><span class="p">]</span> <span class="o">:</span> <span class="n">WatchedFiles</span><span class="p">)</span>
            <span class="p">{</span>
                <span class="k">const</span> <span class="n">U64</span> <span class="n">latestTimeStamp</span> <span class="o">=</span> <span class="n">GetFileTimestamp</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
                <span class="k">if</span> <span class="p">(</span><span class="n">latestTimeStamp</span> <span class="o">&gt;</span> <span class="n">currentTimestamp</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">QueuedFileChanges</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">path</span><span class="p">);</span>
                    <span class="n">WatchedFiles</span><span class="p">[</span><span class="n">path</span><span class="p">]</span> <span class="o">=</span> <span class="n">latestTimeStamp</span><span class="p">;</span>
                <span class="p">}</span>
            <span class="p">}</span>
        <span class="p">}</span>

        <span class="n">std</span><span class="o">::</span><span class="n">this_thread</span><span class="o">::</span><span class="n">sleep_for</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">chrono</span><span class="o">::</span><span class="n">milliseconds</span><span class="p">(</span><span class="n">SleepDurationMilliseconds</span><span class="p">));</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<p>The <code class="language-plaintext highlighter-rouge">WatchedFiles</code> property is an <code class="language-plaintext highlighter-rouge">std::map</code> we write to when requesting to watch a file on the main thread. We also provide an instance of 
a thin struct bundling our <code class="language-plaintext highlighter-rouge">std::function</code> callbacks with an explicit handle that the calling code must provide. This way we can easily find 
and remove the callback when we want to stop watching for changes to the file.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">SFileChangeCallback</span>
<span class="p">{</span>
    <span class="n">SFileChangeCallback</span><span class="p">()</span> <span class="o">=</span> <span class="k">delete</span><span class="p">;</span>
    <span class="k">explicit</span> <span class="n">SFileChangeCallback</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">function</span><span class="o">&lt;</span><span class="kt">void</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span><span class="p">)</span><span class="o">&gt;&amp;</span> <span class="n">function</span><span class="p">,</span> <span class="k">const</span> <span class="n">U64</span><span class="o">&amp;</span> <span class="n">handle</span><span class="p">)</span>
        <span class="o">:</span> <span class="n">Function</span><span class="p">(</span><span class="n">function</span><span class="p">)</span>
        <span class="p">,</span> <span class="n">Handle</span><span class="p">(</span><span class="n">handle</span><span class="p">)</span>
    <span class="p">{}</span>

    <span class="n">std</span><span class="o">::</span><span class="n">function</span><span class="o">&lt;</span><span class="kt">void</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span><span class="p">)</span><span class="o">&gt;</span> <span class="n">Function</span><span class="p">;</span>
    <span class="n">U64</span> <span class="n">Handle</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">bool</span> <span class="n">CFileWatcher</span><span class="o">::</span><span class="n">WatchFileChange</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">SFileChangeCallback</span> <span class="n">callback</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">path</span> <span class="n">newPath</span> <span class="o">=</span> <span class="n">filePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">exists</span><span class="p">(</span><span class="n">newPath</span><span class="p">))</span>
        <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>

    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">Mutex</span><span class="p">);</span>
    <span class="n">StoredCallbacks</span><span class="p">[</span><span class="n">newPath</span><span class="p">].</span><span class="n">push_back</span><span class="p">(</span><span class="n">callback</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">WatchedFiles</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">newPath</span><span class="p">))</span>
        <span class="n">WatchedFiles</span><span class="p">.</span><span class="n">emplace</span><span class="p">(</span><span class="n">newPath</span><span class="p">,</span> <span class="n">GetFileTimestamp</span><span class="p">(</span><span class="n">newPath</span><span class="p">));</span>

    <span class="k">return</span> <span class="nb">true</span><span class="p">;</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">CFileWatcher</span><span class="o">::</span><span class="n">StopWatchFileChange</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filePath</span><span class="p">,</span> <span class="k">const</span> <span class="n">U64</span><span class="o">&amp;</span> <span class="n">callbackHandle</span><span class="p">)</span>
<span class="p">{</span>	
    <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">path</span> <span class="n">existingPath</span> <span class="o">=</span> <span class="n">filePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">();</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">exists</span><span class="p">(</span><span class="n">existingPath</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">StoredCallbacks</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">existingPath</span><span class="p">))</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">Mutex</span><span class="p">);</span>
    <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">SFileChangeCallback</span><span class="o">&gt;&amp;</span> <span class="n">callbackContainer</span> <span class="o">=</span> <span class="n">StoredCallbacks</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="n">existingPath</span><span class="p">);</span>

    <span class="k">auto</span> <span class="n">it</span> <span class="o">=</span> <span class="n">std</span><span class="o">::</span><span class="n">ranges</span><span class="o">::</span><span class="n">find</span><span class="p">(</span><span class="n">callbackContainer</span><span class="p">,</span> <span class="n">callbackHandle</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">SFileChangeCallback</span><span class="o">::</span><span class="n">Handle</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">it</span> <span class="o">==</span> <span class="n">callbackContainer</span><span class="p">.</span><span class="n">end</span><span class="p">())</span>
        <span class="k">return</span><span class="p">;</span>
    
    <span class="n">callbackContainer</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">it</span><span class="p">);</span>

    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">callbackContainer</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span>
        <span class="k">return</span><span class="p">;</span>

    <span class="n">StoredCallbacks</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">existingPath</span><span class="p">);</span>
    <span class="n">WatchedFiles</span><span class="p">.</span><span class="n">erase</span><span class="p">(</span><span class="n">existingPath</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<p>While <code class="language-plaintext highlighter-rouge">UpdateChanges</code> runs on the file watch thread, we try to <code class="language-plaintext highlighter-rouge">FlushChanges</code> at a known point on the main thread (e.g. at the start or end of a frame), where we go through 
the queued up changes from the file watch thread and call all the callbacks.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">CFileWatcher</span><span class="o">::</span><span class="n">FlushChanges</span><span class="p">()</span>
<span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">Mutex</span><span class="p">);</span>
    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">QueuedFileChanges</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">filesystem</span><span class="o">::</span><span class="n">path</span> <span class="n">filePath</span> <span class="o">=</span> <span class="n">QueuedFileChanges</span><span class="p">.</span><span class="n">front</span><span class="p">();</span>
        <span class="n">QueuedFileChanges</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">StoredCallbacks</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">filePath</span><span class="p">))</span>
            <span class="k">continue</span><span class="p">;</span>

        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">SFileChangeCallback</span><span class="o">&gt;&amp;</span> <span class="n">callbacks</span> <span class="o">=</span> <span class="n">StoredCallbacks</span><span class="p">[</span><span class="n">filePath</span><span class="p">];</span>
        <span class="k">for</span> <span class="p">(</span><span class="k">const</span> <span class="n">SFileChangeCallback</span><span class="o">&amp;</span> <span class="n">callback</span> <span class="o">:</span> <span class="n">callbacks</span><span class="p">)</span>
            <span class="n">callback</span><span class="p">.</span><span class="n">Function</span><span class="p">(</span><span class="n">filePath</span><span class="p">.</span><span class="n">string</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<h2 id="shader-hot-reload">Shader Hot Reload</h2>

<p>At the point of loading shaders, we also find the corresponding source files and start watching them for changes. Naturally, we wouldn’t want 
or need to do this for release builds. Notably I’m making it easy for myself here. Because we have an explicit, static set of shaders, 
we can store them in <code class="language-plaintext highlighter-rouge">std::array</code>s and just index into those directly to switch out the shaders when reloading them.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">CRenderStateManager</span><span class="o">::</span><span class="n">AddShader</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filePath</span><span class="p">,</span> <span class="k">const</span> <span class="n">U64</span> <span class="n">index</span><span class="p">,</span> <span class="k">const</span> <span class="n">EShaderType</span> <span class="n">shaderType</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">...</span>

    <span class="k">switch</span> <span class="p">(</span><span class="n">shaderType</span><span class="p">)</span>
    <span class="p">{</span>
    <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Vertex</span><span class="p">:</span>
    <span class="p">{</span>
        <span class="p">...</span>
    <span class="p">}</span>
    <span class="p">...</span>
    <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Pixel</span><span class="p">:</span>
    <span class="p">{</span>
        <span class="c1">// Index directly into the std::array</span>

        <span class="k">if</span> <span class="p">(</span><span class="n">PixelShaders</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">!=</span> <span class="nb">nullptr</span><span class="p">)</span>
            <span class="n">PixelShaders</span><span class="p">[</span><span class="n">index</span><span class="p">]</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span>

        <span class="n">CPixelShader</span><span class="o">*</span> <span class="n">newPixelShader</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
        <span class="n">UGraphicsUtils</span><span class="o">::</span><span class="n">CreatePixelShader</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="n">Framework</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">newPixelShader</span><span class="p">);</span>
        <span class="n">PixelShaders</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">=</span> <span class="n">pixelShader</span><span class="p">;</span>
    <span class="p">}</span>
    <span class="k">break</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">sourceFile</span> <span class="o">=</span> <span class="n">UGeneralUtils</span><span class="o">::</span><span class="n">DeriveSourceFileFromPath</span><span class="p">(</span><span class="n">filePath</span><span class="p">,</span> <span class="s">"hlsl"</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ShaderInitData</span><span class="p">.</span><span class="n">contains</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">GEngine</span><span class="o">::</span><span class="n">GetFileWatcher</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">WatchFileChange</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">,</span> <span class="n">SFileChangeCallback</span><span class="p">(</span><span class="n">std</span><span class="o">::</span><span class="n">bind</span><span class="p">(</span><span class="o">&amp;</span><span class="n">CRenderStateManager</span><span class="o">::</span><span class="n">OnShaderSourceChange</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="n">std</span><span class="o">::</span><span class="n">placeholders</span><span class="o">::</span><span class="n">_1</span><span class="p">),</span> <span class="n">OnShaderSourceChangeFunctionHandle</span><span class="p">));</span>
        
        <span class="c1">// NW: Save some extra context about the file so we can call this function with the same arguments again later</span>
        <span class="n">ShaderInitData</span><span class="p">.</span><span class="n">emplace</span><span class="p">(</span><span class="n">sourceFile</span><span class="p">,</span> <span class="n">SShaderInitData</span><span class="p">{</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">shaderType</span><span class="p">,</span> <span class="n">index</span> <span class="p">});</span>
    <span class="p">}</span>

    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<p>When the source file changes, we queue up the file path to be recompiled to a new binary at a good time, similar to what we do in the <code class="language-plaintext highlighter-rouge">FileWatcher</code>. 
In this case, we flush the changes when the main thread and render thread sync and swap resources.</p>

<p>This code is specific to DirectX11, but the same principles apply for other backends. Implementations and compilers used for Vulkan and even DirectX12 will differ, 
but the information seems fairly easy to find. Havtorn doesn’t yet support the newer generation of backends so I will just show our solution for the 
DirectX11 case here.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">CRenderStateManager</span><span class="o">::</span><span class="n">OnShaderSourceChange</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filePath</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">ShaderRecompileMutex</span><span class="p">);</span>
    <span class="n">QueuedShaderRecompiles</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">filePath</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">CRenderStateManager</span><span class="o">::</span><span class="n">FlushShaderChanges</span><span class="p">()</span>
<span class="p">{</span>
    <span class="c1">// NW: Use DXC for DirectX12 Shader Model 6.0 and above, or one of the Vulkan shader compilers to compile into SPIR-V for Vulkan, e.g. glslc or glslang</span>
    <span class="c1">// https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-part1</span>
    <span class="c1">// https://github.com/KhronosGroup/glslang</span>

    <span class="n">std</span><span class="o">::</span><span class="n">lock_guard</span><span class="o">&lt;</span><span class="n">std</span><span class="o">::</span><span class="n">mutex</span><span class="o">&gt;</span> <span class="n">lock</span><span class="p">(</span><span class="n">ShaderRecompileMutex</span><span class="p">);</span>
    <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">QueuedShaderRecompiles</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">changedSourceFile</span> <span class="o">=</span> <span class="n">QueuedShaderRecompiles</span><span class="p">.</span><span class="n">front</span><span class="p">();</span>
        <span class="n">QueuedShaderRecompiles</span><span class="p">.</span><span class="n">pop</span><span class="p">();</span>

        <span class="k">const</span> <span class="n">SShaderInitData</span> <span class="n">initData</span> <span class="o">=</span> <span class="n">ShaderInitData</span><span class="p">.</span><span class="n">at</span><span class="p">(</span><span class="n">changedSourceFile</span><span class="p">);</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">wideSourceFilePath</span> <span class="o">=</span> <span class="p">{</span> <span class="n">changedSourceFile</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">changedSourceFile</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="p">};</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">wstring</span> <span class="n">wideOutputFilePath</span> <span class="o">=</span> <span class="p">{</span> <span class="n">initData</span><span class="p">.</span><span class="n">OutputFileName</span><span class="p">.</span><span class="n">begin</span><span class="p">(),</span> <span class="n">initData</span><span class="p">.</span><span class="n">OutputFileName</span><span class="p">.</span><span class="n">end</span><span class="p">()</span> <span class="p">};</span>

        <span class="n">ID3DBlob</span><span class="o">*</span> <span class="n">compiledContents</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>
        <span class="n">ID3DBlob</span><span class="o">*</span> <span class="n">errorMessages</span> <span class="o">=</span> <span class="nb">nullptr</span><span class="p">;</span>

        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">shaderModel</span><span class="p">;</span>
        <span class="k">switch</span> <span class="p">(</span><span class="n">initData</span><span class="p">.</span><span class="n">ShaderType</span><span class="p">)</span>
        <span class="p">{</span>
        <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Pixel</span><span class="p">:</span>
            <span class="n">shaderModel</span> <span class="o">=</span> <span class="s">"ps_5_0"</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Geometry</span><span class="p">:</span>
            <span class="n">shaderModel</span> <span class="o">=</span> <span class="s">"gs_5_0"</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Compute</span><span class="p">:</span>
            <span class="n">shaderModel</span> <span class="o">=</span> <span class="s">"cs_5_0"</span><span class="p">;</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="k">case</span> <span class="n">EShaderType</span><span class="o">::</span><span class="n">Vertex</span><span class="p">:</span>
            <span class="p">[[</span><span class="n">fallthrough</span><span class="p">]];</span>
        <span class="nl">default:</span>
            <span class="n">shaderModel</span> <span class="o">=</span> <span class="s">"vs_5_0"</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">UShaderIncludeHandler</span> <span class="n">customIncludeHandler</span><span class="p">;</span>
        <span class="k">const</span> <span class="n">HRESULT</span> <span class="n">compileResult</span> <span class="o">=</span> <span class="n">D3DCompileFromFile</span><span class="p">(</span><span class="n">wideSourceFilePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">customIncludeHandler</span><span class="p">,</span> <span class="s">"main"</span><span class="p">,</span> <span class="n">shaderModel</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">compiledContents</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">errorMessages</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">compileResult</span> <span class="o">!=</span> <span class="n">S_OK</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">HV_LOG_ERROR</span><span class="p">(</span><span class="s">"CRenderStateManager::OnShaderSourceChange: Shader %s could not be recompiled: %s"</span><span class="p">,</span> <span class="n">changedSourceFile</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="p">(</span><span class="kt">char</span><span class="o">*</span><span class="p">)</span><span class="n">errorMessages</span><span class="o">-&gt;</span><span class="n">GetBufferPointer</span><span class="p">());</span>
            <span class="n">errorMessages</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="k">const</span> <span class="n">HRESULT</span> <span class="n">rewriteResult</span> <span class="o">=</span> <span class="n">D3DWriteBlobToFile</span><span class="p">(</span><span class="n">compiledContents</span><span class="p">,</span> <span class="n">wideOutputFilePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">TRUE</span><span class="p">);</span>
        <span class="k">if</span> <span class="p">(</span><span class="n">rewriteResult</span> <span class="o">!=</span> <span class="n">S_OK</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">HV_LOG_ERROR</span><span class="p">(</span><span class="s">"CRenderStateManager::OnShaderSourceChange: Shader %s was successfully recompiled, but output file could not be overwritten."</span><span class="p">,</span> <span class="n">changedSourceFile</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
            <span class="n">compiledContents</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span>
            <span class="k">break</span><span class="p">;</span>
        <span class="p">}</span>

        <span class="n">compiledContents</span><span class="o">-&gt;</span><span class="n">Release</span><span class="p">();</span>

        <span class="c1">// NW: Re-add shader using the context we saved before</span>
        <span class="n">AddShader</span><span class="p">(</span><span class="n">initData</span><span class="p">.</span><span class="n">OutputFileName</span><span class="p">,</span> <span class="n">initData</span><span class="p">.</span><span class="n">ShaderIndex</span><span class="p">,</span> <span class="n">initData</span><span class="p">.</span><span class="n">ShaderType</span><span class="p">);</span>

        <span class="n">HV_LOG_INFO</span><span class="p">(</span><span class="s">"Shader source file %s was recompiled."</span><span class="p">,</span> <span class="n">changedSourceFile</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<p>Note the custom <em>include handler</em> used in the compilation call. DirectX (I’m not sure about Vulkan) needs this to know what to do when it comes across an <code class="language-plaintext highlighter-rouge">#include</code> directive 
in the hlsl source code during compilation. You can provide a default one by using the <code class="language-plaintext highlighter-rouge">D3D_COMPILE_STANDARD_FILE_INCLUDE</code> macro, which will find files relative to the 
source file directory, or pass <code class="language-plaintext highlighter-rouge">nullptr</code> if you don’t include any files in the source code. In our case, we’re including files from a specific <code class="language-plaintext highlighter-rouge">Includes</code> directory 
in the shader source directory, and some include files include other files also relative to this directory. I ended up with this custom include handler for use 
under these very specific conditions.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">class</span> <span class="nc">UShaderIncludeHandler</span> <span class="o">:</span> <span class="k">public</span> <span class="n">ID3DInclude</span>
<span class="p">{</span>
    <span class="n">HRESULT</span> <span class="n">Open</span><span class="p">(</span><span class="n">D3D_INCLUDE_TYPE</span> <span class="cm">/*includeType*/</span><span class="p">,</span> <span class="n">LPCSTR</span> <span class="n">pFileName</span><span class="p">,</span> <span class="n">LPCVOID</span> <span class="cm">/*pParentData*/</span><span class="p">,</span> <span class="n">LPCVOID</span><span class="o">*</span> <span class="n">ppData</span><span class="p">,</span> <span class="n">UINT</span><span class="o">*</span> <span class="n">pBytes</span><span class="p">)</span> <span class="k">override</span>
    <span class="p">{</span>
        <span class="c1">// NW: Only include files in the Shaders/Includes folder in shaders.</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">shaderIncludeSource</span> <span class="o">=</span> <span class="n">UGeneralUtils</span><span class="o">::</span><span class="n">ExtractParentDirectoryFromPath</span><span class="p">(</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">GetWorkingPath</span><span class="p">())</span> <span class="o">+</span> <span class="s">"Source/Engine/Graphics/Shaders/Includes/"</span><span class="p">;</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">inputFileName</span> <span class="o">=</span> <span class="n">UGeneralUtils</span><span class="o">::</span><span class="n">ExtractFileNameFromPath</span><span class="p">(</span><span class="n">pFileName</span><span class="p">);</span>
        <span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">filePath</span> <span class="o">=</span> <span class="n">shaderIncludeSource</span> <span class="o">+</span> <span class="n">inputFileName</span><span class="p">;</span>

        <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">Exists</span><span class="p">(</span><span class="n">filePath</span><span class="p">))</span>
            <span class="k">return</span> <span class="n">E_FAIL</span><span class="p">;</span>

        <span class="n">U32</span> <span class="n">fileSize</span> <span class="o">=</span> <span class="n">STATIC_U32</span><span class="p">(</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">GetFileSize</span><span class="p">(</span><span class="n">filePath</span><span class="p">));</span>
        <span class="kt">char</span><span class="o">*</span> <span class="n">data</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">char</span><span class="p">[</span><span class="n">fileSize</span><span class="p">];</span>
        
        <span class="n">std</span><span class="o">::</span><span class="n">ifstream</span> <span class="n">inputStream</span><span class="p">;</span>
        <span class="n">inputStream</span><span class="p">.</span><span class="n">open</span><span class="p">(</span><span class="n">filePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">(),</span> <span class="n">fstream</span><span class="o">::</span><span class="n">in</span> <span class="o">|</span> <span class="n">fstream</span><span class="o">::</span><span class="n">binary</span><span class="p">);</span>
        
        <span class="n">inputStream</span><span class="p">.</span><span class="n">read</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">fileSize</span><span class="p">);</span>
        <span class="n">inputStream</span><span class="p">.</span><span class="n">close</span><span class="p">();</span>

        <span class="o">*</span><span class="n">pBytes</span> <span class="o">=</span> <span class="n">fileSize</span><span class="p">;</span>
        <span class="o">*</span><span class="n">ppData</span> <span class="o">=</span> <span class="n">data</span><span class="p">;</span>
            
        <span class="k">return</span> <span class="n">S_OK</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="n">HRESULT</span> <span class="n">Close</span><span class="p">(</span><span class="n">LPCVOID</span> <span class="n">pData</span><span class="p">)</span> <span class="k">override</span>
    <span class="p">{</span>
        <span class="k">delete</span><span class="p">[]</span> <span class="n">pData</span><span class="p">;</span>
        <span class="k">return</span> <span class="n">S_OK</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">};</span>
</code></pre></div>  </div>
</details>]]></content><author><name></name></author><category term="havtorn" /><category term="havtorn" /><category term="rendering" /><category term="shader" /><summary type="html"><![CDATA[Reloading shaders while the engine is running vastly increases your iteration speed and simplifies the debugging of old and new graphics features.]]></summary></entry><entry><title type="html">Asset Hot Reloading</title><link href="https://wejdell.github.io/asset-hot-reloading/" rel="alternate" type="text/html" title="Asset Hot Reloading" /><published>2025-09-05T00:00:00+00:00</published><updated>2025-09-05T00:00:00+00:00</updated><id>https://wejdell.github.io/asset-hot-reloading</id><content type="html" xml:base="https://wejdell.github.io/asset-hot-reloading/"><![CDATA[<p>The asset registry in Havtorn was recently refactored to move all asset data (e.g. vertices in a mesh, the pixels of a texture) to a centralized location. Instead of each ECS component having its own copy of the asset data it needs to reference, as before,
every component now has one or more <code class="language-plaintext highlighter-rouge">AssetReference</code>s. These are easy to serialize, and we can let the asset registry have responsibility over the lifetime of the asset data. Systems working on entities just request the data from the registry using the asset references
held by components.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="nc">SAssetReference</span>
<span class="p">{</span>
    <span class="n">U32</span> <span class="n">UID</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">FilePath</span> <span class="o">=</span> <span class="s">"NullAsset"</span><span class="p">;</span>
    
    <span class="n">SAssetReference</span><span class="p">()</span> <span class="o">=</span> <span class="k">default</span><span class="p">;</span>
    <span class="k">explicit</span> <span class="n">SAssetReference</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">string</span><span class="o">&amp;</span> <span class="n">filePath</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">FilePath</span> <span class="o">=</span> <span class="n">UGeneralUtils</span><span class="o">::</span><span class="n">ConvertToPlatformAgnosticPath</span><span class="p">(</span><span class="n">filePath</span><span class="p">);</span>
        <span class="n">U32</span> <span class="n">prime</span> <span class="o">=</span> <span class="mh">0x1000193</span><span class="p">;</span>
        <span class="n">UID</span> <span class="o">=</span> <span class="mh">0x811c9dc5</span><span class="p">;</span>
        
        <span class="k">for</span> <span class="p">(</span><span class="n">U64</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">FilePath</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">U8</span> <span class="n">value</span> <span class="o">=</span> <span class="n">FilePath</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
            <span class="n">UID</span> <span class="o">=</span> <span class="n">UID</span> <span class="o">^</span> <span class="n">value</span><span class="p">;</span>
            <span class="n">UID</span> <span class="o">*=</span> <span class="n">prime</span><span class="p">;</span>
        <span class="p">}</span>
    <span class="p">}</span>
    
       <span class="p">...</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<!--excerpt-begin-->
<p>The gif above shows the first iteration of hot reloading assets, where for any asset that was originally authored in an external program such as Blender or Photoshop, the registry can dynamically reimport it as soon as the source file (fbx, dds) changes on disk.
 As an example, a mesh can be placed in the editor, then the source file watching on the asset be toggled, and whenever the fbx is changed on disk the engine reimports it with the same settings on the fly.
<!--excerpt-end-->
We don’t need to update all the components that care about that particular asset, the registry just internally updates the data that it manages.</p>

<p>The only reason that enabling the file watching is a manual step is so that we don’t hog resources by watching for file changes when it’s not needed. Of course a more automatic setup could be more fitting for exploratory phases of a project.</p>

<h2 id="asset-redirection">Asset Redirection</h2>

<p>As part of this I also implemented rudimentary <em>asset redirection</em>, somewhat similar to what Unreal Engine does, meaning that assets can be moved around in the editor to different directories, and a link is created between the old file path and the new. 
If an asset is already loaded, the components using it can continue to do so with the same <code class="language-plaintext highlighter-rouge">AssetReference</code>. If the asset <em>isn’t</em> loaded, we traverse the redirections to try to find an existing file. If we do, we continue loading as necessary.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">bool</span> <span class="n">CAssetRegistry</span><span class="o">::</span><span class="n">LoadAsset</span><span class="p">(</span><span class="k">const</span> <span class="n">SAssetReference</span><span class="o">&amp;</span> <span class="n">assetRef</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">filePath</span> <span class="o">=</span> <span class="n">assetRef</span><span class="p">.</span><span class="n">FilePath</span><span class="p">;</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">Exists</span><span class="p">(</span><span class="n">filePath</span><span class="p">))</span>
    <span class="p">{</span>
        <span class="n">CJsonDocument</span> <span class="n">config</span> <span class="o">=</span> <span class="n">UFileSystem</span><span class="o">::</span><span class="n">OpenJson</span><span class="p">(</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">EngineConfig</span><span class="p">);</span>
        <span class="n">std</span><span class="o">::</span><span class="n">string</span> <span class="n">redirection</span> <span class="o">=</span> <span class="n">config</span><span class="p">.</span><span class="n">GetValueFromArray</span><span class="p">(</span><span class="s">"Asset Redirectors"</span><span class="p">,</span> <span class="n">filePath</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
                        
        <span class="k">while</span> <span class="p">(</span><span class="o">!</span><span class="n">UFileSystem</span><span class="o">::</span><span class="n">Exists</span><span class="p">(</span><span class="n">redirection</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="n">redirection</span> <span class="o">!=</span> <span class="s">""</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">redirection</span> <span class="o">=</span> <span class="n">config</span><span class="p">.</span><span class="n">GetValueFromArray</span><span class="p">(</span><span class="s">"Asset Redirectors"</span><span class="p">,</span> <span class="n">redirection</span><span class="p">,</span> <span class="s">""</span><span class="p">);</span>
        <span class="p">}</span> 
            
        <span class="k">if</span> <span class="p">(</span><span class="n">redirection</span> <span class="o">==</span> <span class="s">""</span><span class="p">)</span>
        <span class="p">{</span>
            <span class="n">HV_LOG_WARN</span><span class="p">(</span><span class="s">"CAssetRegistry::LoadAsset: Asset file pointed to by %s failed to load, does not exist!"</span><span class="p">,</span> <span class="n">assetRef</span><span class="p">.</span><span class="n">FilePath</span><span class="p">.</span><span class="n">c_str</span><span class="p">());</span>
            <span class="k">return</span> <span class="nb">false</span><span class="p">;</span>
        <span class="p">}</span>
            
        <span class="n">filePath</span> <span class="o">=</span> <span class="n">redirection</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="c1">// Continue with asset loading</span>
    <span class="p">...</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>We can abuse the fact that the <code class="language-plaintext highlighter-rouge">AssetReference</code>s are just file path strings hashed into identifiers on construction. When an asset has been redirected and we succesfully load it, 
we can keep the hashed identifier we got from the original file path, instead of updating the identifier to correspond to the redirected path. That means that every following requester (other
components wanting to use the same redirected asset) can immediately get a hold of the asset with the identifier they already have stored. We only need to resolve the redirection once per load of the asset.</p>

<p>There’s no automatic clean up of the redirectors yet, but there is a manual step where you can click a button to update all assets on disk, if they have dependencies on other assets. Then you would have to go through
all components in all scenes (where ECS entities and components are stored) and update the asset they are pointing to. I think an automatic solution is doable though, you would just have to go through all entities on disk, and iterate through all components to see if
there are any old asset references.</p>]]></content><author><name></name></author><category term="havtorn" /><category term="havtorn" /><category term="editor" /><category term="assets" /><summary type="html"><![CDATA[The asset registry in Havtorn was recently refactored to move all asset data (e.g. vertices in a mesh, the pixels of a texture) to a centralized location. Instead of each ECS component having its own copy of the asset data it needs to reference, as before, every component now has one or more AssetReferences. These are easy to serialize, and we can let the asset registry have responsibility over the lifetime of the asset data. Systems working on entities just request the data from the registry using the asset references held by components. View Code struct SAssetReference { U32 UID = 0; std::string FilePath = "NullAsset"; SAssetReference() = default; explicit SAssetReference(const std::string&amp; filePath) { FilePath = UGeneralUtils::ConvertToPlatformAgnosticPath(filePath); U32 prime = 0x1000193; UID = 0x811c9dc5; for (U64 i = 0; i &lt; FilePath.size(); ++i) { U8 value = FilePath[i]; UID = UID ^ value; UID *= prime; } } ... } The gif above shows the first iteration of hot reloading assets, where for any asset that was originally authored in an external program such as Blender or Photoshop, the registry can dynamically reimport it as soon as the source file (fbx, dds) changes on disk. As an example, a mesh can be placed in the editor, then the source file watching on the asset be toggled, and whenever the fbx is changed on disk the engine reimports it with the same settings on the fly.]]></summary></entry><entry><title type="html">Visual Scripting</title><link href="https://wejdell.github.io/visual-scripting/" rel="alternate" type="text/html" title="Visual Scripting" /><published>2025-07-20T00:00:00+00:00</published><updated>2025-07-20T00:00:00+00:00</updated><id>https://wejdell.github.io/visual-scripting</id><content type="html" xml:base="https://wejdell.github.io/visual-scripting/"><![CDATA[<p>Havtorn uses its own visual scripting system, tailored to the custom ECS I built. The graphical node rendering is based on thedmd’s <a href="https://github.com/thedmd/imgui-node-editor">imgui node editor</a>.</p>

<!--excerpt-begin-->
<p>In the example we see a physics trigger getting a ScriptComponent referencing the test script asset we authored in the separate script editor. 
 When the block suspended in the air above overlaps with the trigger volume, we change the mesh from a block to the clock mesh, and the green point light in the foreground is toggled off. This was set up to show interactions between a few separate entities, our so called data bindings, and a non-trivial entry point for the script (OnBeginOverlap, instead of a simple Tick for example).
<!--excerpt-end--></p>

<p>Scripts are thought of as ECS systems, modifiable in editor on an asset level. They are not the same as, e.g., the editor representations of Unreal Engine’s objects. Because they are assets (like a model mesh or animation clip), only one instance can be loaded at a time.
They way they then work with persistent data, is by <em>declaring</em> what data they are expecting to work on, and receiving that data from each ECS component (holder of data) that <em>defines</em> it. For lack of a better term, I called this concept a <em>data binding</em> between the script and component.</p>

<p>In the example, the work that the script is meant to perform when triggered, is to shut off a point light on an ECS entity, and change the static mesh of the entity triggering the physics trigger volume. It can shut off any point light, and change the mesh to any mesh. We see in the clip 
that the script just declares its data bindings, and then we define exactly what point light entity to toggle the light on, and what mesh to set, in the component referencing the script. The data itself is owned by the component, but the script declares what it should be.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CScriptSystem.cpp</span>
<span class="p">...</span>

<span class="k">for</span> <span class="p">(</span><span class="n">SScriptComponent</span><span class="o">*</span> <span class="n">component</span> <span class="o">:</span> <span class="n">scriptComponents</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">...</span>
    
    <span class="n">SScript</span><span class="o">*</span> <span class="n">script</span> <span class="o">=</span> <span class="n">GEngine</span><span class="o">::</span><span class="n">GetAssetRegistry</span><span class="p">()</span><span class="o">-&gt;</span><span class="n">RequestAssetData</span><span class="o">&lt;</span><span class="n">SScript</span><span class="o">&gt;</span><span class="p">(</span><span class="n">component</span><span class="o">-&gt;</span><span class="n">AssetReference</span><span class="p">,</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">Owner</span><span class="p">.</span><span class="n">GUID</span><span class="p">);</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="n">component</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">!=</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">size</span><span class="p">())</span>
        <span class="n">component</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">size</span><span class="p">());</span>
    
    <span class="c1">// Input component values before running script</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">U64</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">SScriptDataBinding</span><span class="o">&amp;</span> <span class="n">scriptBinding</span> <span class="o">=</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="k">const</span> <span class="n">SScriptDataBinding</span><span class="o">&amp;</span> <span class="n">componentBinding</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        
        <span class="n">scriptBinding</span><span class="p">.</span><span class="n">Data</span> <span class="o">=</span> <span class="n">componentBinding</span><span class="p">.</span><span class="n">Data</span><span class="p">;</span>
    <span class="p">}</span>
    
    <span class="p">...</span>
    
    <span class="c1">// Start traversing by some means</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">beganPlay</span> <span class="o">&amp;&amp;</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">HasNode</span><span class="p">(</span><span class="n">BeginPlayNodeID</span><span class="p">))</span>
        <span class="n">script</span><span class="o">-&gt;</span><span class="n">TraverseFromNode</span><span class="p">(</span><span class="n">BeginPlayNodeID</span><span class="p">,</span> <span class="n">scene</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="n">script</span><span class="o">-&gt;</span><span class="n">HasNode</span><span class="p">(</span><span class="n">TickNodeID</span><span class="p">))</span>
        <span class="n">script</span><span class="o">-&gt;</span><span class="n">TraverseFromNode</span><span class="p">(</span><span class="n">TickNodeID</span><span class="p">,</span> <span class="n">scene</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
    
    <span class="k">if</span> <span class="p">(</span><span class="n">endedPlay</span> <span class="o">&amp;&amp;</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">HasNode</span><span class="p">(</span><span class="n">EndPlayNodeID</span><span class="p">))</span>
        <span class="n">script</span><span class="o">-&gt;</span><span class="n">TraverseFromNode</span><span class="p">(</span><span class="n">EndPlayNodeID</span><span class="p">,</span> <span class="n">scene</span><span class="p">.</span><span class="n">get</span><span class="p">());</span>
    
    <span class="c1">// Output script values to store in component after running script</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">U64</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">.</span><span class="n">size</span><span class="p">();</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">SScriptDataBinding</span><span class="o">&amp;</span> <span class="n">scriptBinding</span> <span class="o">=</span> <span class="n">script</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="n">SScriptDataBinding</span><span class="o">&amp;</span> <span class="n">componentBinding</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">DataBindings</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        
        <span class="n">componentBinding</span><span class="p">.</span><span class="n">Data</span> <span class="o">=</span> <span class="n">scriptBinding</span><span class="p">.</span><span class="n">Data</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="p">...</span>
</code></pre></div>  </div>
</details>

<p>This means that any number of entities can reference a script (loaded once in memory) and have it run logic on the entity’s own data. Just like any other ECS system, the script’s job is to 
manipulate data owned by ECS components. It’s just a system that you can author in the editor.</p>

<h2 id="authoring-nodes">Authoring Nodes</h2>

<p>Havtorn doesn’t have a reflection system (yet?), and authoring nodes is a manual process currently. The overhead isn’t huge, but we’re considering what the best strategic approach would be. Perhaps that would be having many small nodes that affect one property, and try to streamline the creation of those, or focus more on larger nodes that have more control over all the data held in a component for example.
I think it will have to be some sort of combination, and should be driven by the needs of the current project we’re working on. Here’s an example of a simple core functionality node.</p>

<details>
  <summary><strong>View Code</strong></summary>
  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="c1">// CoreNodes.h</span>
<span class="k">struct</span> <span class="nc">SBranchNode</span> <span class="o">:</span> <span class="k">public</span> <span class="n">SNode</span>
<span class="p">{</span>
    <span class="n">ENGINE_API</span> <span class="n">SBranchNode</span><span class="p">(</span><span class="k">const</span> <span class="n">U64</span> <span class="n">id</span><span class="p">,</span> <span class="k">const</span> <span class="n">U32</span> <span class="n">typeID</span><span class="p">,</span> <span class="n">SScript</span><span class="o">*</span> <span class="n">owningScript</span><span class="p">);</span>
    <span class="k">virtual</span> <span class="n">ENGINE_API</span> <span class="n">I8</span> <span class="n">OnExecute</span><span class="p">()</span> <span class="k">override</span><span class="p">;</span>
<span class="p">};</span>

<span class="c1">// CoreNodes.cpp</span>
<span class="n">SBranchNode</span><span class="o">::</span><span class="n">SBranchNode</span><span class="p">(</span><span class="k">const</span> <span class="n">U64</span> <span class="n">id</span><span class="p">,</span> <span class="k">const</span> <span class="n">U32</span> <span class="n">typeID</span><span class="p">,</span> <span class="n">SScript</span><span class="o">*</span> <span class="n">owningScript</span><span class="p">)</span>
    <span class="o">:</span> <span class="n">SNode</span><span class="o">::</span><span class="n">SNode</span><span class="p">(</span><span class="n">id</span><span class="p">,</span> <span class="n">typeID</span><span class="p">,</span> <span class="n">owningScript</span><span class="p">,</span> <span class="n">ENodeType</span><span class="o">::</span><span class="n">Standard</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">AddInput</span><span class="p">(</span><span class="n">UGUIDManager</span><span class="o">::</span><span class="n">Generate</span><span class="p">(),</span> <span class="n">EPinType</span><span class="o">::</span><span class="n">Flow</span><span class="p">);</span>
    <span class="n">AddInput</span><span class="p">(</span><span class="n">UGUIDManager</span><span class="o">::</span><span class="n">Generate</span><span class="p">(),</span> <span class="n">EPinType</span><span class="o">::</span><span class="n">Bool</span><span class="p">,</span> <span class="s">"Condition"</span><span class="p">);</span>

    <span class="n">AddOutput</span><span class="p">(</span><span class="n">UGUIDManager</span><span class="o">::</span><span class="n">Generate</span><span class="p">(),</span> <span class="n">EPinType</span><span class="o">::</span><span class="n">Flow</span><span class="p">,</span> <span class="s">"True"</span><span class="p">);</span>
    <span class="n">AddOutput</span><span class="p">(</span><span class="n">UGUIDManager</span><span class="o">::</span><span class="n">Generate</span><span class="p">(),</span> <span class="n">EPinType</span><span class="o">::</span><span class="n">Flow</span><span class="p">,</span> <span class="s">"False"</span><span class="p">);</span>
<span class="p">}</span>

<span class="n">I8</span> <span class="n">SBranchNode</span><span class="o">::</span><span class="n">OnExecute</span><span class="p">()</span>
<span class="p">{</span>
    <span class="kt">bool</span> <span class="n">condition</span> <span class="o">=</span> <span class="nb">false</span><span class="p">;</span>
    <span class="n">GetDataOnPin</span><span class="p">(</span><span class="n">EPinDirection</span><span class="o">::</span><span class="n">Input</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="n">condition</span><span class="p">);</span>

    <span class="c1">// Return pin index to continue traversal from</span>
    <span class="k">return</span> <span class="n">condition</span> <span class="o">?</span> <span class="mi">0</span> <span class="o">:</span> <span class="mi">1</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>
</details>

<div class="click-zoom">
    <label>
        <input type="checkbox" />
        <img class="centered" src="/assets/images/branch-node.png" />
    </label>
</div>]]></content><author><name></name></author><category term="havtorn" /><category term="havtorn" /><category term="editor" /><category term="scripting" /><summary type="html"><![CDATA[Havtorn uses its own visual scripting system, tailored to the custom ECS I built. The graphical node rendering is based on thedmd’s imgui node editor. In the example we see a physics trigger getting a ScriptComponent referencing the test script asset we authored in the separate script editor. When the block suspended in the air above overlaps with the trigger volume, we change the mesh from a block to the clock mesh, and the green point light in the foreground is toggled off. This was set up to show interactions between a few separate entities, our so called data bindings, and a non-trivial entry point for the script (OnBeginOverlap, instead of a simple Tick for example).]]></summary></entry><entry><title type="html">Skeletal Animation</title><link href="https://wejdell.github.io/skeletal-animation/" rel="alternate" type="text/html" title="Skeletal Animation" /><published>2025-04-17T00:00:00+00:00</published><updated>2025-04-17T00:00:00+00:00</updated><id>https://wejdell.github.io/skeletal-animation</id><content type="html" xml:base="https://wejdell.github.io/skeletal-animation/"><![CDATA[<!--excerpt-begin-->
<p>We purposefully serialize our skeletal mesh animations down to our own data formats and run the math using those. Other solutions instead
make a thin wrapper (or no wrapping at all) around imported assimp scenes, provided they are also working with assimp. I prefer our solution
as it gives us more control in implementing more advanced features such as inverse kinematics now that we’ve split up the logic into clear chunks.
<!--excerpt-end--></p>

<p>Our flow currently looks like this. We have a collection of thin <code class="language-plaintext highlighter-rouge">PlayData</code> objects that refer to distinct animation assets. The thought is that 
whenever we want play an animation, we just push one of these objects into the animation component’s collection. We start by resolving the local 
bone pose for each of the ones currently playing by recursively traversing the node graph. After that, all the <code class="language-plaintext highlighter-rouge">PlayData</code> local poses are blended together.
Following a final traversal of the tree where we apply the blended local pose, we iterate through the posed nodes and simply multiply the inverse bind pose
to get our final world space bone transform, and send that off to the shader.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// CAnimationGraphSystem.cpp</span>
<span class="p">...</span>

<span class="c1">// Read local poses of playing animations</span>
<span class="k">for</span> <span class="p">(</span><span class="n">SSkeletalAnimationPlayData</span><span class="o">&amp;</span> <span class="n">playData</span> <span class="o">:</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">)</span>
<span class="p">{</span>
    <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">UMath</span><span class="o">::</span><span class="n">IsWithin</span><span class="p">(</span><span class="n">playData</span><span class="p">.</span><span class="n">AssetReferenceIndex</span><span class="p">,</span> <span class="mi">0u</span><span class="p">,</span> <span class="n">STATIC_U32</span><span class="p">(</span><span class="n">component</span><span class="o">-&gt;</span><span class="n">AssetReferences</span><span class="p">.</span><span class="n">size</span><span class="p">())))</span>
      <span class="k">continue</span><span class="p">;</span>
    
    <span class="k">const</span> <span class="n">U32</span> <span class="n">index</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">AssetReferences</span><span class="p">[</span><span class="n">playData</span><span class="p">.</span><span class="n">AssetReferenceIndex</span><span class="p">];</span>
    <span class="k">const</span> <span class="n">SSkeletalAnimationAsset</span><span class="o">*</span> <span class="n">animationAsset</span> <span class="o">=</span> <span class="n">assetRegistry</span><span class="o">-&gt;</span><span class="n">RequestAssetData</span><span class="o">&lt;</span><span class="n">SSkeletalAnimationAsset</span><span class="o">&gt;</span><span class="p">(</span><span class="n">index</span><span class="p">,</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">Owner</span><span class="p">.</span><span class="n">GUID</span><span class="p">);</span>
    <span class="k">if</span> <span class="p">(</span><span class="n">animationAsset</span> <span class="o">==</span> <span class="nb">nullptr</span><span class="p">)</span>
      <span class="k">continue</span><span class="p">;</span>
  
    <span class="p">...</span>
  
    <span class="n">playData</span><span class="p">.</span><span class="n">LocalPosedNodes</span> <span class="o">=</span> <span class="p">{};</span>
    <span class="n">ReadAnimationLocalPose</span><span class="p">(</span><span class="n">animationAsset</span><span class="p">,</span> <span class="n">meshAsset</span><span class="p">,</span> <span class="n">animationTime</span><span class="p">,</span> <span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">playData</span><span class="p">.</span><span class="n">LocalPosedNodes</span><span class="p">);</span>
<span class="p">}</span>
				
<span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">SSkeletalPosedNode</span><span class="o">&gt;</span> <span class="n">posedNodes</span> <span class="o">=</span> <span class="p">{};</span>
<span class="n">posedNodes</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">.</span><span class="n">size</span><span class="p">());</span>
				
<span class="c1">// Blend animations</span>
<span class="k">if</span> <span class="p">(</span><span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">1</span><span class="p">)</span>
<span class="p">{</span>
    <span class="c1">// TODO.NW: Barycentric interpolation for more than 2 animations</span>
  
    <span class="k">for</span> <span class="p">(</span><span class="n">U32</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">STATIC_U32</span><span class="p">(</span><span class="n">posedNodes</span><span class="p">.</span><span class="n">size</span><span class="p">());</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">const</span> <span class="n">SSkeletalPosedNode</span><span class="o">&amp;</span> <span class="n">posedNodeA</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">LocalPosedNodes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="k">const</span> <span class="n">SSkeletalPosedNode</span><span class="o">&amp;</span> <span class="n">posedNodeB</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">[</span><span class="mi">1</span><span class="p">].</span><span class="n">LocalPosedNodes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
    
        <span class="n">posedNodes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Name</span> <span class="o">=</span> <span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">Name</span><span class="p">;</span>
        <span class="n">posedNodes</span><span class="p">[</span><span class="n">i</span><span class="p">].</span><span class="n">LocalTransform</span> <span class="o">=</span> <span class="n">SMatrix</span><span class="o">::</span><span class="n">Interpolate</span><span class="p">(</span><span class="n">posedNodeA</span><span class="p">.</span><span class="n">LocalTransform</span><span class="p">,</span> <span class="n">posedNodeB</span><span class="p">.</span><span class="n">LocalTransform</span><span class="p">,</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">BlendValue</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
<span class="k">else</span> <span class="k">if</span> <span class="p">(</span><span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">posedNodes</span> <span class="o">=</span> <span class="n">component</span><span class="o">-&gt;</span><span class="n">PlayData</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">LocalPosedNodes</span><span class="p">;</span>
<span class="p">}</span>

<span class="c1">// Apply local pose and inverse bind transform</span>
<span class="n">SMatrix</span> <span class="n">root</span> <span class="o">=</span> <span class="n">SMatrix</span><span class="o">::</span><span class="n">Identity</span><span class="p">;</span>
<span class="n">root</span><span class="p">.</span><span class="n">SetScale</span><span class="p">(</span><span class="n">importScale</span><span class="p">);</span>
<span class="n">ApplyLocalPoseToHierarchy</span><span class="p">(</span><span class="n">meshAsset</span><span class="p">,</span> <span class="n">posedNodes</span><span class="p">,</span> <span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">Nodes</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span> <span class="n">root</span><span class="p">);</span>

<span class="n">component</span><span class="o">-&gt;</span><span class="n">Bones</span><span class="p">.</span><span class="n">resize</span><span class="p">(</span><span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">BindPoseBones</span><span class="p">.</span><span class="n">size</span><span class="p">(),</span> <span class="n">SMatrix</span><span class="o">::</span><span class="n">Identity</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="n">U32</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">STATIC_U32</span><span class="p">(</span><span class="n">posedNodes</span><span class="p">.</span><span class="n">size</span><span class="p">());</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">SSkeletalPosedNode</span><span class="o">&amp;</span> <span class="n">posedNode</span> <span class="o">=</span> <span class="n">posedNodes</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
	
    <span class="p">...</span>

    <span class="k">const</span> <span class="n">SSkeletalMeshBone</span><span class="o">&amp;</span> <span class="n">bone</span> <span class="o">=</span> <span class="n">meshAsset</span><span class="o">-&gt;</span><span class="n">BindPoseBones</span><span class="p">[</span><span class="n">boneIndex</span><span class="p">];</span>
    <span class="n">component</span><span class="o">-&gt;</span><span class="n">Bones</span><span class="p">[</span><span class="n">boneIndex</span><span class="p">]</span> <span class="o">=</span> <span class="n">bone</span><span class="p">.</span><span class="n">InverseBindPoseTransform</span> <span class="o">*</span> <span class="n">posedNode</span><span class="p">.</span><span class="n">GlobalTransform</span><span class="p">;</span>
<span class="p">}</span>

<span class="p">...</span>
</code></pre></div>  </div>

</details>

<p>For the first game project we’re aiming to at the very least implement IK using the <a href="https://www.researchgate.net/publication/220632147_FABRIK_A_fast_iterative_solver_for_the_Inverse_Kinematics_problem">FABRIK</a> solver, 
as well as drive audio through events triggered on specific keyframes in the animation, similar to how Unreal Engine does it with their <a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/animation-notifications-notifies?application_version=4.27">Animation Notifications</a>. 
I’m thinking that triggering these events is best handled when reading the local pose of running animations, as we interpolate position, rotation and scale between keyframes there anyway.</p>

<p>For the future we’d also like to support blend spaces and explore skeletal deformations, to say nothing about building an animation GUI (preferrably a graph utilizing our <a href="/visual-scripting/">visual scripting</a> system) that’s simple to work with.</p>]]></content><author><name></name></author><category term="havtorn" /><category term="havtorn" /><category term="animation" /><summary type="html"><![CDATA[We purposefully serialize our skeletal mesh animations down to our own data formats and run the math using those. Other solutions instead make a thin wrapper (or no wrapping at all) around imported assimp scenes, provided they are also working with assimp. I prefer our solution as it gives us more control in implementing more advanced features such as inverse kinematics now that we’ve split up the logic into clear chunks.]]></summary></entry><entry><title type="html">Render Based Picking</title><link href="https://wejdell.github.io/render-based-picking/" rel="alternate" type="text/html" title="Render Based Picking" /><published>2024-12-21T00:00:00+00:00</published><updated>2024-12-21T00:00:00+00:00</updated><id>https://wejdell.github.io/render-based-picking</id><content type="html" xml:base="https://wejdell.github.io/render-based-picking/"><![CDATA[<p><em>Picking</em>, in a game engine architecture context, is the term we use to describe the process of clicking on actors or entities in a scene to select, or manipulate them somehow. 
This is often done by shooting a trace or raycast into the scene from the mouse cursor when clicking, using the physics layer, and selecting the entity corresponding to whatever collision body that the ray intersects with, depending on the collision rules set up. 
Rather than using this approach, 
<!--excerpt-begin-->
Havtorn uses information stored in a separate render target to resolve its picking in the editor. By rendering the identifier of each entity onto the screen, our picking becomes pixel perfect and completely decoupled from the physics system. 
<!--excerpt-end--></p>

<p>One nice feature of an ECS architecture is the fact that you can represent an entity and reference all of its data using only an ID. There’s no need to get hold of a pointer to a large actor object, you can simply provide a single data type directly. 
We utilize this fact by extending the <a href="https://en.wikipedia.org/wiki/Deferred_shading">G-Buffer</a> with a separate target to store the ID in, per pixel. 64 bits per ID is enough for us so we make it a 64 bit texture, using the <code class="language-plaintext highlighter-rouge">DXGI_FORMAT_R32G32_UINT</code> format of DirectX. 
When rendering to the G-Buffer, we then just pass along the entity ID of every instance that gets rendered. To solve picking for transparent and invisible objects, I opted to represent them as world space billboard sprites. They are also drawn to the G-Buffer, only after the lighting pass.</p>

<div class="click-zoom">
    <label>
        <input type="checkbox" />
        <img class="centered" src="/assets/images/worldPositionSpriteWidgets.png" />
    </label>
</div>

<p>My thinking is that we can hopefully find a way to represent any entity in the game world like this, either with solid geometry directly or by using one of these sprites. There are definitely challenges in how to scale and layer them so they stay visible, and there are probably also specific cases where this 
solution breaks down or fails to accurately represent some type of entity, but I think it’s worth it in the end to reach the goal of clicking what you see in the viewport to select it. I always get incredibly frustrated when I accidentally click a fog cube instead of the thing I’m actually trying to select in 
the Unreal Editor because I’ve failed to toggle off translucent object selection.</p>

<p>It should be noted that this is only for use in editor time. Extending the G-Buffer is costly and we don’t want to deal with any editor functionality at all in a finished game, if we can help it. Separate render passes are chosen here depending on the play state of the game.</p>

<p>The debug view in the gif above is rendered using a simple formula, taking the average of the red and green channel (making up the complete 64 bit entity ID) for the blue, and then scaling the whole thing by some arbitrary amount to where you can tell most of the entities apart.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// FullscreenEditorData_PS.hlsl</span>
<span class="p">...</span>

<span class="n">Texture2D</span><span class="o">&lt;</span><span class="n">uint2</span><span class="o">&gt;</span> <span class="n">entityDataTexture</span> <span class="o">:</span> <span class="k">register</span><span class="p">(</span><span class="n">t0</span><span class="p">);</span>

<span class="n">PixelOutput</span> <span class="nf">main</span><span class="p">(</span><span class="n">VertexToPixel</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">PixelOutput</span> <span class="n">returnValue</span><span class="p">;</span>
    
    <span class="k">const</span> <span class="n">uint2</span> <span class="n">resource</span> <span class="o">=</span> <span class="n">entityDataTexture</span><span class="p">.</span><span class="n">Load</span><span class="p">(</span><span class="n">int3</span><span class="p">(</span><span class="n">input</span><span class="p">.</span><span class="n">UV</span><span class="p">.</span><span class="n">xy</span> <span class="o">*</span> <span class="n">Resolution</span><span class="p">,</span> <span class="mi">0</span><span class="p">));</span>
    <span class="n">returnValue</span><span class="p">.</span><span class="n">Color</span><span class="p">.</span><span class="n">rgb</span> <span class="o">=</span> <span class="n">float3</span><span class="p">(</span><span class="n">resource</span><span class="p">.</span><span class="n">x</span><span class="p">,</span> <span class="n">resource</span><span class="p">.</span><span class="n">y</span><span class="p">,</span> <span class="p">(</span><span class="n">resource</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">resource</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="o">*</span> <span class="mf">0.5</span><span class="n">f</span><span class="p">);</span>
    <span class="n">returnValue</span><span class="p">.</span><span class="n">Color</span><span class="p">.</span><span class="n">rgb</span> <span class="o">/=</span> <span class="mi">1000000000</span><span class="p">;</span>
    <span class="n">returnValue</span><span class="p">.</span><span class="n">Color</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">returnValue</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div>  </div>

</details>

<h2 id="dragging-entities-into-the-viewport">Dragging Entities into the Viewport</h2>

<div class="click-zoom">
    <label>
        <input type="checkbox" />
        <img class="centered" src="/assets/images/renderBasedDragging.gif" />
    </label>
</div>

<p>A similar approach can also be used to resolve where to put a preview entity you’re dragging in from the asset browser. Again, you can do this by checking collision with the physics system, but I think it’s entirely serviceable 
to render out the world position to another render target, and sample from that one on the CPU in the same way as with the entity IDs.</p>

<div class="click-zoom">
    <label>
        <input type="checkbox" />
        <img class="centered" src="/assets/images/worldPositionTexture.png" />
    </label>
</div>

<p>A key consideration here is how to deal with the geometry of the entity you’re dragging, otherwise it will jump around or travel towards the camera as the cursor position hovers over it. We solve this by just not rendering the 
preview entity to the world position render target, at least until you let go of the cursor and it becomes a proper entity in the scene. Where there is no geometry or sprite rendered (such as over the skybox), you can put it at a set distance from the camera along the vector separating the camera and the cursor.</p>]]></content><author><name></name></author><category term="havtorn" /><category term="havtorn" /><category term="editor" /><summary type="html"><![CDATA[Picking, in a game engine architecture context, is the term we use to describe the process of clicking on actors or entities in a scene to select, or manipulate them somehow. This is often done by shooting a trace or raycast into the scene from the mouse cursor when clicking, using the physics layer, and selecting the entity corresponding to whatever collision body that the ray intersects with, depending on the collision rules set up. Rather than using this approach, Havtorn uses information stored in a separate render target to resolve its picking in the editor. By rendering the identifier of each entity onto the screen, our picking becomes pixel perfect and completely decoupled from the physics system.]]></summary></entry><entry><title type="html">Game Project 8 - Besoket</title><link href="https://wejdell.github.io/game-project-8/" rel="alternate" type="text/html" title="Game Project 8 - Besoket" /><published>2021-06-18T00:00:00+00:00</published><updated>2021-06-18T00:00:00+00:00</updated><id>https://wejdell.github.io/game-project-8</id><content type="html" xml:base="https://wejdell.github.io/game-project-8/"><![CDATA[<center>
	<iframe src="https://www.youtube.com/embed/R9eKjTaD22Y?si=tvvROig8QxMoyu7q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</center>

<center>
<div class="iframe itchIframe">
	<iframe src="https://itch.io/embed/1109245" frameborder="0"><a href="https://the-game-assembly.itch.io/tga19-p8-besoket">TGA19 Game Project 8 - Besoket by The Game Assembly</a></iframe>
</div>
</center>

<p><br /></p>

<!--excerpt-begin-->
<center>
<i>You respond to a cry of help from your friend, locked in a strange Swedish cottage. Find a way to free your friend, solve the puzzles all while evading the unknown horrors of the house. You better be careful, or this act of kindness might be your last..</i>
</center>
<!--excerpt-end-->

<p><br /></p>

<p>Our exam project turned out fairly popular, with thousands of views on <a href="https://the-game-assembly.itch.io/tga19-p8-besoket">itch.io</a> and more than 2000 downloads! Please enjoy the jank if you feel like trying it.</p>

<p><br /></p>

<h2 id="specifications">Specifications</h2>

<ul>
  <li><strong>Genre</strong>:    Horror FPX</li>
  <li><strong>Duration</strong>: 9 Weeks Full-Time, 8h/day</li>
  <li><strong>Engine</strong>:   IronWrought Engine</li>
  <li><strong>Team</strong>:     SoftBlob, 16 people</li>
</ul>

<h2 id="my-contributions">My Contributions</h2>
<ul>
  <li><strong>Graphics</strong> (<a href="#spotlights">Spot Lights</a>, <a href="#camera">Camera Improvements</a>)</li>
  <li><strong>UI</strong>       (<a href="#vignette">UI Improvements</a>)</li>
  <li><strong>Audio</strong>    (<a href="#spatialAudio">3D Audio and Cone Based Occlusion</a>)</li>
  <li><strong>Voice Over &amp; Sound Design</strong></li>
</ul>

<h2 id="details">Details</h2>
<h4 id="spot-lights"><a id="spotlights">Spot Lights</a></h4>

<p>While missing shadowmapping for them, I added support for spotlights imported from Unity (which we used as a level editor during the final school projects) into the engine. They really contributed to the ambience
of the basement levels.</p>

<div class="img-row">
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/project_8-spotlight-import.gif" />
        </label>
      </div>
  </div>
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/besoket-1.png" />
        </label>
      </div>
  </div>
</div>

<h4 id="camera-improvements"><a id="camera">Camera Improvements</a></h4>

<p>Our reference game this time was Soma, and pulling from its’ ancestor Amnesia, we knew we wanted to get a little bit of sway into
the camera movement. We didn’t use very violent shakes in the end but the gentle sway simulating the character breathing is felt
ambiently and helps immersing the player.</p>

<div class="img-row">
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/project_8-subtle-bob.gif" />
        </label>
      </div>
  </div>
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/project_8-crazy-shake.gif" />
        </label>
      </div>
  </div>
</div>

<p>We were also aiming to play more with the main menu than we had before. As part of that, we framed a few different assets nicely
and let them represent different submenus. Below is an in-progress shot of the camera moving between a few such points during 
development.</p>

<div class="click-zoom">
<label>
    <input type="checkbox" />
    <img class="centered" src="/assets/images/project_8-menu-interpolation.gif" />
</label>
</div>

<h4 id="ui-improvements"><a id="vignette">UI Improvements</a></h4>

<p>Also as part of invoking the visual effects of Soma and Amnesia, I built some controls for the artists to tweak the vignette 
overlay which was later animated when killed by the monster in the game.</p>

<div class="img-row">
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/project_8-post-processing.gif" />
        </label>
      </div>
  </div>
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/project-8.gif" />
        </label>
      </div>
  </div>
</div>

<h4 id="3d-audio-and-cone-based-occlusion"><a id="spatialAudio">3D Audio and Cone Based Occlusion</a></h4>

<p>I extended the audio sources with basic spatialization in the final project. For occlusion, I introduced separate angles to 
the prefab in Unity so we could simulate basic occlusion through walls. The attenuation distances (unattenuated to fully attenuated)
and cone angles were then provided to FMOD on initialization.</p>]]></content><author><name></name></author><category term="education" /><category term="project" /><category term="rendering" /><category term="ui" /><category term="audio" /><summary type="html"><![CDATA[TGA19 Game Project 8 - Besoket by The Game Assembly You respond to a cry of help from your friend, locked in a strange Swedish cottage. Find a way to free your friend, solve the puzzles all while evading the unknown horrors of the house. You better be careful, or this act of kindness might be your last..]]></summary></entry><entry><title type="html">Game Project 7 - I Am</title><link href="https://wejdell.github.io/game-project-7/" rel="alternate" type="text/html" title="Game Project 7 - I Am" /><published>2021-04-06T00:00:00+00:00</published><updated>2021-04-06T00:00:00+00:00</updated><id>https://wejdell.github.io/game-project-7</id><content type="html" xml:base="https://wejdell.github.io/game-project-7/"><![CDATA[<center>
	<iframe width="720" height="405" src="https://www.youtube.com/embed/pdI-LQwd7ik?si=xx19s2vtRzH7JHtG" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen=""></iframe>
</center>

<center>
<div class="iframe itchIframe">
	<iframe src="https://itch.io/embed/1108064" width="720" height="167" frameborder="0"><a href="https://the-game-assembly.itch.io/tga19-p7-iam">TGA19 Game Project 7 - I AM by The Game Assembly</a></iframe>
</div>
</center>

<p><br /></p>

<!--excerpt-begin-->
<center>
<i>You took something you shouldn't have! Use the gravity glove you stole to overcome obstacles and dangerous robots as you seek your freedom.</i>
</center>
<!--excerpt-end-->

<p><br /></p>

<h2 id="specifications">Specifications</h2>

<ul>
  <li><strong>Genre</strong>:    3D FPX</li>
  <li><strong>Duration</strong>: 16 Weeks Part-Time, 4h/day</li>
  <li><strong>Engine</strong>:   IronWrought Engine</li>
  <li><strong>Team</strong>:     SoftBlob, 16 people</li>
</ul>

<h2 id="my-contributions">My Contributions</h2>
<ul>
  <li><strong>Graphics</strong> (<a href="#hdr">HDR</a>, <a href="#volumetric_lighting">Volumetric Lighting</a>, <a href="#material">Material Pipeline</a>, <a href="#deferred_decals">Deferred Decals</a>, <a href="#vfx_editor">VFX Editor</a>, <a href="#vertex_paint">Vertex Painting</a>)</li>
  <li><strong>UI</strong>       (<a href="#animated_sprites">Animated sprites</a>, <a href="#ui_architecture">Extended Architecture</a>)</li>
  <li><strong>Audio</strong>    (<a href="#voice_line_event">Voice Line Event and Category Nodes</a>)</li>
  <li><strong>Voice Over</strong>
    <h2 id="details">Details</h2>
    <h4 id="hdr"><a id="hdr">HDR</a></h4>
  </li>
</ul>

<p>Starting on project 7 we moved to a deferred rendering pipeline instead of forward rendering. I took the chance to implement HDR lighting at the same time, using the filmic tonmapping equation of <a href="https://www.gdcvault.com/play/1012351/Uncharted-2-HDR">Uncharted 2</a>.
This vastly enhanced the quality of our emissive lights and improved our control over the lighting over all.</p>

<h4 id="volumetric-lighting"><a id="volumetric_lighting">Volumetric Lighting</a></h4>
<p>Please see the <a href="/education/specialization/">post</a> I made about my specialization project at The Game Assembly.</p>

<h4 id="material-pipeline"><a id="material">Material Pipeline</a></h4>
<p>For project 7, it was important that we could have several sets of materials for any model. I worked closely together with the graphical artists to develop a system which takes care of all material texture memory, using reference counting as well as applying error textures where applicable. 
Material names where applied in Maya and subsequently detected during import of the FBX file. The Material Handler used a single material name to load the relevant textures. We made use of a material suffix to detect which models were to be rendered with alpha blending at a later pass. 
This Material Handler was also used to handle decal textures and materials used for vertex painting.</p>

<h4 id="deferred-decals"><a id="deferred_decals">Deferred Decals</a></h4>
<p>Please see the <a href="https://nicolas-risberg.github.io/2021-03-01/deferred-decals.html">post</a> I made about deferred decals in the highlights.</p>

<h4 id="vfx-editor"><a id="vfx_editor">VFX Editor</a></h4>
<p>Please see the <a href="https://nicolas-risberg.github.io/2021-03-30/vfx-editor.html">post</a> I made about the VFX editor in the highlights.</p>

<h4 id="vertex-painting"><a id="vertex_paint">Vertex Painting</a></h4>
<p>I worked with <a href="http://axelsavage.com">Axel Savage</a> on implementing vertex painting from Unity. On the Unity side we used <a href="https://unity3d.com/unity/features/worldbuilding/polybrush">Polybrush</a> to paint data on the meshes, and the per-vertex data was
exported to our engine. The order of indices in the index buffer turned out to not be the same when the data came from Unity, so we resorted to making a hash function for the vertex positions in order to correctly map position to color using the order we loaded from the FBX file.</p>

<p>In the pixel shader, all values associated with the PBR materials were interpolated using the color values as weights.</p>

<div class="click-zoom">
<label>
    <input type="checkbox" />
    <img class="centered" src="/assets/images/vertexPaint.gif" />
</label>
</div>

<h4 id="animated-sprites"><a id="animated_sprites">Animated Sprites</a></h4>
<p>I extended the sprite rendering I developed in project 6 to support spritesheet animations, breathing life into the crosshair especially. Animations were relatively easy to define in the json document already containing the sprite information, and the logic was general enough to make
simple function calls like <code class="language-plaintext highlighter-rouge">mySprite-&gt;PlayAnimation(anAnimationIndex, doRepeat, doReverse);</code> behave as expected. Animations were given support for transitioning to different animations depending on whether they played in reverse or not. They could also be given a rotational speed
about the \(z\)-axis, as seen in <a href="/assets/images/project-7.gif">this gif</a>.</p>

<p>The animation logic consisted of counting up the index of a collection of all relevant UV-coordinates, all laid out in sequence. What defines an animation is basically its offset from the beginning of the collection and its total number of frames.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c# highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">...</span>
<span class="s">"Animations"</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
    <span class="s">"Name"</span><span class="p">:</span> <span class="s">"Shoot"</span><span class="p">,</span>
    <span class="s">"FrameWidth"</span><span class="p">:</span> <span class="m">240.0</span><span class="p">,</span>
    <span class="s">"FrameHeight"</span><span class="p">:</span> <span class="m">240.0</span><span class="p">,</span>
    <span class="s">"VerticalStartingPos"</span><span class="p">:</span> <span class="m">0.0</span><span class="p">,</span>
    <span class="s">"FrameOffset"</span><span class="p">:</span> <span class="m">0</span><span class="p">,</span>
    <span class="s">"NumberOfFrames"</span><span class="p">:</span> <span class="m">7</span><span class="p">,</span>
    <span class="s">"FramesPerSecond"</span><span class="p">:</span> <span class="m">60</span><span class="p">,</span>
    <span class="s">"RotationSpeedPerSecond"</span><span class="p">:</span> <span class="m">720.0</span><span class="p">,</span>
    <span class="s">"ShouldLoop"</span><span class="p">:</span> <span class="k">false</span><span class="p">,</span>
    <span class="s">"TransitionIndex"</span><span class="p">:</span> <span class="m">2</span><span class="p">,</span>
    <span class="s">"ReverseTransitionIndex"</span><span class="p">:</span> <span class="m">1</span>
<span class="p">},</span>
<span class="p">...</span>
<span class="p">]</span>
</code></pre></div>  </div>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">CSpriteInstance</span><span class="o">::</span><span class="n">Update</span><span class="p">()</span>
<span class="p">{</span>
	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">myShouldAnimate</span><span class="p">)</span>
		<span class="k">return</span><span class="p">;</span>

	<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">myShouldReverseAnimation</span><span class="p">)</span>
		<span class="k">this</span><span class="o">-&gt;</span><span class="n">Rotate</span><span class="p">(</span><span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myRotationSpeedInSeconds</span> <span class="o">*</span> <span class="n">CTimer</span><span class="o">::</span><span class="n">Dt</span><span class="p">());</span>
	<span class="k">else</span> 
		<span class="k">this</span><span class="o">-&gt;</span><span class="n">Rotate</span><span class="p">(</span><span class="o">-</span><span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myRotationSpeedInSeconds</span> <span class="o">*</span> <span class="n">CTimer</span><span class="o">::</span><span class="n">Dt</span><span class="p">());</span>

	<span class="k">if</span> <span class="p">((</span><span class="n">myAnimationTimer</span> <span class="o">+=</span> <span class="n">CTimer</span><span class="o">::</span><span class="n">Dt</span><span class="p">())</span> <span class="o">&gt;</span> <span class="p">(</span><span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myFramesPerSecond</span><span class="p">))</span>
	<span class="p">{</span>
		<span class="n">myAnimationTimer</span> <span class="o">-=</span> <span class="p">(</span><span class="mf">1.0</span><span class="n">f</span> <span class="o">/</span> <span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myFramesPerSecond</span><span class="p">);</span> 

		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">myShouldReverseAnimation</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">myCurrentAnimationFrame</span><span class="o">++</span><span class="p">;</span>
			<span class="k">if</span> <span class="p">(</span><span class="n">myCurrentAnimationFrame</span> <span class="o">&gt;</span> <span class="p">(</span><span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myNumberOfFrames</span> <span class="o">+</span> <span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myFramesOffset</span> <span class="o">-</span> <span class="mi">1</span><span class="p">))</span>
			<span class="p">{</span>
				<span class="n">myShouldAnimate</span> <span class="o">=</span> <span class="n">myShouldLoopAnimation</span><span class="p">;</span>
				<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">myShouldAnimate</span><span class="p">)</span>
				<span class="p">{</span>
					<span class="n">PlayAnimationUsingInternalData</span><span class="p">(</span><span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myTransitionToIndex</span><span class="p">);</span>
					<span class="k">return</span><span class="p">;</span>
				<span class="p">}</span>

				<span class="n">myCurrentAnimationFrame</span> <span class="o">=</span> <span class="n">myAnimationData</span><span class="p">[</span><span class="n">myCurrentAnimationIndex</span><span class="p">].</span><span class="n">myFramesOffset</span><span class="p">;</span>
			<span class="p">}</span>
		<span class="p">}</span>
		<span class="k">else</span> 
		<span class="p">{</span>
			<span class="c1">// ...</span>
		<span class="p">}</span>

		<span class="k">this</span><span class="o">-&gt;</span><span class="n">SetUVRect</span><span class="p">(</span><span class="n">myAnimationFrames</span><span class="p">[</span><span class="n">myCurrentAnimationFrame</span><span class="p">]);</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<h4 id="extended-ui-architecture"><a id="ui_architecture">Extended UI Architecture</a></h4>
<p>The UI architecture was extended to use “widgets” in the main menu. Rather than a state change on a button press, a child canvas of the main canvas is activated, each potentially holding its own buttons and sprites. 
We added a canvas wide offset to such children to properly set the render order of image elements. We limited ourselves to let the button logic only really apply to the base canvas though, so button presses in the widgets
were only really relevant to the base canvas. Since we only used this system in the main menu we were not negatively affected by this limitation, but to really make use of widgets we would have to improve it.</p>

<h4 id="voice-line-event-and-sfx-category-nodes"><a id="voice_line_event">Voice Line Event and SFX Category Nodes</a></h4>
<p>Some of the other team members, especially <a href="http://haqvinbager.github.io">Haqvin Bager</a> built a node editor using <a href="https://github.com/ocornut/imgui">ImGui</a> for this project.
I contributed to this editor by making nodes that would play sounds, for playing voice lines on event triggers in the levels as well as playing randomized SFX and reactionary voice lines based on certain categories. 
Categories included <code class="language-plaintext highlighter-rouge">RobotDeath</code>, <code class="language-plaintext highlighter-rouge">PlayerStepAirVent</code> and <code class="language-plaintext highlighter-rouge">ResearcherPickUpExplosivesReaction</code> among others.
The audio manager would load lists of clips based on each category on start up, and randomize (cycling through the whole list before replaying) what clip to play when receiving a message from the node editor.</p>]]></content><author><name></name></author><category term="education" /><category term="project" /><category term="deferred rendering" /><category term="rendering" /><category term="ui" /><category term="vfx" /><category term="audio" /><summary type="html"><![CDATA[TGA19 Game Project 7 - I AM by The Game Assembly You took something you shouldn't have! Use the gravity glove you stole to overcome obstacles and dangerous robots as you seek your freedom.]]></summary></entry><entry><title type="html">Specialization: Volumetric Lighting in DirectX11</title><link href="https://wejdell.github.io/specialization/" rel="alternate" type="text/html" title="Specialization: Volumetric Lighting in DirectX11" /><published>2021-04-06T00:00:00+00:00</published><updated>2021-04-06T00:00:00+00:00</updated><id>https://wejdell.github.io/specialization</id><content type="html" xml:base="https://wejdell.github.io/specialization/"><![CDATA[<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<script>window.jQuery || document.write('<script src="_/js/libs/jquery-1.9.1.min.js"><\/script>')</script>

<h2 id="introduction">Introduction</h2>

<p>Concurrently with our seventh game project at The Game Assembly, we had the opportunity to specialize; to plan an independent foray into a subject which we wanted to explore further.</p>

<!--excerpt-begin-->
<p>For my specialization I chose to try to implement volumetric lighting in our custom game engine. My main goal was to implement a volumetric directional light, and to optimize it best I could.
 Would there be time left, my secondary goal was to extend this implementation to point lights, spot lights and what I called “box” lights.
<!--excerpt-end-->
We had five weeks half-time to do this, as well as setting up our personal portfolio. I planned for letting my secondary goal slide, and to focus on my main goal.</p>

<p>I based my implementation on a talk at Digital Dragons by Benjamin <a href="https://www.slideshare.net/BenjaminGlatzel/volumetric-lighting-for-many-lights-in-lords-of-the-fallen">Glatzel</a> for Deck 13’s Lords of The Fallen.</p>

<p>After loading the Crytek Sponza scene using our model loading pipeline, I set out to create a first working version of a volumetric directional light.
Shadowmapping was ready for the directional light so I started with ray marching in the fragment shader of a separate light pass.</p>

<h2 id="implementation">Implementation</h2>

<p>The current fragment’s world position is extracted from the depth map. Then the world space camera position and fragment position are transformed into light view space and the ray marching direction is calculated from these.
An upper limit on the ray marched distance is set using this difference prior to normalization, and the size of each ray marching step is determined using a tweakable number of samples.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PixelOutput</span> <span class="nf">main</span><span class="p">(</span><span class="n">VertexToPixel</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">PixelOutput</span> <span class="n">output</span><span class="p">;</span>
    
    <span class="n">float</span> <span class="n">raymarchDistanceLimit</span> <span class="o">=</span> <span class="mi">999</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>

    <span class="kt">float3</span> <span class="n">worldPosition</span> <span class="o">=</span> <span class="n">PixelShader_WorldPosition</span><span class="p">(</span><span class="n">input</span><span class="p">.</span><span class="n">myUV</span><span class="p">).</span><span class="n">rgb</span><span class="p">;</span>
    <span class="kt">float3</span> <span class="n">camPosition</span> <span class="o">=</span> <span class="n">cameraPosition</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>
    
    <span class="n">worldPosition</span> <span class="o">-=</span> <span class="n">directionalLightPosition</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>
    <span class="kt">float3</span> <span class="n">positionLightVS</span> <span class="o">=</span> <span class="nb">mul</span><span class="p">(</span><span class="n">toDirectionalLightView</span><span class="p">,</span> <span class="n">worldPosition</span><span class="p">);</span>
   
    <span class="n">camPosition</span> <span class="o">-=</span> <span class="n">directionalLightPosition</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>
    <span class="kt">float3</span> <span class="n">cameraPositionLightVS</span> <span class="o">=</span> <span class="nb">mul</span><span class="p">(</span><span class="n">toDirectionalLightView</span><span class="p">,</span> <span class="n">camPosition</span><span class="p">);</span>

    <span class="kt">float4</span> <span class="n">invViewDirLightVS</span> <span class="o">=</span> <span class="kt">float4</span><span class="p">(</span><span class="nb">normalize</span><span class="p">(</span><span class="n">cameraPositionLightVS</span><span class="p">.</span><span class="n">xyz</span> <span class="o">-</span> <span class="n">positionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">),</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">);</span>
    <span class="n">float</span> <span class="n">raymarchDistance</span> <span class="o">=</span> <span class="nb">clamp</span><span class="p">(</span><span class="nb">length</span><span class="p">(</span><span class="n">cameraPositionLightVS</span><span class="p">.</span><span class="n">xyz</span> <span class="o">-</span> <span class="n">positionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">),</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="n">raymarchDistanceLimit</span><span class="p">);</span>

    <span class="n">float</span> <span class="n">stepSize</span> <span class="o">=</span> <span class="n">raymarchDistance</span> <span class="o">*</span> <span class="n">numberOfSamplesReciprocal</span><span class="p">;</span>

    <span class="kt">float3</span> <span class="n">rayPositionLightVS</span> <span class="o">=</span> <span class="n">positionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>
    
    <span class="c1">// The total light contribution accumulated along the ray</span>
    <span class="kt">float3</span> <span class="n">VLI</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>

    <span class="p">[</span><span class="nb">loop</span><span class="p">]</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">float</span> <span class="n">l</span> <span class="o">=</span> <span class="n">raymarchDistance</span><span class="p">;</span> <span class="n">l</span> <span class="o">&gt;</span> <span class="n">stepSize</span><span class="p">;</span> <span class="n">l</span> <span class="o">-=</span> <span class="n">stepSize</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ExecuteRaymarching</span><span class="p">(</span><span class="n">rayPositionLightVS</span><span class="p">,</span> <span class="n">invViewDirLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span> <span class="n">stepSize</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">VLI</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="n">output</span><span class="p">.</span><span class="n">myColor</span><span class="p">.</span><span class="n">rgb</span> <span class="o">=</span> <span class="n">directionalLightColor</span><span class="p">.</span><span class="n">rgb</span> <span class="o">*</span> <span class="n">VLI</span><span class="p">;</span>
    <span class="n">output</span><span class="p">.</span><span class="n">myColor</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">output</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>In the <code class="language-plaintext highlighter-rouge">ExecuteRaymarching</code> function, the ray position is incremented towards the camera position and the accumulated light contribution is calculated using the in-scattering term of the 
<a href="https://en.wikipedia.org/wiki/Radiative_transfer">Radiative Transfer Equation</a>, ignoring multiple scattering. 
In the directional light case, there is no world space attenuation calculated here, but the source power of the light and the scattering probability can be tweaked outside of the shader.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">ExecuteRaymarching</span><span class="p">(</span><span class="k">inout</span> <span class="kt">float3</span> <span class="n">rayPositionLightVS</span><span class="p">,</span> <span class="kt">float3</span> <span class="n">invViewDirLightVS</span><span class="p">,</span> <span class="n">float</span> <span class="n">stepSize</span><span class="p">,</span> <span class="n">float</span> <span class="n">l</span><span class="p">,</span> <span class="k">inout</span> <span class="kt">float3</span> <span class="n">VLI</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">rayPositionLightVS</span><span class="p">.</span><span class="n">xyz</span> <span class="o">+=</span> <span class="n">stepSize</span> <span class="o">*</span> <span class="n">invViewDirLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>

    <span class="kt">float3</span> <span class="n">visibilityTerm</span> <span class="o">=</span> <span class="n">ShadowFactor</span><span class="p">(</span><span class="n">rayPositionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">).</span><span class="n">xxx</span><span class="p">;</span>
    
    <span class="c1">// Distance to the current position on the ray in light view space</span>
    <span class="n">float</span> <span class="n">d</span> <span class="o">=</span> <span class="nb">length</span><span class="p">(</span><span class="n">rayPositionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">);</span>
    <span class="n">float</span> <span class="n">dRcp</span> <span class="o">=</span> <span class="nb">rcp</span><span class="p">(</span><span class="n">d</span><span class="p">);</span> <span class="c1">// reciprocal</span>
    
    <span class="kt">float3</span> <span class="n">intensity</span> <span class="o">=</span> <span class="n">scatteringProbability</span> 
        <span class="o">*</span> <span class="p">(</span><span class="n">visibilityTerm</span> <span class="o">*</span> <span class="p">(</span><span class="n">lightPower</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">25</span> <span class="o">*</span> <span class="n">PI_RCP</span><span class="p">)</span> <span class="o">*</span> <span class="n">dRcp</span> <span class="o">*</span> <span class="n">dRcp</span><span class="p">)</span> 
        <span class="o">*</span> <span class="nb">exp</span><span class="p">(</span><span class="o">-</span><span class="n">d</span> <span class="o">*</span> <span class="n">scatteringProbability</span><span class="p">)</span> 
        <span class="o">*</span> <span class="nb">exp</span><span class="p">(</span><span class="o">-</span><span class="n">l</span> <span class="o">*</span> <span class="n">scatteringProbability</span><span class="p">)</span> 
        <span class="o">*</span> <span class="n">stepSize</span><span class="p">;</span>
    
    <span class="n">VLI</span> <span class="o">+=</span> <span class="n">intensity</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>Without any optimizations, this render pass is prohibitively expensive, requiring between 64 and 128 samples per fragment on a full resolution target to look serviceable.</p>

<div class="click-zoom">
<label>
    <input type="checkbox" />
    <img class="centered" src="/assets/images/shafts.jpg" />
</label>
</div>

<h2 id="optimization">Optimization</h2>

<p><em>The two primary optimizations I implemented were rendering volumetrics to a half-resolution accumulation buffer, and interleaved sampling (see <a href="https://www.slideshare.net/BenjaminGlatzel/volumetric-lighting-for-many-lights-in-lords-of-the-fallen">Glatzel</a> slide 42).
Rendering to a half-resolution target meant having to blur the accumulation buffer in order to hide the fact. Together with interleaved sampling, the number of samples required per fragment was minimized to the point that performance increased in the end, however.</em></p>

<p>Before executing the ray marching, an offset to the starting position is calculated based on an 8 by 8 grid and the number of samples is reduced, taking advantage of the fact that the intensity differs by small amounts between neighboring fragments.
This reduces the number of necessary samples to as low as 16 samples.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">PixelOutput</span> <span class="nf">main</span><span class="p">(</span><span class="n">VertexToPixel</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
    <span class="p">[...]</span>

    <span class="n">float</span> <span class="n">stepSize</span> <span class="o">=</span> <span class="n">raymarchDistance</span> <span class="o">*</span> <span class="n">numberOfSamplesReciprocal</span><span class="p">;</span>
    
    <span class="c1">// Input position offset by 0.5f from center of fragment</span>
    <span class="kt">float2</span> <span class="n">interleavedPosition</span> <span class="o">=</span> <span class="nb">fmod</span><span class="p">(</span><span class="n">input</span><span class="p">.</span><span class="n">myPosition</span><span class="p">.</span><span class="n">xy</span> <span class="o">-</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span><span class="p">,</span> <span class="n">INTERLEAVED_GRID_SIZE</span><span class="p">);</span>

    <span class="n">float</span> <span class="n">index</span> <span class="o">=</span> <span class="p">(</span><span class="nb">floor</span><span class="p">(</span><span class="n">interleavedPosition</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="o">*</span> <span class="n">INTERLEAVED_GRID_SIZE</span> <span class="o">+</span> <span class="nb">floor</span><span class="p">(</span><span class="n">interleavedPosition</span><span class="p">.</span><span class="n">x</span><span class="p">));</span>

    <span class="c1">// lightVolumetricRandomRayIndices contains the values 0..63 in a randomized order</span>
    <span class="n">float</span> <span class="n">rayStartOffset</span> <span class="o">=</span> <span class="n">lightVolumetricRandomRayIndices</span><span class="p">[</span><span class="n">index</span><span class="p">]</span> <span class="o">*</span> <span class="p">(</span><span class="n">stepSize</span> <span class="o">*</span> <span class="n">INTERLEAVED_GRID_SIZE_SQR_RCP</span><span class="p">);</span> 
    
    <span class="kt">float3</span> <span class="n">rayPositionLightVS</span> <span class="o">=</span> <span class="n">rayStartOffset</span> <span class="o">*</span> <span class="n">invViewDirLightVS</span><span class="p">.</span><span class="n">xyz</span> <span class="o">+</span> <span class="n">positionLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">;</span>

    <span class="kt">float3</span> <span class="n">VLI</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    
    <span class="p">[</span><span class="nb">loop</span><span class="p">]</span>
    <span class="k">for</span> <span class="p">(</span><span class="n">float</span> <span class="n">l</span> <span class="o">=</span> <span class="n">raymarchDistance</span><span class="p">;</span> <span class="n">l</span> <span class="o">&gt;</span> <span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span> <span class="o">*</span> <span class="n">stepSize</span><span class="p">;</span> <span class="n">l</span> <span class="o">-=</span> <span class="n">stepSize</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="n">ExecuteRaymarching</span><span class="p">(</span><span class="n">rayPositionLightVS</span><span class="p">,</span> <span class="n">invViewDirLightVS</span><span class="p">.</span><span class="n">xyz</span><span class="p">,</span> <span class="n">stepSize</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">VLI</span><span class="p">);</span>
    <span class="p">}</span>
    
    <span class="p">[...]</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>After rendering to the accumulation buffer a bilateral Gaussian blur is applied, separated in horizontal and vertical passes. The advantage of a bilateral blur is that it is edge preserving, contrary to a regular Gaussian blur.</p>

<div class="img-row">
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/gaussian_blur.png" />
        </label>
      </div>
  </div>
  <div class="img-column">
        <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/bilateral_blur.jpg" />
        </label>
      </div>
  </div>
</div>

<p>The bilateral passes will smear the inside of the light volumes while preserving the edges.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Horizontal</span>
<span class="n">PixelOutput</span> <span class="nf">main</span><span class="p">(</span><span class="n">VertexToPixel</span> <span class="n">input</span><span class="p">)</span>
<span class="p">{</span>
    <span class="n">PixelOutput</span> <span class="n">returnValue</span><span class="p">;</span>
	
    <span class="n">float</span> <span class="n">texelSize</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span> <span class="o">/</span> <span class="p">(</span><span class="n">myResolution</span><span class="p">.</span><span class="n">x</span> <span class="o">/</span> <span class="mi">8</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">);</span>
    <span class="kt">float3</span> <span class="n">blurColor</span> <span class="o">=</span> <span class="kt">float3</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">);</span>
    <span class="n">float</span> <span class="n">normalizationFactor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    <span class="n">float</span> <span class="n">bZ</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span> <span class="o">/</span> <span class="n">normpdf</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">,</span> <span class="n">SIGMA</span><span class="p">);</span>
    <span class="n">float</span> <span class="n">colorFactor</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    <span class="kt">float3</span> <span class="n">originalPixelValue</span> <span class="o">=</span> <span class="n">fullscreenTexture1</span><span class="p">.</span><span class="n">Sample</span><span class="p">(</span><span class="n">defaultSampler</span><span class="p">,</span> <span class="n">input</span><span class="p">.</span><span class="n">myUV</span><span class="p">.</span><span class="n">xy</span><span class="p">).</span><span class="n">rgb</span><span class="p">;</span>
    
    <span class="kt">unsigned</span> <span class="n">int</span> <span class="n">kernelSize</span> <span class="o">=</span> <span class="mi">5</span><span class="p">;</span>
    <span class="n">float</span> <span class="n">start</span> <span class="o">=</span> <span class="p">(((</span><span class="n">float</span><span class="p">)</span> <span class="p">(</span><span class="n">kernelSize</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">)</span> <span class="o">/</span> <span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">)</span> <span class="o">*</span> <span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    
    <span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="n">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">kernelSize</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="kt">float2</span> <span class="n">uv</span> <span class="o">=</span> <span class="n">input</span><span class="p">.</span><span class="n">myUV</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="kt">float2</span><span class="p">(</span><span class="n">texelSize</span> <span class="o">*</span> <span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="p">(</span><span class="n">float</span><span class="p">)</span> <span class="n">i</span><span class="p">),</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">);</span>
        <span class="kt">float3</span> <span class="n">resource</span> <span class="o">=</span> <span class="n">fullscreenTexture1</span><span class="p">.</span><span class="n">Sample</span><span class="p">(</span><span class="n">defaultSampler</span><span class="p">,</span> <span class="n">uv</span><span class="p">).</span><span class="n">rgb</span><span class="p">;</span>
        <span class="n">colorFactor</span> <span class="o">=</span> <span class="n">normpdf3</span><span class="p">(</span><span class="n">resource</span> <span class="o">-</span> <span class="n">originalPixelValue</span><span class="p">,</span> <span class="n">SIGMA</span><span class="p">)</span> <span class="o">*</span> <span class="n">bZ</span> <span class="o">*</span> <span class="n">gaussianKernel5</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
        <span class="n">normalizationFactor</span> <span class="o">+=</span> <span class="n">colorFactor</span><span class="p">;</span>
        <span class="n">blurColor</span> <span class="o">+=</span> <span class="n">resource</span> <span class="o">*</span> <span class="n">colorFactor</span><span class="p">;</span>
    <span class="p">}</span>
	
    <span class="n">returnValue</span><span class="p">.</span><span class="n">myColor</span><span class="p">.</span><span class="n">rgb</span> <span class="o">=</span> <span class="n">blurColor</span> <span class="o">/</span> <span class="n">normalizationFactor</span><span class="p">;</span>
    <span class="n">returnValue</span><span class="p">.</span><span class="n">myColor</span><span class="p">.</span><span class="n">a</span> <span class="o">=</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    <span class="k">return</span> <span class="n">returnValue</span><span class="p">;</span> 
<span class="p">};</span>
</code></pre></div>  </div>

</details>

<p>If the <code class="language-plaintext highlighter-rouge">colorFactor</code> variable is not weighted by an appropriate measure (such as depth) the result may look noisy or pixelated around the edges of the volume, especially for hard-to-define fading edges where attenuation takes place. This is not done above, as I could not produce satisfying results.</p>

<h2 id="honorable-mentions">Honorable Mentions</h2>

<p>Having a fairly quick initial setup, I tried accomplishing my secondary goal prior to optimizing. Point lights were implemented but had utilized no shadowmapping, so I started with ray marching from the point lights and created two other light types from scratch hoping I would have the time to realize shadow mapping for all of them.</p>

<p>The other two light types were spot lights and what I called “box” lights. Box because they differ from the usual rectangle or area lights in that they have no angular falloff at the edges, essentially working as localized directional lights.</p>

<p>All the lights were provided geometry; a cube for point lights, a pyramid shape for spots and a cuboid for the box lights. They all functioned as light sources, but while I solved the actual volumetrics for box and point lights, I ran out of time before I could accurately provide shadow mapping for either of them.
This meant the visibility function could not be evaluated, rendering them virtually unusable for volumetric lighting.</p>

<div class="img-row">
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/point_light.jpg" />
        </label>
      </div>
  </div>
  <div class="img-column">
        <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/box_light.jpg" />
        </label>
      </div>
  </div>
</div>

<p>I did explore different phase functions however, using the <a href="https://www.astro.umd.edu/~jph/HG_note.pdf">Henyey-Greenstein</a> phase function as the default on the box light, with a tweakable <code class="language-plaintext highlighter-rouge">g</code>-value (<code class="language-plaintext highlighter-rouge">g = 0</code>, isotropic scattering above).</p>

<p><em>Note the noisy edges of the directional light in the right picture above, as well as at the ends of the box light itself.</em></p>

<h2 id="reflections">Reflections</h2>

<p>All in all I am satisfied in reaching my primary goal, which was to implement a volumetric directional light and to try and optimize it best I could.</p>

<p>I would not underestimate the time needed to create the other light types from scratch again though. 
I found myself working on the basic lighting of the box light and spot light more than I expected, and if given the choice again I would have focused on optimizing
for the directional light prior to working with new light types.</p>

<p>I ventured to try out all the steps of the optimization outlined by <strong><a href="https://www.slideshare.net/BenjaminGlatzel/volumetric-lighting-for-many-lights-in-lords-of-the-fallen">Glatzel</a></strong>,
including “nearest depth up-sampling” (see slide 38), to further make up for the fact that we are rendering to a half-resolution target. I could not make this work to satisfaction, but it would be greatly beneficial
in making the volumetric directional light look good I believe.</p>

<script src="/assets/js/jquery.photoset-grid.js"></script>

<script type="text/javascript">
    $('.photoset-grid-custom').photosetGrid({
    // Set the gutter between columns and rows
    gutter: '5px',
  
    // Wrap the images in links
    highresLinks: true,
  
    // Asign a common rel attribute
    rel: 'print-gallery',

    onInit: function(){},
    
    onComplete: function(){
        // Show the grid after it renders
        $('.photoset-grid-custom').attr('style', '');
    }
});
</script>]]></content><author><name></name></author><category term="education" /><category term="specialization" /><category term="volumetric lighting" /><category term="rendering" /><summary type="html"><![CDATA[Introduction Concurrently with our seventh game project at The Game Assembly, we had the opportunity to specialize; to plan an independent foray into a subject which we wanted to explore further. For my specialization I chose to try to implement volumetric lighting in our custom game engine. My main goal was to implement a volumetric directional light, and to optimize it best I could. Would there be time left, my secondary goal was to extend this implementation to point lights, spot lights and what I called “box” lights.]]></summary></entry><entry><title type="html">VFX Editor using ImGui</title><link href="https://wejdell.github.io/vfx-editor/" rel="alternate" type="text/html" title="VFX Editor using ImGui" /><published>2021-03-30T00:00:00+00:00</published><updated>2021-03-30T00:00:00+00:00</updated><id>https://wejdell.github.io/vfx-editor</id><content type="html" xml:base="https://wejdell.github.io/vfx-editor/"><![CDATA[<script src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>

<script>window.jQuery || document.write('<script src="_/js/libs/jquery-1.9.1.min.js"><\/script>')</script>

<h2 id="introduction">Introduction</h2>

<!--excerpt-begin-->
<p>For our seventh game project at The Game Assembly, I made a VFX editor using <a href="https://github.com/ocornut/imgui">ImGui</a> based on the VFX and particle systems I designed for the project prior.
 With ImGui it was easy and fast to get a graphical user interface running and rendering in our custom engine, and features like the color picker and curve editing were essential in the workflow designed for the graphical artists.
<!--excerpt-end--></p>

<p>This editor was made to provide the artists working with VFX a proper pipeline for doing so, as tweaking values in JSON files that they had been doing up to this point was inconvenient at best.
The systems were still initialized from JSON but did not have to be tweaked inside the files themselves.</p>

<h2 id="implementation">Implementation</h2>

<p>The VFX Editor works on a component called <code class="language-plaintext highlighter-rouge">VFXSystemComponent</code>. This component holds a list of data structures called <code class="language-plaintext highlighter-rouge">VFXEffect</code>s. 
Each effect holds data associated with any number of what we call VFX meshes and particle emitters, where a VFX mesh is simply some geometry using shaders to scroll textures across it. These meshes and particle emitters are rendered using alpha blending and with depth writing turned off in a late render pass.
The system is defined this way to allow a single game object with this component to store several collections of meshes and particle emitters in different configurations, and to activate any one of these collections as an “effect” at any time.</p>

<p>The window class itself holds the data necessary to tweak values in ImGui as well as the data necessary to write to JSON. All data structures have their own, unique <code class="language-plaintext highlighter-rouge">Serialize()</code> methods in order to conserve the specific format used before, rather than generalizing and having to rewrite the JSON import for the <code class="language-plaintext highlighter-rouge">VFXSystemComponent</code>.</p>

<p>The basic logic is very simple. An enum controls which information is shown in the window, and the enum is set by buttons within the window.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">IronWroughtImGui</span><span class="o">::</span><span class="n">CVFXEditorWindow</span><span class="o">::</span><span class="n">OnInspectorGUI</span><span class="p">()</span>
<span class="p">{</span>
	<span class="n">ImGui</span><span class="o">::</span><span class="n">Begin</span><span class="p">(</span><span class="n">Name</span><span class="p">(),</span> <span class="n">Open</span><span class="p">());</span>

	<span class="k">switch</span> <span class="p">(</span><span class="n">myCurrentMenu</span><span class="p">)</span>
	<span class="p">{</span>
	<span class="k">case</span> <span class="n">IronWroughtImGui</span><span class="o">::</span><span class="n">EVFXEditorMenu</span><span class="o">::</span><span class="n">MainMenu</span><span class="p">:</span>
		<span class="n">ShowMainMenu</span><span class="p">();</span>
		<span class="k">break</span><span class="p">;</span>
	<span class="k">case</span> <span class="n">IronWroughtImGui</span><span class="o">::</span><span class="n">EVFXEditorMenu</span><span class="o">::</span><span class="n">VFXMeshView</span><span class="p">:</span>
		<span class="n">ShowVFXMeshWindow</span><span class="p">();</span>
		<span class="k">break</span><span class="p">;</span>
	<span class="k">case</span> <span class="n">IronWroughtImGui</span><span class="o">::</span><span class="n">EVFXEditorMenu</span><span class="o">::</span><span class="n">ParticleEmitterView</span><span class="p">:</span>
		<span class="n">ShowParticleEffectWindow</span><span class="p">();</span>
		<span class="k">break</span><span class="p">;</span>
	<span class="nl">default:</span>
		<span class="k">break</span><span class="p">;</span>
	<span class="p">}</span>

	<span class="n">ImGui</span><span class="o">::</span><span class="n">End</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>On pressing save, all data structures currently held by the window are serialized, written to a filepath set by the user, and the singular VFXSystemComponent is reinitialized using the new or modified JSON file.</p>

<div class="click-zoom">
  <label>
      <input type="checkbox" />
      <img class="centered" src="/assets/images/vfx_save.gif" />
  </label>
</div>

<p>In the main window, VFX systems can be loaded and modified on the highest level. Effects can be added, and for every effect you can add any amount of VFX meshes and particle emitters. These can be offset and rotated about the associated game object, and each have their own delay and duration.
Any mesh or particle emitter file can be modified by a button press, where the view changes to show the file name and all attributes associated with the selected mesh or emitter. Saving works the same way in this view, but the file name of the selected mesh or emitter can also be changed here.</p>

<div class="click-zoom">
  <label>
      <input type="checkbox" />
      <img class="centered" src="/assets/images/vfx_open_separate_files.gif" />
  </label>
</div>

<p>For the particle emitter file view, there are tweakable curves for color, size and direction of the particles. These curves are opened in a separate window. For every attribute that can be changed by a curve, there is an <code class="language-plaintext highlighter-rouge">A</code> and a <code class="language-plaintext highlighter-rouge">B</code> value, such as starting color and final color. 
The curves are defined in the interpolation parameter (<code class="language-plaintext highlighter-rouge">y</code>-axis) over time (<code class="language-plaintext highlighter-rouge">x</code>-axis) coordinate system, meaning if the <code class="language-plaintext highlighter-rouge">y</code>-value at <code class="language-plaintext highlighter-rouge">x = 0</code> is <code class="language-plaintext highlighter-rouge">0</code>, the particle starts its lifetime at attribute value <code class="language-plaintext highlighter-rouge">A</code>, and if <code class="language-plaintext highlighter-rouge">y = 1</code>, the particle starts its lifetime at attribute value <code class="language-plaintext highlighter-rouge">B</code>.</p>

<p>In the current version of the editor, the curves are not actually curved, but straight lines going through several points. We had trouble determining how to interpret smooth line segments on the engine side when we first started setup, 
and so resorted to interpolating between easily definable points instead due to the time constraint.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">const</span> <span class="kt">float</span> <span class="n">SVFXEffect</span><span class="o">::</span><span class="n">CalculateInterpolator</span><span class="p">(</span><span class="k">const</span> <span class="n">std</span><span class="o">::</span><span class="n">vector</span><span class="o">&lt;</span><span class="n">Vector2</span><span class="o">&gt;&amp;</span> <span class="n">somePoints</span><span class="p">,</span> <span class="k">const</span> <span class="kt">float</span> <span class="n">t</span><span class="p">)</span> <span class="k">const</span>
<span class="p">{</span>
	<span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">pointIndex</span> <span class="o">=</span> <span class="k">static_cast</span><span class="o">&lt;</span><span class="kt">unsigned</span> <span class="kt">int</span><span class="o">&gt;</span><span class="p">(</span><span class="n">somePoints</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
	
	<span class="k">for</span> <span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">j</span> <span class="o">=</span> <span class="mi">1</span><span class="p">;</span> <span class="n">j</span> <span class="o">&lt;</span> <span class="n">somePoints</span><span class="p">.</span><span class="n">size</span><span class="p">()</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span> <span class="o">++</span><span class="n">j</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="k">if</span> <span class="p">(</span><span class="n">t</span> <span class="o">&lt;</span> <span class="n">somePoints</span><span class="p">[</span><span class="n">j</span><span class="p">].</span><span class="n">x</span> <span class="o">&amp;&amp;</span> <span class="n">t</span> <span class="o">&gt;</span> <span class="n">somePoints</span><span class="p">[</span><span class="n">j</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="n">x</span><span class="p">)</span>
		<span class="p">{</span>
			<span class="n">pointIndex</span> <span class="o">=</span> <span class="n">j</span><span class="p">;</span>
			<span class="k">break</span><span class="p">;</span>
		<span class="p">}</span>
	<span class="p">}</span>
	
	<span class="c1">// Nice function inspired by Freya Holmer, inverse lerps from one range and lerps into another</span>
	<span class="kt">float</span> <span class="n">val</span> <span class="o">=</span> <span class="n">Remap</span><span class="p">(</span><span class="n">somePoints</span><span class="p">[</span><span class="n">pointIndex</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="n">x</span><span class="p">,</span> <span class="n">somePoints</span><span class="p">[</span><span class="n">pointIndex</span><span class="p">].</span><span class="n">x</span><span class="p">,</span> <span class="mf">0.0</span><span class="n">f</span><span class="p">,</span> <span class="mf">1.0</span><span class="n">f</span><span class="p">,</span> <span class="n">t</span><span class="p">);</span>
	<span class="k">return</span> <span class="n">Lerp</span><span class="p">(</span><span class="n">somePoints</span><span class="p">[</span><span class="n">pointIndex</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="n">y</span><span class="p">,</span> <span class="n">somePoints</span><span class="p">[</span><span class="n">pointIndex</span><span class="p">].</span><span class="n">y</span><span class="p">,</span> <span class="n">val</span><span class="p">);</span>
<span class="p">}</span>

<span class="kt">void</span> <span class="n">SVFXEffect</span><span class="o">::</span><span class="n">UpdateParticles</span><span class="p">(</span><span class="kt">unsigned</span> <span class="kt">int</span> <span class="n">anIndex</span><span class="p">,</span> <span class="n">CParticleEmitter</span><span class="o">::</span><span class="n">SParticleData</span><span class="o">&amp;</span> <span class="n">particleData</span><span class="p">)</span>
<span class="p">{</span>
	<span class="c1">// ...</span>

	<span class="k">for</span> <span class="p">(</span><span class="n">UINT</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="n">myParticleVertices</span><span class="p">[</span><span class="n">anIndex</span><span class="p">].</span><span class="n">size</span><span class="p">();</span> <span class="o">++</span><span class="n">i</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="kt">float</span> <span class="n">quotient</span> <span class="o">=</span> <span class="n">myParticleVertices</span><span class="p">[</span><span class="n">anIndex</span><span class="p">][</span><span class="n">i</span><span class="p">].</span><span class="n">myLifeTime</span> <span class="o">/</span> <span class="n">particleData</span><span class="p">.</span><span class="n">myParticleLifetime</span><span class="p">;</span>		
	
		<span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">particleData</span><span class="p">.</span><span class="n">myColorCurve</span><span class="p">.</span><span class="n">empty</span><span class="p">())</span>
		<span class="p">{</span>
			<span class="n">myParticleVertices</span><span class="p">[</span><span class="n">anIndex</span><span class="p">][</span><span class="n">i</span><span class="p">].</span><span class="n">myColor</span> <span class="o">=</span> <span class="n">Vector4</span><span class="o">::</span><span class="n">Lerp</span>
			<span class="p">(</span>
				<span class="n">particleData</span><span class="p">.</span><span class="n">myParticleStartColor</span><span class="p">,</span>
				<span class="n">particleData</span><span class="p">.</span><span class="n">myParticleEndColor</span><span class="p">,</span>
				<span class="n">CalculateInterpolator</span><span class="p">(</span><span class="n">particleData</span><span class="p">.</span><span class="n">myColorCurve</span><span class="p">,</span> <span class="n">quotient</span><span class="p">)</span>
			<span class="p">);</span>
		<span class="p">}</span>

		<span class="c1">// ... Continue updating particles</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>When a curve is saved, the system is reinitialized.</p>

<div class="click-zoom">
  <label>
      <input type="checkbox" />
      <img class="centered" src="/assets/images/vfx_size_curve.gif" />
  </label>
</div>

<h2 id="improvements">Improvements</h2>

<p>Considering the editor was made under time constraints, there are a lot of improvements that could be made, some of which are listed below.</p>

<ul>
  <li>Velocity and opacity curves for particle emitter</li>
  <li>Scroll speed, opacity and size curves for vfx meshes</li>
  <li>Full PBR support for textures scrolled over vfx mesh, and particle textures</li>
  <li>Randomizable rotation for particle textures and meshes</li>
  <li>Easier removal and adding of systems, effects, vfx meshes and particle emitters</li>
</ul>

<p>Hopefully there will be time to realize these during our final exam project.</p>

<script src="/assets/js/jquery.photoset-grid.js"></script>

<script type="text/javascript">
    $('.photoset-grid-custom').photosetGrid({
    // Set the gutter between columns and rows
    gutter: '5px',
  
    // Wrap the images in links
    highresLinks: true,
  
    // Asign a common rel attribute
    rel: 'print-gallery',

    onInit: function(){},
    
    onComplete: function(){
        // Show the grid after it renders
        $('.photoset-grid-custom').attr('style', '');
    }
});
</script>]]></content><author><name></name></author><category term="education" /><category term="vfx" /><category term="imgui" /><category term="tools" /><summary type="html"><![CDATA[Introduction For our seventh game project at The Game Assembly, I made a VFX editor using ImGui based on the VFX and particle systems I designed for the project prior. With ImGui it was easy and fast to get a graphical user interface running and rendering in our custom engine, and features like the color picker and curve editing were essential in the workflow designed for the graphical artists.]]></summary></entry><entry><title type="html">Deferred Decals</title><link href="https://wejdell.github.io/deferred-decals/" rel="alternate" type="text/html" title="Deferred Decals" /><published>2021-03-01T00:00:00+00:00</published><updated>2021-03-01T00:00:00+00:00</updated><id>https://wejdell.github.io/deferred-decals</id><content type="html" xml:base="https://wejdell.github.io/deferred-decals/"><![CDATA[<h2 id="introduction">Introduction</h2>

<!--excerpt-begin-->
<p>I implemented deferred decals for our seventh game project at The Game Assembly.
The decals were exported from Unity and rendered to the G-Buffer in three separate passes depending on which textures to use; albedo, normal or “material” (containing metalness, roughness, emissive and detail normal strength).
<!--excerpt-end--></p>

<p>Using three separate passes, there is no need to copy the G-Buffer and you have higher flexibility regarding which textures to use and how to alpha blend the results.
I chose to use a cube volume for projection due to its simplicity, held by the renderer and scaled by the game object transform component.</p>

<h2 id="implementation">Implementation</h2>

<p>A decal factory receives data from the Unity export, including the filepath to a directory of textures to be used by the decal, and three flags representing which render passes to run for this specific decal. 
The albedo, material and normal (also containing ambient occlusion information) textures are requested from a material handler utility, being set to <code class="language-plaintext highlighter-rouge">nullptr</code> should they not exist rather than receiving a pointer to an error texture. 
Should any of the textures be <code class="language-plaintext highlighter-rouge">nullptr</code>, their respective flags are set to false.</p>

<p>The decal render pass is run right after rendering the G-Buffer. If the three decal render passes were not separated, we would set the whole G-Buffer as our render target.
If we then want to render different sets of textures for every decal, we would need to copy the whole G-Buffer and sample it in the shader so as to not overwrite the normals when we aren’t interested in the normals of the decal, for example. 
The normal texture is still bound as a render target, and if we do not discard the run of the entire fragment shader (which we might not want) we are going to overwrite the G-Buffer normals.</p>

<p>In the below GIFs there is no copying of the G-Buffer. Decal albedo data is being written in both cases, but there is no decal data being written to the material target (left) or normals target (right). 
These are still bound as render targets though, meaning they are overwritten by default values since there is no useful data to write.</p>

<div class="img-row">
  <div class="img-column">
    <div class="click-zoom">
      <label>
          <input type="checkbox" />
          <img class="centered" src="/assets/images/missing_material.gif" />
      </label>
    </div>
  </div>
  <div class="img-column">
  <div class="click-zoom">
      <label>
          <input type="checkbox" />
          <img class="centered" src="/assets/images/missing_normals.gif" />
      </label>
    </div>
  </div>
</div>

<p>That said, if every decal we render renders information to the same targets, this is not an issue, and it becomes cheaper to make only one draw call.</p>

<p>Since we wanted the freedom to render decals to any subset of the G-Buffer, I decided to render decals in different passes to avoid copying the G-Buffer. With this setup, different render targets can be set for every shader and you are guaranteed to not overwrite.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-c++ highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="n">CDecalRenderer</span><span class="o">::</span><span class="n">Render</span><span class="p">(...)</span>
<span class="p">{</span>
    <span class="c1">// ... Bind state common to all decals</span>
    
    <span class="k">for</span> <span class="p">(</span><span class="k">auto</span><span class="o">&amp;</span> <span class="n">gameObject</span> <span class="o">:</span> <span class="n">someDecalGameObjects</span><span class="p">)</span>
	<span class="p">{</span>
		<span class="n">CDecalComponent</span><span class="o">*</span> <span class="n">decalComponent</span> <span class="o">=</span> <span class="n">gameObject</span><span class="o">-&gt;</span><span class="n">GetComponent</span><span class="o">&lt;</span><span class="n">CDecalComponent</span><span class="o">&gt;</span><span class="p">();</span>

		<span class="n">CDecal</span><span class="o">*</span> <span class="n">decal</span> <span class="o">=</span> <span class="n">decalComponent</span><span class="o">-&gt;</span><span class="n">GetMyDecal</span><span class="p">();</span>
		<span class="k">const</span> <span class="n">CDecal</span><span class="o">::</span><span class="n">SDecalData</span><span class="o">&amp;</span> <span class="n">decalData</span> <span class="o">=</span> <span class="n">decal</span><span class="o">-&gt;</span><span class="n">GetDecalData</span><span class="p">();</span>

		<span class="n">myObjectBufferData</span><span class="p">.</span><span class="n">myToWorld</span> <span class="o">=</span> <span class="n">gameObject</span><span class="o">-&gt;</span><span class="n">myTransform</span><span class="o">-&gt;</span><span class="n">GetWorldMatrix</span><span class="p">();</span>
		<span class="n">myObjectBufferData</span><span class="p">.</span><span class="n">myToObjectSpace</span> <span class="o">=</span> <span class="n">gameObject</span><span class="o">-&gt;</span><span class="n">myTransform</span><span class="o">-&gt;</span><span class="n">GetWorldMatrix</span><span class="p">().</span><span class="n">Invert</span><span class="p">();</span>
		<span class="n">BindBuffer</span><span class="p">(</span><span class="n">myObjectBuffer</span><span class="p">,</span> <span class="n">myObjectBufferData</span><span class="p">,</span> <span class="s">"Object Buffer"</span><span class="p">);</span>

		<span class="n">myContext</span><span class="o">-&gt;</span><span class="n">VSSetConstantBuffers</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">myObjectBuffer</span><span class="p">);</span>
		<span class="n">myContext</span><span class="o">-&gt;</span><span class="n">PSSetConstantBuffers</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">myObjectBuffer</span><span class="p">);</span>
		<span class="n">myContext</span><span class="o">-&gt;</span><span class="n">PSSetShaderResources</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">decalData</span><span class="p">.</span><span class="n">myMaterial</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">decalData</span><span class="p">.</span><span class="n">myShouldRenderAlbedo</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">PSSetShader</span><span class="p">(</span><span class="n">myAlbedoPixelShader</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">DrawIndexed</span><span class="p">(</span><span class="n">myNumberOfIndices</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">CRenderManager</span><span class="o">::</span><span class="n">myNumberOfDrawCallsThisFrame</span><span class="o">++</span><span class="p">;</span>
                <span class="p">}</span>
        
                <span class="k">if</span> <span class="p">(</span><span class="n">decalData</span><span class="p">.</span><span class="n">myShouldRenderMaterial</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">PSSetShader</span><span class="p">(</span><span class="n">myMaterialPixelShader</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">DrawIndexed</span><span class="p">(</span><span class="n">myNumberOfIndices</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">CRenderManager</span><span class="o">::</span><span class="n">myNumberOfDrawCallsThisFrame</span><span class="o">++</span><span class="p">;</span>
                <span class="p">}</span>

                <span class="k">if</span> <span class="p">(</span><span class="n">decalData</span><span class="p">.</span><span class="n">myShouldRenderNormals</span><span class="p">)</span>
                <span class="p">{</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">PSSetShader</span><span class="p">(</span><span class="n">myNormalPixelShader</span><span class="p">,</span> <span class="nb">nullptr</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">myContext</span><span class="o">-&gt;</span><span class="n">DrawIndexed</span><span class="p">(</span><span class="n">myNumberOfIndices</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">);</span>
                    <span class="n">CRenderManager</span><span class="o">::</span><span class="n">myNumberOfDrawCallsThisFrame</span><span class="o">++</span><span class="p">;</span>
                <span class="p">}</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>Then comes the issue of alpha blending. Setting up alpha blending between one source and one destination is simple, but as our material target of the G-Buffer (which is <em>not</em> identical to the material texture of the decal) contains metalness, roughness, emissive and ambient occlusion, one has to be sacrificed as the alpha blending channel.</p>

<p>In the current implementation, I chose to sacrifice ambient occlusion on the decals and to sample the alpha level from the albedo texture. This means that even though we don’t render the decal to the albedo target, an albedo shader resource must still be bound. 
For the normals, the fourth component is unoccupied (since we moved the ambient occlusion from the normal texture to the material target), and also samples from the albedo shader resource.
The graphical artists could work the transparency into the material and normal textures for decals, but I found this solution better suited for our needs.</p>

<details>
  <summary><strong>View Code</strong></summary>

  <div class="language-hlsl highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Decal_MaterialPixelShader.hlsl</span>
<span class="cp">#include</span> <span class="cpf">"DecalShaderStructs.hlsli"</span><span class="cp">
</span>
<span class="kt">float4</span> <span class="nf">main</span><span class="p">(</span><span class="n">VertexToPixel</span> <span class="n">input</span><span class="p">)</span> <span class="o">:</span> <span class="n">SV_TARGET3</span>
<span class="p">{</span>
    <span class="kt">float3</span> <span class="n">clipSpace</span> <span class="o">=</span> <span class="n">input</span><span class="p">.</span><span class="n">myClipSpacePosition</span><span class="p">;</span>
    <span class="n">clipSpace</span><span class="p">.</span><span class="n">y</span> <span class="o">*=</span> <span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>
    <span class="kt">float2</span> <span class="n">screenSpaceUV</span> <span class="o">=</span> <span class="p">(</span><span class="n">clipSpace</span><span class="p">.</span><span class="n">xy</span> <span class="o">/</span> <span class="n">clipSpace</span><span class="p">.</span><span class="n">z</span><span class="p">)</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span> <span class="o">+</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span><span class="p">;</span>
    
    <span class="n">float</span> <span class="n">z</span> <span class="o">=</span> <span class="n">depthTexture</span><span class="p">.</span><span class="n">Sample</span><span class="p">(</span><span class="n">defaultSampler</span><span class="p">,</span> <span class="n">screenSpaceUV</span><span class="p">).</span><span class="n">r</span><span class="p">;</span>
    <span class="n">float</span> <span class="n">x</span> <span class="o">=</span> <span class="n">screenSpaceUV</span><span class="p">.</span><span class="n">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
    <span class="n">float</span> <span class="n">y</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span> <span class="o">-</span> <span class="n">screenSpaceUV</span><span class="p">.</span><span class="n">y</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span> <span class="o">-</span> <span class="mi">1</span><span class="p">;</span>
    <span class="kt">float4</span> <span class="n">projectedPos</span> <span class="o">=</span> <span class="kt">float4</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">);</span>
    <span class="kt">float4</span> <span class="n">viewSpacePos</span> <span class="o">=</span> <span class="nb">mul</span><span class="p">(</span><span class="n">toCameraFromProjection</span><span class="p">,</span> <span class="n">projectedPos</span><span class="p">);</span>
    <span class="n">viewSpacePos</span> <span class="o">/=</span> <span class="n">viewSpacePos</span><span class="p">.</span><span class="n">w</span><span class="p">;</span>
    <span class="kt">float4</span> <span class="n">worldPosFromDepth</span> <span class="o">=</span> <span class="nb">mul</span><span class="p">(</span><span class="n">toWorldFromCamera</span><span class="p">,</span> <span class="n">viewSpacePos</span><span class="p">);</span>
    
    <span class="kt">float4</span> <span class="n">objectPosition</span> <span class="o">=</span> <span class="nb">mul</span><span class="p">(</span><span class="n">toObjectSpace</span><span class="p">,</span> <span class="n">worldPosFromDepth</span><span class="p">);</span>
    <span class="nb">clip</span><span class="p">(</span><span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span> <span class="o">-</span> <span class="nb">abs</span><span class="p">(</span><span class="n">objectPosition</span><span class="p">.</span><span class="n">xyz</span><span class="p">));</span>
    <span class="kt">float2</span> <span class="n">decalUV</span> <span class="o">=</span> <span class="n">objectPosition</span><span class="p">.</span><span class="n">xy</span> <span class="o">+</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="n">f</span><span class="p">;</span>
    <span class="n">decalUV</span><span class="p">.</span><span class="n">y</span> <span class="o">*=</span> <span class="o">-</span><span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">;</span>

    <span class="kt">float3</span> <span class="n">material</span> <span class="o">=</span> <span class="n">materialTexture</span><span class="p">.</span><span class="n">Sample</span><span class="p">(</span><span class="n">defaultSampler</span><span class="p">,</span> <span class="n">decalUV</span><span class="p">).</span><span class="n">rgb</span><span class="p">;</span>
    <span class="n">float</span> <span class="n">alpha</span> <span class="o">=</span> <span class="n">albedoTexture</span><span class="p">.</span><span class="n">Sample</span><span class="p">(</span><span class="n">defaultSampler</span><span class="p">,</span> <span class="n">decalUV</span><span class="p">).</span><span class="n">a</span><span class="p">;</span>
	
    <span class="k">return</span> <span class="kt">float4</span><span class="p">(</span><span class="n">material</span><span class="p">.</span><span class="n">rgb</span><span class="p">,</span> <span class="n">alpha</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>  </div>

</details>

<p>The three screen shots below depict a decal being rendered to different parts of the G-Buffer. Left is only albedo, using the material and normal information already stored in the G-Buffer, middle is only material, and right is only normals.</p>

<div class="img-row">
  <div class="img-column">
    <div class="click-zoom">
      <label>
          <input type="checkbox" />
          <img class="centered" src="/assets/images/decal_albedo.jpg" />
      </label>
    </div>
  </div>
  <div class="img-column">
      <div class="click-zoom">
        <label>
            <input type="checkbox" />
            <img class="centered" src="/assets/images/decal_material.jpg" />
        </label>
      </div>
  </div>
  <div class="img-column">
    <div class="click-zoom">
      <label>
          <input type="checkbox" />
          <img class="centered" src="/assets/images/decal_normal.jpg" />
      </label>
    </div>
  </div>
</div>]]></content><author><name></name></author><category term="education" /><category term="deferred rendering" /><category term="decals" /><category term="rendering" /><summary type="html"><![CDATA[Introduction I implemented deferred decals for our seventh game project at The Game Assembly. The decals were exported from Unity and rendered to the G-Buffer in three separate passes depending on which textures to use; albedo, normal or “material” (containing metalness, roughness, emissive and detail normal strength).]]></summary></entry></feed>