<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on ~</title><link>https://hk47196.github.io/posts/</link><description>Recent content in Posts on ~</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><managingEditor>rogerswaggoner@gmail.com (Roger Waggoner)</managingEditor><webMaster>rogerswaggoner@gmail.com (Roger Waggoner)</webMaster><lastBuildDate>Thu, 18 Jun 2026 00:00:00 -0400</lastBuildDate><atom:link href="https://hk47196.github.io/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>Ultima Underworld PSX outside a PlayStation: AbyssX</title><link>https://hk47196.github.io/ultima-underworld-psx-outside-a-playstation-abyssx/</link><pubDate>Thu, 18 Jun 2026 00:00:00 -0400</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/ultima-underworld-psx-outside-a-playstation-abyssx/</guid><description>&lt;p>I&amp;rsquo;ve been working on &lt;strong>AbyssX&lt;/strong>, a C++20 reimplementation of the PlayStation version of &lt;em>Ultima Underworld&lt;/em>.&lt;/p>
&lt;p>It is still incomplete, but it has reached a fun point: I can wander around the first level, interact with objects, manage inventory, and get into fights.&lt;/p>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe src="https://www.youtube.com/embed/Eg8ncxRtjvs" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
&lt;/div>
&lt;h2 id="what-is-working">What is working&lt;/h2>
&lt;p>The video is mostly me poking around level one, but a lot has to be in place before that is possible:&lt;/p>
&lt;ul>
&lt;li>the original BIN/CUE game data is read locally&lt;/li>
&lt;li>&lt;code>DATA/AVATAR&lt;/code> resources are loaded through the native storage layer&lt;/li>
&lt;li>map and object state are far enough along for first-level exploration&lt;/li>
&lt;li>inventory handling works&lt;/li>
&lt;li>combat works&lt;/li>
&lt;li>world object interaction works&lt;/li>
&lt;li>input and VSync-style timing are connected to the recovered game loop&lt;/li>
&lt;li>rendering is far enough along to make the level playable&lt;/li>
&lt;li>native audio, video, and platform backends cover enough of the PSX-facing contracts for early gameplay&lt;/li>
&lt;/ul>
&lt;p>There is a useful modding path now, too.&lt;/p>
&lt;p>At startup, AbyssX can build a virtual &lt;code>DATA/AVATAR&lt;/code> archive from local override files. That lets me experiment with resource changes without patching the original disc image. I used that to adapt an existing English patch into a native text override.&lt;/p>
&lt;h2 id="what-is-not-working-yet">What is not working yet&lt;/h2>
&lt;p>Plenty.&lt;/p>
&lt;p>AddressSanitizer is especially noisy right now. Some crashes are ordinary reimplementation bugs. Others come from recovered original out of bounds indexing, buffer overflows, etc.,&lt;/p>
&lt;p>Still, it is a good milestone. Enough of the recovered game is running through native systems that the result feels like a game again. It&amp;rsquo;s like seeing light at the end of a very, very long tunnel.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next&lt;/h2>
&lt;p>The next phase is mostly correctness work:&lt;/p>
&lt;ul>
&lt;li>work through AddressSanitizer crashes, including unsafe assumptions inherited from the original game code&lt;/li>
&lt;li>improve rendering and audio edge cases&lt;/li>
&lt;li>expose local save storage cleanly&lt;/li>
&lt;li>keep filling in map, object, script, and resource semantics&lt;/li>
&lt;li>compare more behavior against the original PlayStation executable&lt;/li>
&lt;/ul></description></item><item><title>Reverse Engineering DOS Games on FM Towns</title><link>https://hk47196.github.io/reverse-engineering-dos-games-on-fm-towns/</link><pubDate>Thu, 04 Dec 2025 12:48:14 -0500</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/reverse-engineering-dos-games-on-fm-towns/</guid><description>&lt;p>I&amp;rsquo;ve been reverse engineering old DOS games, and one of the biggest headaches is segmented memory. Real-mode x86 is painful to work with in Ghidra - even if the tooling wasn&amp;rsquo;t lacking, dealing with segmented memory is still difficult.&lt;/p>
&lt;p>So I&amp;rsquo;ve been looking for ports that avoid it. Enter the FM Towns.&lt;/p>
&lt;h2 id="why-fm-towns">Why FM Towns?&lt;/h2>
&lt;p>The FM Towns is a bit of an oddity. It&amp;rsquo;s x86-based, but it&amp;rsquo;s &lt;em>not&lt;/em> IBM PC compatible - it ran its own custom version of MS-DOS and won&amp;rsquo;t work with DOSBox. What makes it interesting for RE is that applications used a DOS extender by default, giving you flat 32-bit protected mode binaries instead of segmented real-mode nightmares.&lt;/p>
&lt;p>The FM Towns was popular in Japan, and many Western games got Japanese ports. If a game you&amp;rsquo;re interested in has a Towns version, there&amp;rsquo;s a good chance you can work with a flat memory model instead.&lt;/p>
&lt;p>From what I&amp;rsquo;ve seen, most of these applications use the Phar Lap DOS extender. I&amp;rsquo;ve written a Ghidra loader for the P3 format (the most common one I&amp;rsquo;ve encountered) - I&amp;rsquo;ll get that on GitHub eventually.&lt;/p>
&lt;h2 id="adding-tracing-to-tsugaru">Adding tracing to Tsugaru&lt;/h2>
&lt;p>Static analysis only gets you so far. I wanted to record execution traces, so I forked &lt;a href="https://github.com/captainys/TOWNSEMU">Tsugaru&lt;/a> (the main FM Towns emulator) to add tracing support.&lt;/p>
&lt;p>Traces are stored in SQLite for easy querying. The tracer runs on a separate thread, so runtime overhead is minimal.&lt;/p>
&lt;pre tabindex="0">&lt;code>STARTTRACE output.db -SNAPINT 0 -PCRANGE 0x400000 0x450ee3 -ADAPTIVE 1000
&lt;/code>&lt;/pre>&lt;p>The flags:&lt;/p>
&lt;ul>
&lt;li>&lt;code>-PCRANGE&lt;/code>: Only trace instructions within this address range. This is in linear address space so you&amp;rsquo;ll need to figure out the mappings.&lt;/li>
&lt;li>&lt;code>-SNAPINT 0&lt;/code>: Snapshot interval - 0 disables periodic memory snapshots, otherwise it&amp;rsquo;s in # of instructions.&lt;/li>
&lt;li>&lt;code>-ADAPTIVE 1000&lt;/code>: Throttle capture after 1000 hits to the same address&lt;/li>
&lt;/ul>
&lt;p>This captures memory reads/writes, CALLs, and RETs for instructions in the specified range.&lt;/p>
&lt;h2 id="keeping-trace-sizes-manageable">Keeping trace sizes manageable&lt;/h2>
&lt;p>Naive tracing would generate gigabytes in minutes. The tracer deduplicates aggressively and uses adaptive throttling to back off from hotspots. Playing Ultima Underworld for 30 minutes produced about 214MiB of trace data, with growth slowing significantly after the first ~150MiB as the throttling kicks in.&lt;/p>
&lt;h2 id="whats-next">What&amp;rsquo;s next&lt;/h2>
&lt;p>I&amp;rsquo;ll cover how I&amp;rsquo;m using these traces to map out Ultima Underworld&amp;rsquo;s internals in a future post. The Ghidra loader and the Tsugaru fork will be on my GitHub once they&amp;rsquo;re cleaned up.&lt;/p></description></item><item><title>Infinity FM-Towns Driver</title><link>https://hk47196.github.io/infinity-fm-towns-driver/</link><pubDate>Thu, 27 Nov 2025 00:31:09 -0500</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/infinity-fm-towns-driver/</guid><description>&lt;p>INFINITY Co.,Ltd., a studio that ported games to the FMTowns system, used a small FMTowns graphics wrapper to assist with this.&lt;/p>
&lt;p>I&amp;rsquo;m actually not sure if this is something that was written by INFINITY or was provided in an SDK for FMTowns, but I did find it with one of their ports.&lt;/p>
&lt;p>The file has no standard header and begins with a 26-entry long jump table:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nasm" data-lang="nasm">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000000&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[0]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_InitWithReloc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000004&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[1]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_ShutDown
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000008&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[2]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_Nop1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000000&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">3&lt;/span>] addr drv_GetPageSize
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000010&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[4]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_PageFlip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000014&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[5]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_SwapBuffers
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000018&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[6]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_SetPalette
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000001&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">7&lt;/span>] addr drv_FillRect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000020&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[8]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_CopyRect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000024&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[9]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_PutPixel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000028&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[10]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_GetPixel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000002&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">11&lt;/span>] addr drv_BlockCopy
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000030&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[12]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_DrawLine
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000034&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[13]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_BlitTransparent
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000038&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[14]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_Nop2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000003&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">15&lt;/span>] addr drv_BlitOpaque
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000040&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[16]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_DrawSprite
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000044&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[17]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_DrawSpriteAlt
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000048&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[18]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_Blit1bpp
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000004&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">19&lt;/span>] addr drv_CopyScanline
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000050&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[20]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_BlitRect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000054&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[21]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_BlitRect
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000058&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[22]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_SetFontMetrics
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">0000005&lt;/span>&lt;span style="color:#a6e22e">c&lt;/span> [&lt;span style="color:#ae81ff">23&lt;/span>] addr drv_DrawText
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000060&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[24]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_CopyScanlineBoth
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">|&lt;/span>_ram:&lt;span style="color:#960050;background-color:#1e0010">00000064&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">[25]&lt;/span> &lt;span style="color:#a6e22e">addr&lt;/span> drv_Nop3
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It is loaded dynamically at runtime.&lt;/p>
&lt;p>Here are some of my findings:&lt;/p>
&lt;h3 id="1-relocation-and-dynamic-loading">1. Relocation and Dynamic Loading&lt;/h3>
&lt;p>The most immediate characteristic of this driver is its self-relocation mechanism. The function at index 0, &lt;code>drv_InitWithReloc&lt;/code>, reveals that the code is designed to be position-independent but requires a one-time setup to function correctly.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nasm" data-lang="nasm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">; undefined drv_InitWithReloc(void)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">c2&lt;/span> e843ffffff CALL drv_InternalVideoSetup
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">c7&lt;/span> &lt;span style="color:#ae81ff">0b&lt;/span>c0 OR EAX,EAX
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">c9&lt;/span> &lt;span style="color:#ae81ff">7411&lt;/span> JZ LAB_000001dc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">cb&lt;/span> b968000000 MOV ECX,&lt;span style="color:#ae81ff">0x68&lt;/span> &lt;span style="color:#75715e">; 26 entries * 4 bytes&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">d0&lt;/span> c1e902 SHR ECX,&lt;span style="color:#ae81ff">0x2&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">d3&lt;/span> &lt;span style="color:#ae81ff">8&lt;/span>bfb MOV EDI,EBX &lt;span style="color:#75715e">; EBX is likely the load address&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>LAB_000001d5:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">d5&lt;/span> &lt;span style="color:#ae81ff">011&lt;/span>f ADD &lt;span style="color:#66d9ef">dword&lt;/span> ptr [EDI],EBX
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">d7&lt;/span> &lt;span style="color:#ae81ff">83&lt;/span>c704 ADD EDI,&lt;span style="color:#ae81ff">0x4&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000001&lt;/span>&lt;span style="color:#a6e22e">da&lt;/span> e2f9 LOOP LAB_000001d5
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The routine iterates through the initial 26-entry jump table, adding the base load address (passed in &lt;code>EBX&lt;/code>) to each entry. This implies the main executable allocates a block of memory, loads this binary blob, and then calls the first instruction with the allocated address in &lt;code>EBX&lt;/code> to &amp;ldquo;patch&amp;rdquo; the driver into validity.&lt;/p>
&lt;h3 id="2-software-line-doubling">2. Software Line-Doubling&lt;/h3>
&lt;p>A distinct feature of the drawing routines is the implementation of software-based scanline doubling. An examination of &lt;code>drv_FillRect&lt;/code> and &lt;code>drv_CopyScanline&lt;/code> shows a specific memory access pattern:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nasm" data-lang="nasm">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">; From drv_FillRect&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000002&lt;/span>&lt;span style="color:#a6e22e">b8&lt;/span> &lt;span style="color:#ae81ff">2&lt;/span>bfa SUB EDI,param_2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000002&lt;/span>&lt;span style="color:#a6e22e">ba&lt;/span> &lt;span style="color:#ae81ff">81&lt;/span>c700040000 ADD EDI,&lt;span style="color:#ae81ff">0x400&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">; ... repeat store operation ...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">; From drv_CopyScanline&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">000006&lt;/span>&lt;span style="color:#a6e22e">b3&lt;/span> &lt;span style="color:#ae81ff">81&lt;/span>c500080000 ADD EBP,&lt;span style="color:#ae81ff">0x800&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The driver operates with a virtual stride of 1024 bytes (&lt;code>0x400&lt;/code>). However, primitive drawing operations write to the target offset &lt;code>Y&lt;/code>, add &lt;code>0x400&lt;/code> to write the same data to &lt;code>Y+1&lt;/code>, and then add &lt;code>0x800&lt;/code> to the base pointer to advance to the next logical line.&lt;/p>
&lt;p>Rather than relying on hardware scaling, the driver &amp;ldquo;doubles&amp;rdquo; the height by drawing every line twice explicitly in VRAM.&lt;/p>
&lt;h3 id="3-shift-jis-character-support">3. Shift-JIS Character Support&lt;/h3>
&lt;p>The driver contains a robust text rendering engine, specifically tailored for Japanese text. &lt;code>drv_DrawText&lt;/code> (offset &lt;code>0x005c&lt;/code>) performs boundary checks consistent with Shift-JIS encoding:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-nasm" data-lang="nasm">&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">0000087&lt;/span>&lt;span style="color:#a6e22e">d&lt;/span> &lt;span style="color:#ae81ff">3&lt;/span>c81 CMP param_1,&lt;span style="color:#ae81ff">0x81&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">0000087&lt;/span>&lt;span style="color:#a6e22e">f&lt;/span> &lt;span style="color:#ae81ff">7216&lt;/span> JC LAB_00000897 &lt;span style="color:#75715e">; ASCII/Half-width&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">00000881&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">3&lt;/span>&lt;span style="color:#a6e22e">c9f&lt;/span> CMP param_1,&lt;span style="color:#ae81ff">0x9f&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ram:&lt;span style="color:#960050;background-color:#1e0010">00000883&lt;/span> &lt;span style="color:#960050;background-color:#1e0010">7608&lt;/span> &lt;span style="color:#a6e22e">JBE&lt;/span> LAB_0000088d &lt;span style="color:#75715e">; Shift-JIS Lead Byte&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="4-hardware-port-utilization">4. Hardware Port Utilization&lt;/h3>
&lt;p>The driver manipulates the Towns&amp;rsquo; unique video hardware directly via I/O ports, rather than relying solely on BIOS calls.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Ports 0x440/0x442:&lt;/strong> Used in &lt;code>drv_PageFlip&lt;/code> to toggle display pages, indicating a double-buffered rendering setup.&lt;/li>
&lt;li>&lt;strong>Ports 0xFD90-0xFD9F:&lt;/strong> Accessed in &lt;code>drv_SetPalette&lt;/code>. These correspond to the FM Towns palette registers.&lt;/li>
&lt;li>&lt;strong>Ports 0x58/0x5A:&lt;/strong> The &lt;code>drv_InternalVideoSetup&lt;/code> function writes to these ports, which usually control the CRTC (Cathode Ray Tube Controller) priority and layer mixing, likely ensuring the graphical layer used by the game overlays correctly on top of the system background.&lt;/li>
&lt;/ul>
&lt;p>Not all of this has been fact-checked nor am I an expert at FM-Towns.&lt;/p></description></item><item><title>Ghidra Lessons</title><link>https://hk47196.github.io/ghidra-lessons/</link><pubDate>Tue, 18 Nov 2025 03:33:59 -0500</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/ghidra-lessons/</guid><description>&lt;p>I&amp;rsquo;ve been doing a lot of reversing lately and learning a ton while doing it, I&amp;rsquo;m
going to try to document and maybe share some knowledge.&lt;/p>
&lt;ol>
&lt;li>Segmented memory is really hard and the tools are bad. Unless you have good
reason, stay away from x86 dos-era binaries using real mode. I will check back in on this in 5 years and see if the tooling has improved. A lot of games were ported to other platforms, so this can sometimes be avoided just by working on a different version of the same game.&lt;/li>
&lt;li>Ghidra is really great and easily extensible. But it has some issues along
with a steep learning curve.&lt;/li>
&lt;/ol>
&lt;p>Using a union with &lt;code>uint8_t&lt;/code> (&lt;code>byte&lt;/code>) array greatly helped improve decompilation output:&lt;/p>
&lt;p>Before:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/94dc15ffc79b4bdd31ab8ab804ae135953fe21e4.jpg">
&lt;/p>
&lt;p>The &lt;code>+ -10&lt;/code> part is because the &lt;code>.partySlotToCharacterIndex&lt;/code>&lt;/p>
&lt;p>After:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/2d62fe10135788e49b603281120a7d30a741eab8.jpg">
&lt;/p>
&lt;p>A few other tips:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>Start bottom up. Identify known functions and begin propagating types upwards. Even in old executables you will find plenty of system library calls that are used as the foundational building blocks of the program.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Make usage of the version control feature, and checkin regularly.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Use auto-analysis to propagate analyses after working on the program.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Use the diff tool to diff the results of auto-analysis. You can diff against the checked in version, but make sure you actually select the latest version rather than it from the listing. This is good to even see the results of various auto-analyses options.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The documentation is very good&lt;/p>
&lt;/li>
&lt;li>
&lt;p>LLMs seem to fair poorly with the output of the decompilation. It often has false positives, red herrings, etc., that they latch onto. On the other hand, the disassembly alone seems to be fine.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Massive majority of articles, plugins, and so on for Ghidra are related to malware analysis, vulnerability research, etc.,&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Be careful of constants that happen to have the same address as pointers. This has bitten me a lot!&lt;/p>
&lt;/li>
&lt;/ol></description></item><item><title>MicroRules: A Tiny Expression Language for C#</title><link>https://hk47196.github.io/microrules-a-tiny-expression-language-for-c/</link><pubDate>Sun, 29 Jun 2025 00:00:00 +0000</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/microrules-a-tiny-expression-language-for-c/</guid><description>&lt;p>I built &lt;strong>MicroRules&lt;/strong> because I got tired of writing verbose conditional logic for business rules that changed frequently. What started as a simple expression parser evolved into a full-featured domain-specific language that compiles string expressions into strongly-typed, executable C# functions.&lt;/p>
&lt;h2 id="the-problem">The Problem&lt;/h2>
&lt;p>Business logic is messy. You have discount calculations, shipping rules, access control, content moderation - all these rules that seem simple but get complex fast. Traditional approaches either scatter this logic throughout your codebase or force you into heavy rule engines that are overkill for most scenarios.&lt;/p>
&lt;p>I wanted something lightweight that could express rules compactly while maintaining strong typing and performance.&lt;/p>
&lt;h2 id="the-solution">The Solution&lt;/h2>
&lt;p>MicroRules takes string expressions and compiles them into actual .NET functions at runtime. Not interpreted - compiled. The result is a tiny DSL that&amp;rsquo;s both readable and fast.&lt;/p>
&lt;p>Here&amp;rsquo;s a simple discount rule:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">var&lt;/span> discountRule = MicroRules.Compile&amp;lt;SelfFunc&amp;lt;Customer, &lt;span style="color:#66d9ef">double&lt;/span>&amp;gt;&amp;gt;(&lt;span style="color:#e6db74">&amp;#34;SpendTotal &amp;gt; 100 ? 0.1 : 0&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">double&lt;/span> discount = discountRule(customer);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="pattern-matching-for-complex-logic">Pattern Matching for Complex Logic&lt;/h2>
&lt;p>The real power comes from pattern matching. Take shipping costs - traditionally a nightmare of nested if-statements:&lt;/p>
&lt;pre tabindex="0">&lt;code>(zone, weight, service, membership) -&amp;gt; {
(Domestic, &amp;lt;= 5, Express, Premium): baseCost * 0.8,
(International, _, _, Premium): baseCost * 1.2,
(_, &amp;gt; 20, Ground, _): baseCost + (weight - 20) * surchargeRate,
(Remote, _, _, _): baseCost * 1.5,
_: baseCost
}
&lt;/code>&lt;/pre>&lt;p>This tuple-based pattern matching supports exact values, range comparisons (&lt;code>&amp;lt;= 5&lt;/code>, &lt;code>&amp;gt; 20&lt;/code>), and wildcards (&lt;code>_&lt;/code>). First match wins, so you can layer specific cases over general ones.&lt;/p>
&lt;h2 id="linq-integration">LINQ Integration&lt;/h2>
&lt;p>Modern business rules often involve collections. MicroRules supports LINQ operations with lambda expressions:&lt;/p>
&lt;pre tabindex="0">&lt;code>// Block checkout if any item is unavailable
cart.items.Any(i =&amp;gt; i.inventory &amp;lt;= 0)
// Check if user has required permissions
user.permissions.All(p =&amp;gt; p.level &amp;gt;= AdminLevel &amp;amp;&amp;amp; p.active)
&lt;/code>&lt;/pre>&lt;h2 id="collection-contains-patterns">Collection Contains Patterns&lt;/h2>
&lt;pre tabindex="0">&lt;code>(patientAge, weight, condition, allergies) -&amp;gt; {
(&amp;gt;= 65, _, Hypertension, [&amp;#39;ace-inhibitor&amp;#39;]): alternativeDose * 0.5,
(&amp;lt; 18, &amp;lt; 50, _, _): pediatricDose,
(_, &amp;gt;= 100, Diabetes, _): standardDose * 1.2,
_: standardDose
}
&lt;/code>&lt;/pre>&lt;p>The &lt;code>['ace-inhibitor']&lt;/code> checks if the allergies collection contains that specific value. Works with arrays, lists, dictionaries, and even strings (substring search).&lt;/p>
&lt;h2 id="technical-implementation">Technical Implementation&lt;/h2>
&lt;p>Under the hood, MicroRules uses a three-stage compilation pipeline:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Lexer&lt;/strong> - Tokenizes expressions with support for string literals, numbers, operators, and identifiers&lt;/li>
&lt;li>&lt;strong>Parser&lt;/strong> - Recursive descent parser building LINQ Expression trees with proper operator precedence&lt;/li>
&lt;li>&lt;strong>Compiler&lt;/strong> - Wraps compiled expressions in strongly-typed delegates&lt;/li>
&lt;/ol>
&lt;p>The parser handles some tricky features:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Enum inference&lt;/strong> - &lt;code>weaponType == Sword&lt;/code> automatically infers &lt;code>WeaponType.Sword&lt;/code> from context&lt;/li>
&lt;li>&lt;strong>Mixed arithmetic&lt;/strong> - Integers and doubles play nicely together&lt;/li>
&lt;li>&lt;strong>Extension methods&lt;/strong> - Full LINQ support plus custom extension method discovery&lt;/li>
&lt;li>&lt;strong>Deep member access&lt;/strong> - &lt;code>user.profile.settings.theme&lt;/code> just works&lt;/li>
&lt;/ul>
&lt;h2 id="type-safety-and-performance">Type Safety and Performance&lt;/h2>
&lt;p>Everything is strongly typed. The compiler validates member access, enum values, and type conversions at compile time. Invalid expressions throw clear error messages before your code ever runs.&lt;/p>
&lt;p>Performance-wise, compiled expressions run at native .NET speed. The compilation cost is paid once - after that, it&amp;rsquo;s just a function call.&lt;/p>
&lt;h2 id="usage-patterns">Usage Patterns&lt;/h2>
&lt;p>I designed MicroRules around two main usage patterns:&lt;/p>
&lt;p>&lt;strong>Custom delegates&lt;/strong> for multi-parameter scenarios:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">delegate&lt;/span> &lt;span style="color:#66d9ef">double&lt;/span> PricingRule(Order order, &lt;span style="color:#66d9ef">double&lt;/span> basePrice);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">var&lt;/span> rule = MicroRules.Compile&amp;lt;PricingRule&amp;gt;(&lt;span style="color:#e6db74">&amp;#34;order.Items.Any(i =&amp;gt; i.Category == Premium) ? basePrice * 1.2 : basePrice&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;strong>SelfFunc&lt;/strong> for single-parameter cases where you want implicit property lookup:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">var&lt;/span> rule = MicroRules.Compile&amp;lt;SelfFunc&amp;lt;Customer, &lt;span style="color:#66d9ef">bool&lt;/span>&amp;gt;&amp;gt;(&lt;span style="color:#e6db74">&amp;#34;Age &amp;gt;= 18 &amp;amp;&amp;amp; CreditScore &amp;gt; 600&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">bool&lt;/span> eligible = rule(customer);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &amp;ldquo;self&amp;rdquo; in SelfFunc enables that clean syntax where &lt;code>Age&lt;/code> automatically resolves to &lt;code>customer.Age&lt;/code>.&lt;/p>
&lt;p>*MicroRules is open source and available on &lt;a href="https://github.com/HK47196/MicroRules">GitHub&lt;/a>.&lt;/p></description></item><item><title>When lerp isn't linear</title><link>https://hk47196.github.io/when-lerp-isnt-linear/</link><pubDate>Sat, 13 Jul 2024 04:01:40 -0400</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/when-lerp-isnt-linear/</guid><description>&lt;p>You will frequently come across code in the wild that vaguely resembles this:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C#" data-lang="C#">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Most engines will provide a lerp for you&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>T lerp(T a, T b, &lt;span style="color:#66d9ef">float&lt;/span> t) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> a + t * (b - a);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> update(&lt;span style="color:#66d9ef">float&lt;/span> delta) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> position = lerp(position, destination, delta);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Perhaps even multiplying the speed by delta in the &lt;code>t&lt;/code> parameter.&lt;/p>
&lt;p>At face value, it seems to &lt;em>work&lt;/em>, but it&amp;rsquo;s probably not doing what you think
it&amp;rsquo;s doing. Lerp is not a smoothing function, it is for linearly interpolating a
value across a line from &lt;code>a&lt;/code> to &lt;code>b&lt;/code> by the factor &lt;code>t&lt;/code>. If you are repeatedly
changing the &lt;code>a&lt;/code> and/or &lt;code>b&lt;/code> values, you are using lerp &lt;strong>incorrectly&lt;/strong> — it is no
longer linear.&lt;/p>
&lt;p>What you are doing is applying a telescoping function — it is a geometric
series. &lt;code>position&lt;/code> will nonlinearly(asymptotically) approach &lt;code>destination&lt;/code>,
which creates the desired appearance of smoothing.&lt;/p>
&lt;h1 id="what-to-use-instead">What to use instead?&lt;/h1>
&lt;p>If you just want to move &lt;code>a&lt;/code> towards &lt;code>b&lt;/code> at a constant speed, you do &lt;strong>not&lt;/strong>
want or need &lt;code>lerp&lt;/code>. The Godot engine provides a function named &lt;code>move_toward&lt;/code>
that does exactly what you want. This is how it&amp;rsquo;s implemented in the C++
source:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C++" data-lang="C++">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">double&lt;/span> &lt;span style="color:#a6e22e">move_toward&lt;/span>(&lt;span style="color:#66d9ef">double&lt;/span> p_from, &lt;span style="color:#66d9ef">double&lt;/span> p_to, &lt;span style="color:#66d9ef">double&lt;/span> p_delta) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> abs(p_to &lt;span style="color:#f92672">-&lt;/span> p_from) &lt;span style="color:#f92672">&amp;lt;=&lt;/span> p_delta &lt;span style="color:#f92672">?&lt;/span> p_to : p_from &lt;span style="color:#f92672">+&lt;/span> SIGN(p_to &lt;span style="color:#f92672">-&lt;/span> p_from) &lt;span style="color:#f92672">*&lt;/span> p_delta;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="what-if-you-want-to-smoothly-move-a-towards-b">What if you want to smoothly move &lt;code>a&lt;/code> towards &lt;code>b&lt;/code>?&lt;/h2>
&lt;p>Game Programming Gems IV(2004) book, Chapter 1.10 &amp;ldquo;Critically Damped
Ease-In/Ease-Out Smoothing&amp;rdquo; describes an implementation of smoothing based on a
critically damped spring model.
&lt;img src="https://hk47196.github.io/images/spring.webp">
&lt;/p>
&lt;p>I&amp;rsquo;m borrowing this implementation from Unity&amp;rsquo;s reference source code for
demonstration:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-C#" data-lang="C#">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">public&lt;/span> &lt;span style="color:#66d9ef">static&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> SmoothDamp(&lt;span style="color:#66d9ef">float&lt;/span> current, &lt;span style="color:#66d9ef">float&lt;/span> target, &lt;span style="color:#66d9ef">ref&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> currentVelocity, &lt;span style="color:#66d9ef">float&lt;/span> smoothTime, &lt;span style="color:#66d9ef">float&lt;/span> maxSpeed, &lt;span style="color:#66d9ef">float&lt;/span> deltaTime)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> smoothTime = Mathf.Max(&lt;span style="color:#ae81ff">0.0001F&lt;/span>, smoothTime);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> omega = &lt;span style="color:#ae81ff">2F&lt;/span> / smoothTime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> x = omega * deltaTime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> exp = &lt;span style="color:#ae81ff">1F&lt;/span> / (&lt;span style="color:#ae81ff">1F&lt;/span> + x + &lt;span style="color:#ae81ff">0.48F&lt;/span> * x * x + &lt;span style="color:#ae81ff">0.235F&lt;/span> * x * x * x);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> change = current - target;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> originalTo = target;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Clamp maximum speed&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> maxChange = maxSpeed * smoothTime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> change = Mathf.Clamp(change, -maxChange, maxChange);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> target = current - change;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> temp = (currentVelocity + omega * change) * deltaTime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentVelocity = (currentVelocity - omega * temp) * exp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> output = target + (change + temp) * exp;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Prevent overshooting&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (originalTo - current &amp;gt; &lt;span style="color:#ae81ff">0.0F&lt;/span> == output &amp;gt; originalTo)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> output = originalTo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> currentVelocity = (output - originalTo) / deltaTime;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> output;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Unity&amp;rsquo;s documentation defines the parameters as following:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Parameter&lt;/th>
&lt;th>Description&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>current&lt;/td>
&lt;td>The current value.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>target&lt;/td>
&lt;td>The target value.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>currentVelocity&lt;/td>
&lt;td>Use this parameter to specify the initial velocity to move the current value towards the target value. This method updates the currentVelocity based on this movement and smooth-damping.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>smoothTime&lt;/td>
&lt;td>The approximate time it takes for the current value to reach the target value. The lower the smoothTime, the faster the current value reaches the target value. The minimum smoothTime is 0.0001. If a lower value is specified, it is clamped to the minimum value.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>maxSpeed&lt;/td>
&lt;td>Use this optional parameter to specify a maximum speed. By default, the maximum speed is set to infinity.&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>deltaTime&lt;/td>
&lt;td>The time since this method was last called. By default, this is set to &lt;code>Time.deltaTime&lt;/code>.&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I find it practical to define &lt;code>smooth_time&lt;/code> as &lt;code>distance&lt;/code> / &lt;code>desired_velocity&lt;/code>,
as we often know the velocity we want when smoothing.
If you want it to also ease out, apply &lt;code>sqrt&lt;/code> to &lt;code>smooth_time&lt;/code>. Additionally,
you&amp;rsquo;ll want to set &lt;code>max_speed&lt;/code> to &lt;code>desired_velocity&lt;/code>.&lt;/p></description></item><item><title>Godot Plugin Tool Script Type Detection</title><link>https://hk47196.github.io/godot-plugin-tool-script-type-detection/</link><pubDate>Thu, 11 Jul 2024 14:20:30 -0400</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/godot-plugin-tool-script-type-detection/</guid><description>&lt;p>When you have a tool script attached to a node in Godot&amp;rsquo;s editor, it will run
both while you are editing the scene, and when it is attached to the editor
itself. You can differentiate between which &amp;lsquo;kind&amp;rsquo; is runnig by checking the
viewport:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c#" data-lang="c#">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> (GetViewport()?.Name != &lt;span style="color:#e6db74">&amp;#34;root&amp;#34;&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The edited scene will not have the root viewport as its nearest viewport.&lt;/p></description></item><item><title>Blender Tip 1</title><link>https://hk47196.github.io/blender-tip-1/</link><pubDate>Sat, 01 Jun 2024 16:47:31 -0400</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/blender-tip-1/</guid><description>&lt;p>In my spare time I do some hard edge modeling &amp;amp; level design in Blender. As
anyone who has used Blender is aware, Blender is rather difficult to learn. I&amp;rsquo;ve
been using it for a very long time off and on, and thought I&amp;rsquo;d share some tips.
To start this out, here&amp;rsquo;s a tip for detecting faces with incorrect normals.&lt;/p>
&lt;p>First, click the &amp;lsquo;Overlays&amp;rsquo; dropdown in the top-right of the 3D editor, and
toggle on &amp;lsquo;Face Orientation&amp;rsquo;:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/blender_tip_0.jpg">
&lt;/p>
&lt;p>Seems simple so far, but this will add a highlight to both faces with their
normal pointing towards and away from the camera. We just want the faces with
the normals pointing away, so we must go into the preferences(Edit →
Preferences). Select the &amp;lsquo;Themes&amp;rsquo; tab on the left, click the &amp;lsquo;3D Viewport&amp;rsquo; tab
on the right to toggle it open. Scroll down to &amp;lsquo;Face Orientation Front&amp;rsquo;:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/blender_tip_1.avif">
&lt;/p>
&lt;p>What you want to do is lower the alpha value of this color to 0, so that there
is no overlay for faces that are oriented towards us. Save your preferences.&lt;/p>
&lt;p>To make sur the overlay is enabled at startup, you must save the scene as your
startup file(File → Defaults → Save Startup File).&lt;/p>
&lt;p>Here&amp;rsquo;s an example of how it should look, with faces that are pointing away
appearing red after I&amp;rsquo;ve removed a face from the default cube:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/blender_tip_2.jpg">
&lt;/p>
&lt;p>Very handy for level design to quickly find faces that need their normal
flipped.&lt;/p></description></item><item><title>Texture Blending 2(With Godot)</title><link>https://hk47196.github.io/texture-blending-2with-godot/</link><pubDate>Sat, 23 Dec 2023 23:53:15 -0500</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/texture-blending-2with-godot/</guid><description>&lt;p>This is a direct continuation of the previous post.&lt;/p>
&lt;p>I created a plane mesh instance which I exported to Blender for subdividing and
UV coloring. We will be using the colors as weights for both blending and
layering.&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/Mix_02_1.jpg">
&lt;/p>
&lt;p>I found Blender to be a bit troublesome for manipulating the colors of
individual vertices, perhaps I&amp;rsquo;ll create a tool for the Godot editor in the
future to easily handle this.&lt;/p>
&lt;p>On the edges will be 100% our R texture, and 100% our G texture in the middle.
Inbetween I added yellow vertices for a nice blend of an overlapping RG layer.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>shader_type spatial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>render_mode blend_mix,depth_draw_opaque,cull_back,diffuse_burley,specular_schlick_ggx;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Macro to define a uniform group for a material layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Each layer (R, G, B) will have its own set of uniforms for various material properties.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define DEFINE_UNIFORM_GROUP(suffix) \&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group_uniforms Material&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span> albedo&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">:&lt;/span> source_color &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span>(&lt;span style="color:#ae81ff">1.0&lt;/span>); &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_albedo&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">:&lt;/span> source_color,filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> roughness&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_range(&lt;span style="color:#ae81ff">0&lt;/span>,&lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_metallic&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_default_white,filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span> metallic_texture_channel&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_roughness&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_roughness_r,filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> specular&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> metallic&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_normal&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_normal,filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> normal_scale&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_range(&lt;span style="color:#f92672">-&lt;/span>&lt;span style="color:#ae81ff">16&lt;/span>,&lt;span style="color:#ae81ff">16&lt;/span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_ambient_occlusion&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_default_white, filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span> ao_texture_channel&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span>(&lt;span style="color:#ae81ff">1.0&lt;/span>, &lt;span style="color:#ae81ff">0.0&lt;/span>, &lt;span style="color:#ae81ff">0.0&lt;/span>, &lt;span style="color:#ae81ff">0.0&lt;/span>); &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> ao_light_affect&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_heightmap&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix&lt;span style="color:#f92672">:&lt;/span> hint_default_black,filter_linear_mipmap,repeat_enable; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> heightmap_scale&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">5.0&lt;/span>; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec2&lt;/span> heightmap_flip&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> group_uniforms;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Define uniforms for three material layers: R, G, and B.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DEFINE_UNIFORM_GROUP(R)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>DEFINE_UNIFORM_GROUP(G)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">//DEFINE_UNIFORM_GROUP(B)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span> uv1_scale &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span>(&lt;span style="color:#ae81ff">1.0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span> uv1_offset;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span> uv2_scale &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span>(&lt;span style="color:#ae81ff">1.0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">vec3&lt;/span> uv2_offset;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Uniform to control the sharpness of the blend.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> BlendSharpness&lt;span style="color:#f92672">:&lt;/span> hint_range(&lt;span style="color:#ae81ff">1.0&lt;/span>,&lt;span style="color:#ae81ff">10.0&lt;/span>) &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">3.0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_noise;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> vertex() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UV&lt;span style="color:#f92672">=&lt;/span>UV&lt;span style="color:#f92672">*&lt;/span>uv1_scale.xy&lt;span style="color:#f92672">+&lt;/span>uv1_offset.xy;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> MaterialProperties {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> albedo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> roughness;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> metallic_texture_channel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> specular;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> metallic;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> normal_scale;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> ao_texture_channel;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> ao_light_affect;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> heightmap_scale;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec2&lt;/span> heightmap_flip;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec2&lt;/span> uv;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> vertex;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> normal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> tangent;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> binormal;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> ProcessMaterialOut {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> albedo;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> metallic;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> roughness;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> specular;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> normal_map;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> normal_map_depth;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> ao;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> ao_light_affect;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> depth;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> height;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Function to process material properties and textures for a layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Applies texture sampling and calculations to derive final material attributes.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ProcessMaterialOut process_material(MaterialProperties props, &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_albedo, &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_heightmap,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_metallic, &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_roughness, &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_normal,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> texture_ambient_occlusion) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProcessMaterialOut p_out;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec2&lt;/span> base_uv;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> view_dir &lt;span style="color:#f92672">=&lt;/span> normalize(normalize(&lt;span style="color:#f92672">-&lt;/span>props.vertex) &lt;span style="color:#f92672">*&lt;/span> mat3(props.tangent &lt;span style="color:#f92672">*&lt;/span> props.heightmap_flip.x, &lt;span style="color:#f92672">-&lt;/span>props.binormal &lt;span style="color:#f92672">*&lt;/span> props.heightmap_flip.y, props.normal));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> height &lt;span style="color:#f92672">=&lt;/span> texture(texture_heightmap, props.uv).r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> depth &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1.0&lt;/span> &lt;span style="color:#f92672">-&lt;/span> height;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.depth &lt;span style="color:#f92672">=&lt;/span> depth;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.height &lt;span style="color:#f92672">=&lt;/span> height;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec2&lt;/span> ofs &lt;span style="color:#f92672">=&lt;/span> props.uv &lt;span style="color:#f92672">-&lt;/span> view_dir.xy &lt;span style="color:#f92672">*&lt;/span> depth &lt;span style="color:#f92672">*&lt;/span> props.heightmap_scale &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0.01&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> base_uv&lt;span style="color:#f92672">=&lt;/span>ofs;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> albedo_tex &lt;span style="color:#f92672">=&lt;/span> texture(texture_albedo,base_uv);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.albedo &lt;span style="color:#f92672">=&lt;/span> props.albedo.rgb &lt;span style="color:#f92672">*&lt;/span> albedo_tex.rgb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> metallic_tex &lt;span style="color:#f92672">=&lt;/span> dot(texture(texture_metallic,base_uv), props.metallic_texture_channel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.metallic &lt;span style="color:#f92672">=&lt;/span> metallic_tex &lt;span style="color:#f92672">*&lt;/span> props.metallic;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> roughness_texture_channel &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#66d9ef">vec4&lt;/span>(&lt;span style="color:#ae81ff">1.0&lt;/span>,&lt;span style="color:#ae81ff">0.0&lt;/span>,&lt;span style="color:#ae81ff">0.0&lt;/span>,&lt;span style="color:#ae81ff">0.0&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> roughness_tex &lt;span style="color:#f92672">=&lt;/span> dot(texture(texture_roughness,base_uv),roughness_texture_channel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.roughness &lt;span style="color:#f92672">=&lt;/span> roughness_tex &lt;span style="color:#f92672">*&lt;/span> props.roughness;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.specular &lt;span style="color:#f92672">=&lt;/span> props.specular;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.normal_map &lt;span style="color:#f92672">=&lt;/span> texture(texture_normal,base_uv).rgb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.normal_map_depth &lt;span style="color:#f92672">=&lt;/span> props.normal_scale;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.ao &lt;span style="color:#f92672">=&lt;/span> dot(texture(texture_ambient_occlusion,base_uv),props.ao_texture_channel);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> p_out.ao_light_affect &lt;span style="color:#f92672">=&lt;/span> props.ao_light_affect;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> p_out;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Utility function to reconstruct the Z component of a normal vector from its X and Y components.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> reconstructZ(&lt;span style="color:#66d9ef">vec3&lt;/span> norm) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> sqrt(max(&lt;span style="color:#ae81ff">0.0&lt;/span>, &lt;span style="color:#ae81ff">1.0&lt;/span> &lt;span style="color:#f92672">-&lt;/span> dot(norm.xy, norm.xy)));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">// Macro to initialize material properties for a layer based on the defined uniforms.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#define DEFINE_MATERIAL_PROPERTIES(suffix) \&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> MaterialProperties mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.albedo &lt;span style="color:#f92672">=&lt;/span> albedo&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.roughness &lt;span style="color:#f92672">=&lt;/span> roughness&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.metallic_texture_channel &lt;span style="color:#f92672">=&lt;/span> metallic_texture_channel&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.specular &lt;span style="color:#f92672">=&lt;/span> specular&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.metallic &lt;span style="color:#f92672">=&lt;/span> metallic&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.normal_scale &lt;span style="color:#f92672">=&lt;/span> normal_scale&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.ao_texture_channel &lt;span style="color:#f92672">=&lt;/span> ao_texture_channel&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.ao_light_affect &lt;span style="color:#f92672">=&lt;/span> ao_light_affect&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.heightmap_scale &lt;span style="color:#f92672">=&lt;/span> heightmap_scale&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.heightmap_flip &lt;span style="color:#f92672">=&lt;/span> heightmap_flip&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.uv &lt;span style="color:#f92672">=&lt;/span> UV; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.vertex &lt;span style="color:#f92672">=&lt;/span> VERTEX; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.normal &lt;span style="color:#f92672">=&lt;/span> NORMAL; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.tangent &lt;span style="color:#f92672">=&lt;/span> TANGENT; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix.binormal &lt;span style="color:#f92672">=&lt;/span> BINORMAL; &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">out&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix &lt;span style="color:#f92672">=&lt;/span> process_material(mat&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, texture_albedo&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, texture_heightmap&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, texture_metallic&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, &lt;span style="color:#960050;background-color:#1e0010">\&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> texture_roughness&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, texture_normal&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix, texture_ambient_occlusion&lt;span style="color:#960050;background-color:#1e0010">##&lt;/span>suffix);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> fragment() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Output structures for each layer (R, G, B) after processing their material properties.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProcessMaterialOut outR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ProcessMaterialOut outG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//ProcessMaterialOut outB;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Initialize material properties for each layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DEFINE_MATERIAL_PROPERTIES(R)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DEFINE_MATERIAL_PROPERTIES(G)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// DEFINE_MATERIAL_PROPERTIES(B)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Ensure that vertex colors are non-negative.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> vcolor &lt;span style="color:#f92672">=&lt;/span> max(COLOR.rgb, &lt;span style="color:#66d9ef">vec3&lt;/span>(&lt;span style="color:#ae81ff">0.0&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Calculate blending weights for each layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// These weights are based on the red, green, and the minimum of red and green components of the vertex color.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> weightR &lt;span style="color:#f92672">=&lt;/span> vcolor.r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> weightG &lt;span style="color:#f92672">=&lt;/span> vcolor.g;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> weightRG &lt;span style="color:#f92672">=&lt;/span> min(weightR, weightG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> adjWeightR &lt;span style="color:#f92672">=&lt;/span> pow(max(weightR &lt;span style="color:#f92672">-&lt;/span> weightRG, &lt;span style="color:#ae81ff">0.0&lt;/span>), BlendSharpness);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> adjWeightG &lt;span style="color:#f92672">=&lt;/span> pow(max(weightG &lt;span style="color:#f92672">-&lt;/span> weightRG, &lt;span style="color:#ae81ff">0.0&lt;/span>), BlendSharpness);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> adjWeightRG &lt;span style="color:#f92672">=&lt;/span> pow(weightRG, BlendSharpness);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> totalWeight &lt;span style="color:#f92672">=&lt;/span> adjWeightR &lt;span style="color:#f92672">+&lt;/span> adjWeightG &lt;span style="color:#f92672">+&lt;/span> adjWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Normalize the weights so they sum up to 1.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> normWeightR &lt;span style="color:#f92672">=&lt;/span> adjWeightR &lt;span style="color:#f92672">/&lt;/span> totalWeight;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> normWeightG &lt;span style="color:#f92672">=&lt;/span> adjWeightG &lt;span style="color:#f92672">/&lt;/span> totalWeight;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> normWeightRG &lt;span style="color:#f92672">=&lt;/span> adjWeightRG &lt;span style="color:#f92672">/&lt;/span> totalWeight;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Determine the step function for layer height comparison.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// This is used to create a sharp transition between layers based on their height.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> stepRG &lt;span style="color:#f92672">=&lt;/span> step(outR.height, outG.height);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Blend the albedo, roughness, specular, normal map, and other properties from each layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// The blending is based on the normalized weights and the height-based step function.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Each property is blended separately.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerColorR &lt;span style="color:#f92672">=&lt;/span> outR.albedo &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerColorG &lt;span style="color:#f92672">=&lt;/span> outG.albedo &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerColorRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.albedo, outG.albedo, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerRoughnessR &lt;span style="color:#f92672">=&lt;/span> outR.roughness &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerRoughnessG &lt;span style="color:#f92672">=&lt;/span> outG.roughness &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerRoughnessRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.roughness, outG.roughness, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerSpecularR &lt;span style="color:#f92672">=&lt;/span> outR.specular &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerSpecularG &lt;span style="color:#f92672">=&lt;/span> outG.specular &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerSpecularRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.specular, outG.specular, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Normal map blending is handled with care to preserve correct surface details.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalR &lt;span style="color:#f92672">=&lt;/span> outR.normal_map ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalR.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalR &lt;span style="color:#f92672">*=&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalG &lt;span style="color:#f92672">=&lt;/span> outG.normal_map;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalG.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalG &lt;span style="color:#f92672">*=&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// This is correct, the step creates a sharp transition, no lerp happens.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.normal_map, outG.normal_map, stepRG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalRG.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalRG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> layerNormalRG &lt;span style="color:#f92672">*=&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Blend the normal map depth, ambient occlusion, and AO light affect from each layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerNormalDepthR &lt;span style="color:#f92672">=&lt;/span> outR.normal_map_depth &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerNormalDepthG &lt;span style="color:#f92672">=&lt;/span> outG.normal_map_depth &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerNormalDepthRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.normal_map_depth, outG.normal_map_depth, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoR &lt;span style="color:#f92672">=&lt;/span> outR.ao &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoG &lt;span style="color:#f92672">=&lt;/span> outG.ao &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.ao, outG.ao, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoLightR &lt;span style="color:#f92672">=&lt;/span> outR.ao_light_affect &lt;span style="color:#f92672">*&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoLightG &lt;span style="color:#f92672">=&lt;/span> outG.ao_light_affect &lt;span style="color:#f92672">*&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> layerAoLightRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.ao_light_affect, outG.ao_light_affect, stepRG) &lt;span style="color:#f92672">*&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Final composition of the material properties.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// The properties from each layer are added together based on their respective weights.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALBEDO &lt;span style="color:#f92672">=&lt;/span> layerColorR &lt;span style="color:#f92672">+&lt;/span> layerColorG &lt;span style="color:#f92672">+&lt;/span> layerColorRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ROUGHNESS &lt;span style="color:#f92672">=&lt;/span> layerRoughnessR &lt;span style="color:#f92672">+&lt;/span> layerRoughnessG &lt;span style="color:#f92672">+&lt;/span> layerRoughnessRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> SPECULAR &lt;span style="color:#f92672">=&lt;/span> layerSpecularR &lt;span style="color:#f92672">+&lt;/span> layerSpecularG &lt;span style="color:#f92672">+&lt;/span> layerSpecularRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NORMAL_MAP &lt;span style="color:#f92672">=&lt;/span> normalize(layerNormalR &lt;span style="color:#f92672">+&lt;/span> layerNormalG &lt;span style="color:#f92672">+&lt;/span> layerNormalRG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NORMAL_MAP_DEPTH &lt;span style="color:#f92672">=&lt;/span> layerNormalDepthR &lt;span style="color:#f92672">+&lt;/span> layerNormalDepthG &lt;span style="color:#f92672">+&lt;/span> layerNormalDepthRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AO &lt;span style="color:#f92672">=&lt;/span> layerAoR &lt;span style="color:#f92672">+&lt;/span> layerAoG &lt;span style="color:#f92672">+&lt;/span> layerAoRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> AO_LIGHT_AFFECT &lt;span style="color:#f92672">=&lt;/span> layerAoLightR &lt;span style="color:#f92672">+&lt;/span> layerAoLightG &lt;span style="color:#f92672">+&lt;/span> layerAoLightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>That&amp;rsquo;s a lot of code, but don&amp;rsquo;t be intimidated. It&amp;rsquo;s from the Standard Material
3D shader with the properties we want. The calculations were extracted to a
function, and we use preprocessor macros to generate our layer code.&lt;/p>
&lt;p>This could be extended to blending between three sub-materials, but it would go
from 3 possible combinations to 7. That&amp;rsquo;s a lot of operations.&lt;/p>
&lt;p>Our final output:
&lt;img src="https://hk47196.github.io/images/Mix_02_2.jpg">
&lt;/p>
&lt;p>We can see where the yellow vertices are is a nice overlapping layer of dirt and
stone which creates a pleasant layer to blend between the two.&lt;/p>
&lt;p>The code we&amp;rsquo;re most interested in is after:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Initialize material properties for each layer.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DEFINE_MATERIAL_PROPERTIES(R)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> DEFINE_MATERIAL_PROPERTIES(G)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The sub-materials are applied based on weighting for R, G, and RG. Recall that
RG is our R+G height-based layered sub-material.&lt;/p>
&lt;p>This code &lt;em>may&lt;/em> be unfamiliar:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> stepRG &lt;span style="color:#f92672">=&lt;/span> step(outR.height, outG.height);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[&lt;span style="color:#960050;background-color:#1e0010">…&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>mix(outR.roughness, outG.roughness, stepRG)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>It&amp;rsquo;s just an optimization to skip conditionals. stepRG is always 0.0 or 1.0,
therefore we&amp;rsquo;re always getting 100% of outR&amp;rsquo;s value or outG&amp;rsquo;s value based on the
value in the height maps at our fragment&amp;rsquo;s location.&lt;/p>
&lt;p>We use a sharpness parameter to control the sharpness of the blends between
sub-materials:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> adjWeightR &lt;span style="color:#f92672">=&lt;/span> pow(max(weightR &lt;span style="color:#f92672">-&lt;/span> weightRG, &lt;span style="color:#ae81ff">0.0&lt;/span>), BlendSharpness);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> adjWeightG &lt;span style="color:#f92672">=&lt;/span> pow(max(weightG &lt;span style="color:#f92672">-&lt;/span> weightRG, &lt;span style="color:#ae81ff">0.0&lt;/span>), BlendSharpness);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">float&lt;/span> adjWeightRG &lt;span style="color:#f92672">=&lt;/span> pow(weightRG, BlendSharpness);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Sharpness value of 1.0:
&lt;img src="https://hk47196.github.io/images/Mix_02_Sharpness1.jpg">
&lt;/p>
&lt;p>Sharpness value of 3.0:
&lt;img src="https://hk47196.github.io/images/Mix_02_Sharpness3.jpg">
&lt;/p>
&lt;p>To be able to properly weight &amp;amp; normalize our normal vectors, we have to
reconstruct the Z parameter. Normal maps pack R&amp;amp;G(x, y) together.
After reconstruction, we nlerp between them based on their respective vertex
color weights. This probably doesn&amp;rsquo;t look too good if there&amp;rsquo;s a significant
angle difference, but as this is for ground materials I&amp;rsquo;ll probably be OK.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalR &lt;span style="color:#f92672">=&lt;/span> outR.normal_map ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalR.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalR &lt;span style="color:#f92672">*=&lt;/span> normWeightR;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalG &lt;span style="color:#f92672">=&lt;/span> outG.normal_map;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalG.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalG &lt;span style="color:#f92672">*=&lt;/span> normWeightG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">vec3&lt;/span> layerNormalRG &lt;span style="color:#f92672">=&lt;/span> mix(outR.normal_map, outG.normal_map, stepRG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalRG.z &lt;span style="color:#f92672">=&lt;/span> reconstructZ(layerNormalRG);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>layerNormalRG &lt;span style="color:#f92672">*=&lt;/span> normWeightRG;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[&lt;span style="color:#960050;background-color:#1e0010">…&lt;/span>]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>NORMAL_MAP &lt;span style="color:#f92672">=&lt;/span> normalize(layerNormalR &lt;span style="color:#f92672">+&lt;/span> layerNormalG &lt;span style="color:#f92672">+&lt;/span> layerNormalRG);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></description></item><item><title>Texture Blending(With Godot)</title><link>https://hk47196.github.io/texture-blendingwith-godot/</link><pubDate>Sat, 23 Dec 2023 04:16:50 -0500</pubDate><author>rogerswaggoner@gmail.com (Roger Waggoner)</author><guid>https://hk47196.github.io/texture-blendingwith-godot/</guid><description>&lt;p>We&amp;rsquo;re going to start by obtaining two textures that will showcase blending well,
one texture will be approximately average height, and the other will have lots
of highs and lows.&lt;/p>
&lt;p>AmbientCG provides CC-0 textures.&lt;/p>
&lt;p>For the first, we have some dirt: &lt;a href="https://ambientcg.com/view?id=Ground042">https://ambientcg.com/view?id=Ground042&lt;/a>
And for the second a brick material: &lt;a href="https://ambientcg.com/view?id=Bricks076A">https://ambientcg.com/view?id=Bricks076A&lt;/a>&lt;/p>
&lt;p>We can start by creating a simple Godot shader to blend between two textures.
I&amp;rsquo;ll be using a Godot primitive plane, and therefore its default UV coordinates
will work fine for us.
Create a MeshPrimitive node, assign a Plane shape, and set the material
override as a new ShaderMaterial. Assign our new shader to that
ShaderMaterial.&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>shader_type spatial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureB;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> fragment() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorA &lt;span style="color:#f92672">=&lt;/span> texture(textureA, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorB &lt;span style="color:#f92672">=&lt;/span> texture(textureB, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> blenderFactor &lt;span style="color:#f92672">=&lt;/span> UV.x;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> blendedColor &lt;span style="color:#f92672">=&lt;/span> mix(colorA, colorB, blenderFactor);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALBEDO &lt;span style="color:#f92672">=&lt;/span> blendedColor.rgb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>
&lt;img src="https://hk47196.github.io/images/Mix1.jpg">
&lt;/p>
&lt;p>And we&amp;rsquo;re done!
…No? Not good enough? Fine.&lt;/p>
&lt;p>What if instead of using a simple &lt;code>mix&lt;/code> we used something else to
control the blending?&lt;/p>
&lt;p>We can use the height maps of each material to control how they layer
over each other like so:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>shader_type spatial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureB;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> heightTexA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> heightTexB;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> fragment() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorA &lt;span style="color:#f92672">=&lt;/span> texture(textureA, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorB &lt;span style="color:#f92672">=&lt;/span> texture(textureB, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> heightA &lt;span style="color:#f92672">=&lt;/span> texture(heightTexA, UV).r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> heightB &lt;span style="color:#f92672">=&lt;/span> texture(heightTexB, UV).r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> blenderFactor &lt;span style="color:#f92672">=&lt;/span> UV.x;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> blendedColor &lt;span style="color:#f92672">=&lt;/span> mix(colorA, colorB, blenderFactor);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// NOTE: this can be converted to a mix + step, as seen later.&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (heightA &lt;span style="color:#f92672">&amp;gt;=&lt;/span> heightB) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALBEDO &lt;span style="color:#f92672">=&lt;/span> colorA.rgb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALBEDO &lt;span style="color:#f92672">=&lt;/span> colorB.rgb;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And while that does produce a rather nice-looking result:&lt;/p>
&lt;p>
&lt;img src="https://hk47196.github.io/images/Mix2.jpg">
&lt;/p>
&lt;p>It&amp;rsquo;s not &lt;em>actually&lt;/em> what we&amp;rsquo;re looking for. We can differentiate between the
first as blending, and the second as layering. If we wanted to smoothly
transition from our dirt to our bricks then, we could use both.&lt;/p>
&lt;p>Assume that at UV.x = 0, we just have bricks.
And at UV.x = 1, we have dirt.
Between this, is our blend.&lt;/p>
&lt;p>Therefore, we have three colors. Brick, BrickDirt, Dirt.&lt;/p>
&lt;p>Therefore, for this post, we have our final shader code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-glsl" data-lang="glsl">&lt;span style="display:flex;">&lt;span>shader_type spatial;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> textureB;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> heightTexA;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">uniform&lt;/span> &lt;span style="color:#66d9ef">sampler2D&lt;/span> heightTexB;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">vec3&lt;/span> triMix(&lt;span style="color:#66d9ef">vec3&lt;/span> colorA, &lt;span style="color:#66d9ef">vec3&lt;/span> colorB, &lt;span style="color:#66d9ef">vec3&lt;/span> colorC, &lt;span style="color:#66d9ef">float&lt;/span> t) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> mixAB &lt;span style="color:#f92672">=&lt;/span> mix(colorA, colorB, t &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2.0&lt;/span>); &lt;span style="color:#75715e">// Mix between colorA and colorB for t in [0, 0.5]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> mixBC &lt;span style="color:#f92672">=&lt;/span> mix(colorB, colorC, (t &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">0.5&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">2.0&lt;/span>); &lt;span style="color:#75715e">// Mix between colorB and colorC for t in [0.5, 1]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> mix(mixAB, mixBC, step(&lt;span style="color:#ae81ff">0.5&lt;/span>, t)); &lt;span style="color:#75715e">// Blend between mixAB and mixBC at t = 0.5&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> fragment() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorA &lt;span style="color:#f92672">=&lt;/span> texture(textureA, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec4&lt;/span> colorB &lt;span style="color:#f92672">=&lt;/span> texture(textureB, UV);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> heightA &lt;span style="color:#f92672">=&lt;/span> texture(heightTexA, UV).r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> heightB &lt;span style="color:#f92672">=&lt;/span> texture(heightTexB, UV).r;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> blendFactor &lt;span style="color:#f92672">=&lt;/span> UV.x;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// If substitued with step+mix&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">vec3&lt;/span> layerColor &lt;span style="color:#f92672">=&lt;/span> mix(colorB.rgb, colorA.rgb, step(heightB, heightA));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ALBEDO &lt;span style="color:#f92672">=&lt;/span> triMix(colorA.rgb, layerColor, colorB.rgb, blendFactor);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>
&lt;img src="https://hk47196.github.io/images/Mix3.jpg">
&lt;/p>
&lt;p>The blend itself could be controlled by adding more parameters to the three-way
mix.
We also aren&amp;rsquo;t limited to &lt;code>mix&lt;/code> either. It&amp;rsquo;s common to use a texture to control
the blending. For example, Valve&amp;rsquo;s Hammer makes use of a &lt;a href="https://developer.valvesoftware.com/wiki/$blendmodulatetexture">modulation texture&lt;/a>.&lt;/p>
&lt;p>For my purposes, however, this is exactly what I need, and will expand upon that
in a future post.&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>&lt;a href="https://hk47196.github.io/images/Mix4.jpg">Screenshot of the final node setup.&lt;/a>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></description></item></channel></rss>