<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Chanwoo</title>
    <description>The latest articles on DEV Community by Chanwoo (@enghqii).</description>
    <link>https://dev.to/enghqii</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1461897%2F371ae7e2-b05f-4bcb-a6d2-a9c6fa9b3c19.jpeg</url>
      <title>DEV Community: Chanwoo</title>
      <link>https://dev.to/enghqii</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/enghqii"/>
    <language>en</language>
    <item>
      <title>Obtaining the Cubic Hermite Spline Derivative on an Arbitrary Interval</title>
      <dc:creator>Chanwoo</dc:creator>
      <pubDate>Sun, 12 May 2024 04:50:05 +0000</pubDate>
      <link>https://dev.to/enghqii/obtaining-the-cubic-hermite-spline-derivative-on-an-arbitrary-interval-1hio</link>
      <guid>https://dev.to/enghqii/obtaining-the-cubic-hermite-spline-derivative-on-an-arbitrary-interval-1hio</guid>
      <description>&lt;p&gt;Recently, I had to work with the Hermite spline for the first time in a while. I realized that I had forgotten some details about how it works. I decided to wrap it up here while I was at it.&lt;br&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  1. Finding Hermite Spline
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1.1. The 4 constraints
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P1&lt;/code&gt;: The initial position, &lt;code&gt;P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m1&lt;/code&gt;: The end point position, &lt;code&gt;Q&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;P2&lt;/code&gt;: The slope/velocity at the initial position, &lt;code&gt;P&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;m2&lt;/code&gt;: The slope/velocity at the end point, &lt;code&gt;Q&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu91e0hngn906g1yogamz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu91e0hngn906g1yogamz.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The red points represent &lt;code&gt;P1&lt;/code&gt;, &lt;code&gt;P2&lt;/code&gt;, while the blue slopes represent &lt;code&gt;m1&lt;/code&gt;, and &lt;code&gt;m2&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;Since it is expressed as a cubic function that has 4 coefficients, it can have 4 constraints. The constraints of Hermite spline are the positions and the velocities (=slope) of the start and end points.&lt;/p&gt;
&lt;h3&gt;
  
  
  1.2. Finding the blending functions
&lt;/h3&gt;

&lt;p&gt;Let's consider the Hermite spline to be obtained, denoted as &lt;code&gt;P(t)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This, a nondescript cubic function, can be represented in a multiplication of 2 matrices. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lhda3b964nqexewrqko.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9lhda3b964nqexewrqko.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A multiplication of a &lt;u&gt;1-by-4&lt;/u&gt; matrix and a &lt;u&gt;4-by-1&lt;/u&gt; matrix yields a &lt;u&gt;1-by-1&lt;/u&gt; matrix. The one and only element in the matrix would be the evaluation of the spline given the &lt;code&gt;t&lt;/code&gt; value.&lt;/p&gt;

&lt;p&gt;To be able to evaluate the Hermite spline function, we need to figure out what the &lt;code&gt;A&lt;/code&gt; matrix consist of. The &lt;code&gt;A&lt;/code&gt; matrix comprises the 4 coefficients of a cubic polynomial function, the typical &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt;, and &lt;code&gt;d&lt;/code&gt;, as we all are familiar with.&lt;/p&gt;

&lt;p&gt;The 4 constraints kick in here to determine the coefficients. &lt;/p&gt;

&lt;p&gt;By definition, &lt;code&gt;P1&lt;/code&gt; represents the value &lt;code&gt;P(t)&lt;/code&gt; where &lt;code&gt;t = 0&lt;/code&gt;, and &lt;code&gt;P2&lt;/code&gt; does, &lt;code&gt;P(1)&lt;/code&gt;. &lt;code&gt;m1&lt;/code&gt; and &lt;code&gt;m2&lt;/code&gt; represents the slope of &lt;code&gt;P(t)&lt;/code&gt; where &lt;code&gt;t = 0, 1&lt;/code&gt; respectively.&lt;/p&gt;

&lt;p&gt;Substituting each &lt;code&gt;t&lt;/code&gt; values in the &lt;code&gt;T&lt;/code&gt; matrix with 0 and 1 accordingly, the constraints can be grouped as below. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjggdoka4jrcjc1esz0dg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjggdoka4jrcjc1esz0dg.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To rearrange only the lateral sides of the equation using matrices, We could represent the matrix C (as in &lt;em&gt;Constraints&lt;/em&gt;) as a multiplication of the coefficient matrix above and the &lt;code&gt;A&lt;/code&gt; matrix. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcgymq03ygn4hbt712u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5zcgymq03ygn4hbt712u.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By rearranging the equation around it, We could represent &lt;code&gt;A&lt;/code&gt; in the form above. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: When finding an inverse matrix, &lt;a href="https://matrix.reshish.com/inverse.php" rel="noopener noreferrer"&gt;https://matrix.reshish.com/inverse.php&lt;/a&gt; comes in quite handy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcntf0arai05qqc1da1pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcntf0arai05qqc1da1pk.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;By substituting the newly found &lt;code&gt;A&lt;/code&gt; matrix into the existing expression &lt;code&gt;P(t)&lt;/code&gt;, we get the expression above. &lt;/p&gt;

&lt;p&gt;We can take one step further and arrange the equation in the form of &lt;code&gt;ax + by + cz + d = 0&lt;/code&gt;, but it's not gonna be useful. We will want to have it parameterized by the constraints, As your code will likely provide the constrains, not the coefficients. We will want to plug in them as-is.&lt;/p&gt;

&lt;p&gt;The polynomial multiplied by each constraint is called a &lt;strong&gt;basis function&lt;/strong&gt; or a &lt;strong&gt;blending function&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lptuh1d53g8yls9gbfq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4lptuh1d53g8yls9gbfq.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, in conclusion, the spline, and its derivative could be represented in the form below, where &lt;code&gt;0 &amp;lt;= t &amp;lt;= 1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5uoeqhxdncungrs9ylv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd5uoeqhxdncungrs9ylv.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. On an arbitrary interval &lt;code&gt;[x_{k}, x_{k+1}]&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;The Hermite spline obtained above is valid only in the interval where &lt;code&gt;t&lt;/code&gt; is in the canonical &lt;code&gt;[0, 1]&lt;/code&gt; interval. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When &lt;code&gt;t = 0&lt;/code&gt;, the evaluation and the slope would be &lt;code&gt;P1&lt;/code&gt;, and &lt;code&gt;m1&lt;/code&gt; .&lt;/li&gt;
&lt;li&gt;When &lt;code&gt;t = 1&lt;/code&gt;, they would be &lt;code&gt;P2&lt;/code&gt; and &lt;code&gt;m2&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the ranges before and after 0 and 1, the evaluated values and the slopes will be completely meaningless to the given constraints setting, as it only works in the canonical [0, 1] range. &lt;/p&gt;

&lt;p&gt;However, there are times when we want to move &lt;code&gt;t&lt;/code&gt; along an arbitrary interval, but the canonical one. For example, what if we want to interpolate the values ​​with the desired position and velocity when &lt;code&gt;t&lt;/code&gt; is in the interval &lt;code&gt;[3, 5]&lt;/code&gt;? &lt;/p&gt;

&lt;p&gt;You might think: &lt;em&gt;'I can just normalize &lt;code&gt;t&lt;/code&gt; to the length of the interval, to map &lt;code&gt;[3, 5]&lt;/code&gt; to &lt;code&gt;[0, 1]&lt;/code&gt;. Simple 1:1 interpolation. It would work, right?'&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Nope, it didn't work.&lt;br&gt;
 &lt;br&gt;
For instance, let's say you want to find a Hermite spline with the constraints setting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starting point: (position: 1, velocity: 1)

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P1&lt;/code&gt; = 1, &lt;code&gt;m1&lt;/code&gt; = 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;End point: (position: 0.7, velocity: 0)

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;P2&lt;/code&gt; = 0.7, &lt;code&gt;m2&lt;/code&gt; = 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This will yield a spline valid only in the &lt;code&gt;[0, 1]&lt;/code&gt; range. We could map the parameter &lt;code&gt;t&lt;/code&gt; from it to the &lt;code&gt;[3, 5]&lt;/code&gt; range by normalizing it as below.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;tnormalized=(t−3)/(5−3)
t_{normalized} = (t - 3) / (5 - 3)
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;t&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;n&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;or&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;ma&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;l&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;i&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;ze&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;d&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal"&gt;t&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;span class="mord"&gt;/&lt;/span&gt;&lt;span class="mopen"&gt;(&lt;/span&gt;&lt;span class="mord"&gt;5&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;−&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;3&lt;/span&gt;&lt;span class="mclose"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;However, after the normalization, what you're getting is the curve in black color, whereas the actual curve we intended to have is in green color.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1suj827xbbxeilzz1z5q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1suj827xbbxeilzz1z5q.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The orange curve is the spline drawn in the normalized interval.)&lt;/p&gt;

&lt;p&gt;When &lt;code&gt;t&lt;/code&gt; is normalized as above, the original orange graph on the left (&lt;code&gt;[0, 1]&lt;/code&gt; section) gets stretched left and right by the length of the target interval, then moved to the side by the beginning value of the target interval, yielding the black graph.&lt;/p&gt;

&lt;p&gt;Stretching the graph left and right like this, &lt;strong&gt;the slope (=velocity) of the graph will be stretched as well&lt;/strong&gt;, resulting in a stretched velocity different from the original. As this creates a spline that does not align with the intended constraints, &lt;strong&gt;this is an incorrect way to map the intervals.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What we want is the &lt;u&gt;green graph&lt;/u&gt;, where the slope remains consistent across the range aligning with the initial constraints. To achieve this, we need to &lt;strong&gt;compensate&lt;/strong&gt; for the reduced velocity that results from stretching the graph horizontally. To do this, simply &lt;strong&gt;multiply&lt;/strong&gt; the velocity &lt;strong&gt;by the length of the interval&lt;/strong&gt; to account for the stretch.&lt;/p&gt;

&lt;p&gt;The spline curve &lt;code&gt;P(x)&lt;/code&gt;, the green one, on an arbitrary section below is as follows. &lt;/p&gt;


&lt;div class="katex-element"&gt;
  &lt;span class="katex-display"&gt;&lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;interval=[xk,xk+1]
interval = [x_k, x_{k+1}]
&lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;in&lt;/span&gt;&lt;span class="mord mathnormal"&gt;t&lt;/span&gt;&lt;span class="mord mathnormal"&gt;er&lt;/span&gt;&lt;span class="mord mathnormal"&gt;v&lt;/span&gt;&lt;span class="mord mathnormal"&gt;a&lt;/span&gt;&lt;span class="mord mathnormal"&gt;l&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mopen"&gt;[&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;k&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mpunct"&gt;,&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mord mathnormal"&gt;x&lt;/span&gt;&lt;span class="msupsub"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;k&lt;/span&gt;&lt;span class="mbin mtight"&gt;+&lt;/span&gt;&lt;span class="mord mtight"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmorjayixuf5mxoin5j10.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmorjayixuf5mxoin5j10.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44zd4ljkwno152f0lxbo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44zd4ljkwno152f0lxbo.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;p_k&lt;/code&gt;, &lt;code&gt;p_{k+1}&lt;/code&gt; are the positions, &lt;code&gt;m_k&lt;/code&gt;, &lt;code&gt;m_{k+1}&lt;/code&gt; are the velocity. &lt;/p&gt;

&lt;p&gt;You can see that the velocity constraints are multiplied by the length of the interval for stretch compensation.&lt;/p&gt;

&lt;p&gt;This slope factor compensation is also mentioned in the &lt;a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Interpolation_on_an_arbitrary_interval" rel="noopener noreferrer"&gt;Wikipedia article&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Derivative of the Cubic Hermit Spline
&lt;/h2&gt;

&lt;p&gt;The code I was working on required me to provide &lt;strong&gt;the derivative of the cubic Hermite spline&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The derivative of spline &lt;code&gt;P(x)&lt;/code&gt; on an arbitrary interval is as follows. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcysstznn4b0ykeriikd7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcysstznn4b0ykeriikd7.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Since the constraints are mere scalars, they could be placed out of the differentiation brackets)&lt;/p&gt;

&lt;p&gt;However, the blending functions are functions of &lt;code&gt;t&lt;/code&gt;, not of &lt;code&gt;x&lt;/code&gt;. Currently, they are differentiated with respect to &lt;code&gt;x&lt;/code&gt;, so they need to be converted to differentiation with respect to &lt;code&gt;t&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In fact, conveniently, the derivatives of each function with respect to &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;t&lt;/code&gt; have the following relationship.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnza1mtcist4wh59t0m7p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnza1mtcist4wh59t0m7p.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's take &lt;code&gt;h_00(t)&lt;/code&gt; as an example. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;First, substitute &lt;code&gt;t&lt;/code&gt; with &lt;code&gt;x&lt;/code&gt; and differentiate&lt;/li&gt;
&lt;li&gt;Then, rearrange it for &lt;code&gt;t&lt;/code&gt;,&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;as shown below.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotkd5w62f5b10ge9zw5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotkd5w62f5b10ge9zw5x.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea7kcle6imcbag5wiqu2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fea7kcle6imcbag5wiqu2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The numerator of the final result is &lt;strong&gt;identical&lt;/strong&gt; to &lt;u&gt;the derivative of &lt;code&gt;h00(t)&lt;/code&gt; with respect to &lt;code&gt;t&lt;/code&gt;&lt;/u&gt;. Substituting yields the form on the right side above.&lt;/p&gt;

&lt;p&gt;This pattern unfolds similarly for the other blending functions, leading to the following final result.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frqpax6endpz57wuwx0i2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frqpax6endpz57wuwx0i2.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(As for the slope constraints, since &lt;code&gt;(x_{k+1} - x_{k})&lt;/code&gt; is already in the numerator, it cancels itself out.)&lt;/p&gt;

&lt;p&gt;That is, in fact, the green graph shown above. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgueu9bqxwadkdix9186.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flgueu9bqxwadkdix9186.png" alt="Image description"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The black graph is the incorrectly stretched one.&lt;/li&gt;
&lt;li&gt;The green graph is the correctly stretched, and compensated one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of code, I have parameterized the derivative of a cubic Hermite spline in the Desmos link below so that you can examine it. &lt;/p&gt;

&lt;p&gt;Try changing the value of &lt;code&gt;t_{slider}&lt;/code&gt; and observe how the slope graph touches and moves along the green curve.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.desmos.com/calculator/h8taue3l9q" rel="noopener noreferrer"&gt;https://www.desmos.com/calculator/h8taue3l9q&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: When calculating derivatives, this website comes in handy: &lt;a href="https://www.derivative-calculator.net/" rel="noopener noreferrer"&gt;https://www.derivative-calculator.net/&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Understanding the Hermite Spline Constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cubic Hermite spline is defined by four constraints: the initial and final positions (&lt;code&gt;P1&lt;/code&gt; and &lt;code&gt;P2&lt;/code&gt;) and their corresponding velocities (&lt;code&gt;m1&lt;/code&gt; and &lt;code&gt;m2&lt;/code&gt;). &lt;/li&gt;
&lt;li&gt;Understanding these constraints and their relationship to the cubic polynomial's coefficients helps you draw the blending functions of the cubic Hermite spline. &lt;/li&gt;
&lt;li&gt;This step is crucial for mapping the spline's range and performing further differentiation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mapping from &lt;code&gt;[0, 1]&lt;/code&gt; to another Arbitrary Range:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mapping the Hermite spline from the canonical &lt;code&gt;[0, 1]&lt;/code&gt; interval to an arbitrary range &lt;code&gt;[x, y]&lt;/code&gt; requires compensation for the interval's lateral scaling effect. &lt;/li&gt;
&lt;li&gt;The velocity constraints, need to be adjusted to maintain the initially desired spline across different ranges. &lt;/li&gt;
&lt;li&gt;This makes sure that the spline accurately interpolates the points and maintains the correct curvature.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deriving the Spline's Derivative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is necessary to find the derivative with respect to &lt;code&gt;x&lt;/code&gt; and then represent it as a derivative with respect to &lt;code&gt;t&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Together with the arbitrary range mapping above, be aware that velocity compensation must also be included. Pay close attention to which terms are canceled out in this process.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>gamedev</category>
      <category>spline</category>
      <category>derivative</category>
      <category>math</category>
    </item>
    <item>
      <title>Interpolating Between 2 Aspect Ratios in a Slightly Overcomplicated Way</title>
      <dc:creator>Chanwoo</dc:creator>
      <pubDate>Mon, 06 May 2024 14:45:21 +0000</pubDate>
      <link>https://dev.to/enghqii/lerping-between-2-aspect-ratios-1l8b</link>
      <guid>https://dev.to/enghqii/lerping-between-2-aspect-ratios-1l8b</guid>
      <description>&lt;p&gt;The other day, I had to work on a simple Lerp calculation for a Unity project. It was about lerping (= Linear Interpolation) an offset value from &lt;code&gt;3.0f&lt;/code&gt; to &lt;code&gt;4.0f&lt;/code&gt;, depending on the current window aspect ratio. &lt;/p&gt;

&lt;p&gt;The initial specifications from the director were quite straightforward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In 'Vertical Mode', when the game's window is narrow, the offset should be at the lower end (&lt;code&gt;3.0f&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;In 'Horizontal Mode', when the window is wide, the offset should be at the upper end (&lt;code&gt;4.0f&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;To prevent the offset from becoming too extreme, the value should be capped at each end:

&lt;ul&gt;
&lt;li&gt;If the aspect ratio becomes smaller than &lt;code&gt;1080 * 1920&lt;/code&gt; (a 9 : 16 aspect ratio), the offset should be capped at &lt;code&gt;3.0f&lt;/code&gt;, and should not be smaller than the cap. &lt;/li&gt;
&lt;li&gt;If the ratio exceeds &lt;code&gt;1920 * 1080&lt;/code&gt; (a 16 : 9 ratio), the offset should be capped at &lt;code&gt;4.0f&lt;/code&gt;. No greater than that.&lt;/li&gt;
&lt;li&gt;When the aspect ratio is 1:1, the offset value should be at 50% of the range, i.e., &lt;code&gt;(3.0 + 4.0) / 2 = 3.5&lt;/code&gt;. &lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, users can freely adjust the window size to whatever they want, hence the need for capping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0ntdidapt9eur86tof.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2For0ntdidapt9eur86tof.png" alt="Image description" width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above summarizes and visualizes the details:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Each capping end and the middle screen resolutions are denoted by the yellow boxes and the yellow &lt;strong&gt;'resolution' points&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Since the game's window can be any size, any &lt;strong&gt;resolution points&lt;/strong&gt; on the quadrant should be properly mapped to a lerp factor. (&lt;code&gt;0.0f to 1.0f&lt;/code&gt;)

&lt;ul&gt;
&lt;li&gt;If a &lt;strong&gt;resolution point&lt;/strong&gt; lies along the &lt;code&gt;y = x&lt;/code&gt; line, such as (800 * 800), the factor must be &lt;code&gt;0.5f&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If a &lt;strong&gt;resolution point&lt;/strong&gt; lies on or below the &lt;code&gt;y = 1080 / 1920 x&lt;/code&gt; line, such as (480 * 270), the factor should be &lt;code&gt;0.0f&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If it lies on or above the &lt;code&gt;y = 1920 / 1080 x&lt;/code&gt; line, such as (100 * 1000), the factor should be &lt;code&gt;1.0f&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  1. Can't just plug in the Aspect Ratio itself.
&lt;/h2&gt;

&lt;p&gt;Simply plugging in the aspect ratio to obtain the lerpFactor wouldn't suffice. While it nicely interpolates between the 2 ends, it wouldn't yield the middle value when the aspect ratio is 1:1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;minRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// = 0.5625f&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;maxRatio&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1920&lt;/span&gt;&lt;span class="p"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// = 1.7778f&lt;/span&gt;

&lt;span class="c1"&gt;// Capping the ratio&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;aspectRatioClamped&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aspectRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;minRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxRatio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Getting the lerpFactor (0 - 1)&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;  &lt;span class="n"&gt;aspectRatioClamped&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;minRatio&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxRatio&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;minRatio&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// NOTE: When the ratio is 1:1, the lerpFactor shall NOT be 0.5f. &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the ratio is 1:1, the lerp factor would be &lt;code&gt;1 - 0.5625 / (1.7778 - 0.5625) = 0.5372&lt;/code&gt;, slightly off from exactly 0.5f.&lt;/p&gt;

&lt;p&gt;Converting the ratio into numerical form means setting the denominator to 1. In the graphical representation below, it's like obtaining the y-value of the intersection point between the line &lt;code&gt;x = 1&lt;/code&gt;, and &lt;code&gt;y = ratio * x&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For instance, if I am trying to get the lerp factor of a resolution of &lt;code&gt;960 * 640&lt;/code&gt;, the aspect ratio would be &lt;code&gt;960 / 640 = 1.5&lt;/code&gt;. To derive the lerp factor, I should first find the intersection point between &lt;code&gt;x = 1&lt;/code&gt;, and &lt;code&gt;y = 1.5 x&lt;/code&gt;, which is &lt;code&gt;(1, 1.5)&lt;/code&gt;. Then, I can perform the inverse-lerp with the y-value, to get a lerp factor of &lt;code&gt;(1.5 - 0.5625) / (1.7778 - 0.5625) = 0.7714&lt;/code&gt;.&lt;br&gt;
Any resolution proportional to this would have the same aspect ratio of 1.5, regardless of the size, thus yielding the same Lerp factor. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;x = 1&lt;/code&gt; line vertically slices the ratio graph; as it's not perpendicular to each capping end, it naturally divides the range where &lt;code&gt;slope &amp;lt; 1&lt;/code&gt; and &lt;code&gt;slope &amp;gt;&lt;/code&gt; 1 unevenly.&lt;/p&gt;

&lt;p&gt;It's basically like performing an off-center projection (= oblique projection), so the center happens to be slightly off.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei9vdpbxu9lzb5m8l2yd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fei9vdpbxu9lzb5m8l2yd.png" alt="Image description" width="800" height="806"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  2. The &lt;code&gt;Atan()&lt;/code&gt; way
&lt;/h2&gt;

&lt;p&gt;To circumvent the unevenly divided range situation, one of the simplest approaches is to calculate the angles directly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdrw07szvs3cw23nq7vm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqdrw07szvs3cw23nq7vm.png" alt="Image description" width="800" height="661"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get the angle between the resolution point to the x-axis (referred to as theta here).&lt;/li&gt;
&lt;li&gt;Compare and perform an &lt;code&gt;inverse-lerp&lt;/code&gt; operation on the angle within the range of (alpha to beta)&lt;/li&gt;
&lt;li&gt;This ensures that the ranges around the midpoint are evenly divided; a resolution of 1:1 would yield a lerp factor of 0.5f.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;FYI: (1920/1080 = 30 degrees, 1080/1920 = 60 degrees)&lt;/p&gt;

&lt;p&gt;To represent this in C# code, it would appear as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;AtanLerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;resolutionPointAngle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Atan2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;resolutionPointAngle&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolutionPointAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MinAngle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxAngle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolutionPointAngle&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MinAngle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MaxAngle&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MinAngle&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code directly calculates the angle between the &lt;code&gt;resolutionPoint&lt;/code&gt; to the x-axis, then checks whether the angle lies within the angular range of the 2 capped aspect ratios.&lt;/p&gt;

&lt;p&gt;There's no need for projection to a vertical slice line; it works as intended. This was the approach I initially took in the codebase.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The Projection Matrix Way
&lt;/h2&gt;

&lt;p&gt;After a couple of weeks from the implementation, I happened to glance over the graph I scribbled while rifling through the notebook. The graphs had somehow etched into my brain, and then I realized a function call to &lt;code&gt;atan2()&lt;/code&gt; for each frame could be somewhat costly.&lt;/p&gt;

&lt;p&gt;In reality, the performance cost of the function is virtually negligible. There are plenty of other parts that impose much heavier workloads. Nevertheless, I decided to delve into this during my free time for no reason.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznr7uvcnxpvoe50tjiyl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fznr7uvcnxpvoe50tjiyl.png" alt="Image description" width="800" height="662"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The major issue with the 1st method (using the aspect ratio) is that the &lt;strong&gt;'projection line'&lt;/strong&gt; wasn't aligned with the projection range. As long as I set the &lt;strong&gt;'projection line'&lt;/strong&gt; (the green one) perpendicular to the mid-range slope (the dark-blue line: &lt;code&gt;y = x&lt;/code&gt;), evenly dividing the projection range (the light-blue lines), there will be no more skewed projection issue.&lt;/p&gt;

&lt;p&gt;Once I obtain the projected point (the yellow point where the green and yellow lines intersect) from a &lt;strong&gt;resolution point&lt;/strong&gt;, I can then calculate the lerp factor with a few floating-point arithmetic operations by comparing the projected point with the capping points (the green points).&lt;/p&gt;

&lt;p&gt;This approach sounded quite promising. The projection could be handled with a matrix-vector multiplication, which could potentially be SIMD accelerated. Additionally, a few floating-point additions, subtractions, and divisions wouldn't hurt the performance as much as &lt;code&gt;atan2()&lt;/code&gt; would.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.1. Projection Matrix
&lt;/h3&gt;

&lt;p&gt;I can derive a projection matrix that transforms a &lt;strong&gt;resolution point&lt;/strong&gt; into a &lt;strong&gt;'projection point'&lt;/strong&gt;, utilizing the very same technique that every game engine uses, but in a way much simpler way. &lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;'projected point'&lt;/strong&gt; is simply the intersection point between the projection line and the line that connects the origin point and a resolution point. &lt;/p&gt;

&lt;p&gt;The Latter, &lt;u&gt;the line connecting the origin and the resolution point&lt;/u&gt;, could be represented as &lt;code&gt;P(t) = t * P&lt;/code&gt;, where &lt;code&gt;P&lt;/code&gt; is the resolution point, and &lt;code&gt;t&lt;/code&gt; is a scalar parameter. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpr2qvjrvlj7fflr3nirl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpr2qvjrvlj7fflr3nirl.png" alt="Image description" width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To get the intersection point (= &lt;code&gt;projected point&lt;/code&gt;), plug in the parameterized &lt;code&gt;P(t)&lt;/code&gt; to the projection line &lt;code&gt;y = -x + 2&lt;/code&gt;, and then rearrange the equations to solve for &lt;code&gt;t&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklv393oet04do7rw5ldk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fklv393oet04do7rw5ldk.png" alt="Image description" width="529" height="639"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2enw958s3b4wpvhiefy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm2enw958s3b4wpvhiefy.png" alt="Image description" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After rearranging the equation, it becomes clear how to determine the projected point from the resolution point &lt;code&gt;P&lt;/code&gt;. For each element of &lt;code&gt;P&lt;/code&gt;, multiply it by 2, then divide it by &lt;code&gt;(y + x)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The matrix below will perform such a calculation with a homogeneous 2D vector (augmented with w = 1).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxvgrjlmbdwcu2xtn6g8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftxvgrjlmbdwcu2xtn6g8.png" alt="Image description" width="800" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After homogeneous division, I can finally attain the projected point. &lt;/p&gt;

&lt;h3&gt;
  
  
  3.2. Calculating the Lerp Factor
&lt;/h3&gt;

&lt;p&gt;Locating the projected point &lt;code&gt;(x, y)&lt;/code&gt; is one thing, but I still need to derive the lerp factor from it. &lt;/p&gt;

&lt;p&gt;To put it naively, I could compare &lt;u&gt;the length between the projected point to a min or max projected point&lt;/u&gt; with &lt;u&gt;the length between the min and max points&lt;/u&gt;. While &lt;code&gt;Vector2.Distance()&lt;/code&gt; could work, but it's quite computationally costly, defeating the purpose of the whole point.&lt;/p&gt;

&lt;p&gt;In a case like this, the 'projection' property of the dot product can be utilized. I can take a dot product of 2 vectors. One points at the projected point from the min projected point, (the yellow dashed one, 'Projected'), and the other points from the min to the max projected point (the green one, 'Direction').&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggd3lx5atbtrcr4vwtbq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fggd3lx5atbtrcr4vwtbq.png" alt="Image description" width="800" height="815"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The length of the &lt;code&gt;Direction&lt;/code&gt; could be computed beforehand, preferably in the constructor of a class or someplace similar. &lt;/p&gt;

&lt;p&gt;Then, I can calculate the length of the yellow vector, denoted as &lt;code&gt;l&lt;/code&gt;. Once I divide it once more by the length of direction, then I get the lerp factor.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Prepare the projection matrix upon class initialization.&lt;/span&gt;
&lt;span class="c1"&gt;// (no need to re-initialize the very same matrix for each method invocation)&lt;/span&gt;
&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="n"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Matrix4x4&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Vector4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ProjectionLerp1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projectedPointInHomgen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projectedPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectedPointInHomgen&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;projectedPointInHomgen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Calc the lerp factor&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;originAlignedPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;projectedPoint&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MinProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originAlignedPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projLineDirection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;projLineLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;normalizedLerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp01&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;projLineLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// normalize the factor to a range of (0, 1)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;normalizedLerpFactor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3.3. The Performance wasn't Good Enough
&lt;/h3&gt;

&lt;p&gt;Not only there was no performance gain, but it turns out the performance has been severely degraded compared to the prior version. &lt;/p&gt;

&lt;p&gt;I created a simple demo in Unity that accumulates the tick counts for each method. Every frame, it invokes each method once, counts its tick count, and accumulates it respectively. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1amawbguhqljogbvinfh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1amawbguhqljogbvinfh.png" alt="Image description" width="532" height="132"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Until frame number 4890, it took 31766 ticks in total to invoke the Atan version method, 42244 for the projection matrix version. (8 and 13 ticks at this particular frame)&lt;/p&gt;

&lt;p&gt;There must be something happening under the hood. Let's take a look.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.4. Examining the IL2CPP and Build Outcome
&lt;/h3&gt;

&lt;h4&gt;
  
  
  3.4.1 The Atan method post-il2cpp outcome
&lt;/h4&gt;

&lt;p&gt;Plain and simple. It almost looks like a direct translation from the original C# code.&lt;/p&gt;

&lt;p&gt;It calls the &lt;code&gt;atan2f()&lt;/code&gt;, &lt;code&gt;Mathf.Clamp()&lt;/code&gt;, and then subsequently calls the &lt;code&gt;il2cpp_codegen_subtract&lt;/code&gt; and division.&lt;/p&gt;

&lt;p&gt;Although It may seem like to make 4 function calls, only one of the calls to &lt;code&gt;atan2f()&lt;/code&gt; happens. The rest of the calls get inlined by the C++ compiler. I could peer into the final outcome (&lt;code&gt;GameAssembly.dll&lt;/code&gt;) through Ghidra.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9p4jvjr6wbrydvm7wvz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu9p4jvjr6wbrydvm7wvz.png" alt="Image description" width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9l2qd2vfopmez9qtw3i.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc9l2qd2vfopmez9qtw3i.png" alt="Image description" width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(After all, &lt;code&gt;atan2f()&lt;/code&gt; is the only function that gets invoked in the method)&lt;/p&gt;

&lt;h4&gt;
  
  
  3.4.2 The Projection Matrix method post-il2cpp outcome
&lt;/h4&gt;

&lt;p&gt;Looks slightly more verbose than the atan version, but it doesn't seem so bad at a glance. The function calls, mostly vector/matrix arithmetic operations, look like they're going to easily get inlined by the compiler in the end. &lt;/p&gt;

&lt;p&gt;Considering the &lt;code&gt;atan2f()&lt;/code&gt; and the extra complexity it imposes with its nested if-else statements inside, this new version seems to have fewer instructions. Right?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbblzii5v19a0y3i6466x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbblzii5v19a0y3i6466x.png" alt="Image description" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well, that wasn't the case. It has a larger instruction count, hence the degraded performance. The image below shows what it actually looks like after the C++ compiler.&lt;/p&gt;

&lt;p&gt;As shown in the image, there's a bulky matrix initialization at the beginning of the decompiled function, followed by a function call to the &lt;code&gt;Matrix4x4.Multiply(Vector3)&lt;/code&gt;, which didn't get inlined.&lt;/p&gt;

&lt;p&gt;Overall, this projection version shows no performance advantage over the &lt;code&gt;atan2f()&lt;/code&gt; version inherently.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwkhfook8zwlztmekprp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwwkhfook8zwlztmekprp.png" alt="Image description" width="641" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This bloated length of instructions will definitely NOT make this one faster than the previous one.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The vector way
&lt;/h2&gt;

&lt;p&gt;Using a 4-by-4 matrix where a couple of simple vector arithmetic operations should do justice seems to be an overkill anyway. It could be worthwhile if it's a part of a series of affine transforms, but it isn't.&lt;/p&gt;

&lt;p&gt;I could just get a &lt;code&gt;t&lt;/code&gt; value and multiply it for each component of a resolution point.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ProjectionLerp2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Calc the point on the projection line&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projectedPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Calc the lerp factor: &lt;/span&gt;
    &lt;span class="c1"&gt;// Project the projected point on the projection segment, to get the 't'&lt;/span&gt;
    &lt;span class="c1"&gt;// (dot product)&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;originAlignedPoint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;projectedPoint&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MinProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Dot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;originAlignedPoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;projLineDirection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;projLineLength&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Normalize the factor to a range of (0,1)&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;normalizedLerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp01&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="n"&gt;projLineLength&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;normalizedLerpFactor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Disassembled&lt;/th&gt;
&lt;th&gt;Decompiled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fubk84p2b1te8b1e66vza.png" alt="Image description" width="797" height="773"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nya14uk4fdppwb7sw33.png" alt="Image description" width="727" height="501"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Compared to the previous matrix version, it looks much more lightweight. No single function call is involved. Let's count how many ticks it consumes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19si3vcbg5uj9tnzsvqc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F19si3vcbg5uj9tnzsvqc.png" alt="Image description" width="538" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Seems pretty performant this time.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Calc the x position only
&lt;/h2&gt;

&lt;p&gt;Then I thought: &lt;em&gt;why don't I just calculate the x-value only? I still can get the LerpFactor, and that's one less component to deal with. It could be more performant&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The projection line is just a plain, simple monotonous 1:1 mapping function (the green one). So just by designating one x-value, the corresponding y-value gets determined automatically, as the y-value is dependent on the x-value. (the other way around also makes sense, btw) &lt;/p&gt;

&lt;p&gt;Simply performing the range comparison and finding the lerp factor of the yellow point on the x-axis in the green range will suffice.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7e103jwwdyn6yj9fdqx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv7e103jwwdyn6yj9fdqx.png" alt="Image description" width="800" height="658"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code would look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ProjectionLerp3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Vector2&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Calc only the X of the projected point&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2f&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;projectedPointX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="p"&gt;*&lt;/span&gt; &lt;span class="n"&gt;resolutionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// Just Map the x value to the projection segment range.&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;clampedProjectedPointX&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Mathf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Clamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;projectedPointX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MaxProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MinProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MinProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;clampedProjectedPointX&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MinProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="n"&gt;MaxProjectionPoint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lerpFactor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Disassembled&lt;/th&gt;
&lt;th&gt;Decompiled&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flb7m3z3w31itbbj7vkso.png" alt="Image description" width="797" height="765"&gt;&lt;/td&gt;
&lt;td&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffm7cqxzc3p8n6v7upxvn.png" alt="Image description" width="678" height="499"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Well, the instruction count didn't shrink much this time. The previous vector version itself was quite efficient enough, taking advantage of the SIMD acceleration. Leaving not enough room to squeeze.&lt;/p&gt;

&lt;h2&gt;
  
  
  Overall Tick Counts
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t3vn72fnrs4zhabe5eq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7t3vn72fnrs4zhabe5eq.png" alt="Image description" width="538" height="215"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Over the course of 4890 frames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;atan2f()&lt;/code&gt; version has accumulated 31766 ticks for its invocations, with 8 ticks during this frame.&lt;/li&gt;
&lt;li&gt;The Projection Matrix version has 42244 ticks so far, with 13 ticks in this frame.&lt;/li&gt;
&lt;li&gt;The Vector version has 16428 ticks, with 5 ticks in this frame.&lt;/li&gt;
&lt;li&gt;The float version has 19761 ticks, with 4 ticks in this frame.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The projection matrix version couldn't bring much performance gain because of the matrix initialization overhead and the not-inlined function call. &lt;/p&gt;

&lt;p&gt;The other 2 versions have successfully removed unnecessary operations, including function calls, resulting in fewer tick counts than the other previous 2 versions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;When lerp-ing between 2 aspect ratios, you can't just lerp the number themselves.&lt;/li&gt;
&lt;li&gt;While lerp-ing, make sure your mid-point lies in the middle.&lt;/li&gt;
&lt;li&gt;Could take advantage of the homogeneous coordinate and a projection matrix, to make it evenly lerp-ed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Performance-wise:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Matrix-to-Vector multiplication doesn't necessarily always get inlined.&lt;/li&gt;
&lt;li&gt;Copy-constructing a matrix can take some time.&lt;/li&gt;
&lt;li&gt;Take a look at what your compiler emits. Examining the code sometimes isn't sufficient.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
      <category>il2cpp</category>
      <category>ghidra</category>
    </item>
    <item>
      <title>Deploying Unity Executable as a Windows Service</title>
      <dc:creator>Chanwoo</dc:creator>
      <pubDate>Wed, 01 May 2024 17:15:28 +0000</pubDate>
      <link>https://dev.to/enghqii/deploying-unity-executable-as-a-windows-service-116i</link>
      <guid>https://dev.to/enghqii/deploying-unity-executable-as-a-windows-service-116i</guid>
      <description>&lt;p&gt;When you're running out of rendering budget in your game project, offloading some less time-critical rendering operations to another machine can be an option. Recently at work, we created an image server using Unity as a method to offload some of the rendering workload from the game client program and added it to the server deployment script as part of our server group.&lt;/p&gt;

&lt;p&gt;However, deploying a Unity-built executable that utilizes the graphics API as a Windows service didn't go as smoothly as I had expected initially. In this post, I am going to walk through some of the technical difficulties I faced while deploying the image server.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Image Server
&lt;/h2&gt;

&lt;p&gt;The image server is responsible for accepting HTTP requests from clients and, returns the rendered image in the response, upon completing the rendering processes as specified in the request.&lt;/p&gt;

&lt;p&gt;In practice, the server will render some elements that the client doesn’t immediately need but will within a few seconds. This could be the map image of a procedurally generated dungeon or an image of a randomized NPC appearance, possibly displayed in a corner of the UI. (By the way, neither of these examples was our case.) Essentially, it handles whatever tasks the client can delay for a short time before retrieving the actual image. I won't delve much deeper into this aspect, as it heavily depends on your project's requirements and what you choose to render on the server.&lt;/p&gt;

&lt;p&gt;Setting aside the rendering processes, one of the simplest forms to illustrate the server's behavior might look like the code below. It's essentially a basic HTTP server that listens on port 3000 and returns hard-coded HTML in response.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Net&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;System.Threading&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="nn"&gt;UnityEngine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyHttpServer&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MonoBehaviour&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;HttpListener&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="n"&gt;Thread&lt;/span&gt; &lt;span class="n"&gt;listenerThread&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;HttpListener&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Prefixes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"http://localhost:3000/"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;Debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Listening..."&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;listenerThread&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ThreadStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;listenerThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Listen&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;HttpListenerContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetContext&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="n"&gt;HttpListenerRequest&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;HttpListenerResponse&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

            &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;responseString&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;Hello World!&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;responseString&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

            &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentLength64&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IO&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stream&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OutputStream&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;OnApplicationQuit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;listenerThread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Abort&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Attach it to an arbitrary GameObject in the scene, then build the executable. Once it's up and running, you should be able to see the simple html code mentioned above returned as a response in your web browser at the address (localhost:3000, in this case).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwesn3u0t6r6dxbzcb1wp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwesn3u0t6r6dxbzcb1wp.png" alt="Image description" width="800" height="766"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(Let's pretend it's returning an image instead of the HTML)&lt;/p&gt;

&lt;h2&gt;
  
  
  Issues with the Windows service
&lt;/h2&gt;

&lt;p&gt;The servers were deployed as services on Windows machines, primarily to simplify the automatic starting and stopping of server processes when the machine is turned on and off.&lt;/p&gt;

&lt;p&gt;The issues kick in right at this point. A Windows service process runs with admin privileges in a special session called 'Session 0', while other standard processes run in each user's interactive logon session.&lt;/p&gt;

&lt;p&gt;In Session 0, a service process is meant to run as a 'background' process. A background process in Windows does not inherit the capability to use the GUI system; it is unable to create a window or use graphic APIs, such as DirectX or OpenGL. Even if you spawn a service process from a Unity-built executable, it is initiated as a background process. Being unable to use the GUI, it gets stuck at the initialization stage, even before the HTTP server MonoBehavior is attached to the GameObject.&lt;/p&gt;

&lt;p&gt;This means that &lt;u&gt;the image server won't function when it runs as a regular service running on Session 0&lt;/u&gt;. Since its primary purpose is to 'render images', which requires full utilization of the GUI and graphics API, it should operate in an interactive logon session to enable this functionality. Not as a regular background service process.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if I run it as a service anyway?
&lt;/h2&gt;

&lt;p&gt;The process will be spawned, but &lt;u&gt;it will get stuck in the graphics API initialization stage&lt;/u&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiffa3l47vow9gkm4fk28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fiffa3l47vow9gkm4fk28.png" alt="Image description" width="800" height="177"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(The image server process was created as a 'background' process, stuck in the init stage)&lt;/p&gt;

&lt;p&gt;In the image above, the &lt;code&gt;HttpServerTest&lt;/code&gt; process is up and running. However, since it couldn't complete the initialization process, it was unable to bind to port 3000. The output of the command &lt;code&gt;netstat -aon | findstr :3000&lt;/code&gt; reveals that no process is bound to the designated port number. Consequently, the command &lt;code&gt;Invoke-WebRequest&lt;/code&gt;, which sends a GET http request, fails to connect to the given URL.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbrqxat6v0iff7ndqbag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbrqxat6v0iff7ndqbag.png" alt="Image description" width="800" height="660"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you create a dump of the stuck process, you can locate where it's actually stuck. After opening the dump and examining the call stack, I noticed it was stuck at the resolution setting. The exact message the program is trying to display, but couldn't because it couldn't use GUI, becomes apparent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use PsExec to run it on a user session
&lt;/h2&gt;

&lt;p&gt;A sysinternals program &lt;a href="https://learn.microsoft.com/en-us/sysinternals/downloads/psexec"&gt;&lt;code&gt;PsExec&lt;/code&gt;&lt;/a&gt; can be quite helpful in this case. It allows you to execute other programs on another user's session, even on a remote computer, if you have provided the right credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; psexec &lt;span class="se"&gt;\\&lt;/span&gt;RemoteMachine &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;-p&lt;/span&gt; password &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; &amp;lt;session &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &amp;lt;program to execute&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the command line option &lt;code&gt;-i&lt;/code&gt;, you can specify the session id the 'program to execute' will be running on. A proper session ID can easily be obtained from the command, &lt;code&gt;query session&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xui22llc7ffun3hhlap.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8xui22llc7ffun3hhlap.png" alt="Image description" width="717" height="110"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(You can use PsExec to execute a program on either an 'Active' or a 'Disconnected' session.)&lt;/p&gt;

&lt;p&gt;With the commands mentioned above, you can write a simple program or a PowerShell script that utilizes PsExec to execute your server in a user session. Please be advised that you need to log in to the target machine at least once, to create an interactable user logon session.&lt;/p&gt;

&lt;h2&gt;
  
  
  But PsExec requires some extra configs
&lt;/h2&gt;

&lt;p&gt;The commands should work. as they did for me in the proof-of-concept script. However, I soon realized that PsExec requires additional configurations on the recipient machines. I was concerned that the security department, responsible for auditing server configurations and deploying live servers, might not approve these changes due to the significant security risks this might impose. In my case especially, this meant that I needed to find another way to circumvent the direct usage of PsExec.&lt;/p&gt;

&lt;p&gt;The additional configurations include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Opening up the TCP 445 port, by adding allowing rules to the firewall.&lt;/li&gt;
&lt;li&gt;Turning off the UAC (User Account Control).&lt;/li&gt;
&lt;li&gt;Enabling the 'File and Printer Sharing' option.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So instead of executing PsExec from the deployment script directly, I chose to bundle the PsExec executable with the image server, let the recipient machine run the PsExec locally on the machine, via the &lt;code&gt;Invoke-Command&lt;/code&gt; executed remotely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# `query session` command&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$rawOutput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComputerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remoteIp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ScriptBlock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$remoteUsername&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remoteUsername&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArgumentList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remoteUsername&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# parse the output and obtain a proper session id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$sessionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# could be 1, or 2, etc.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Invoke-Command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComputerName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$remoteIp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$credential&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ScriptBlock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sessionId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\Path\To\PsExec'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/accepteula&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sessionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\Path\To\ImageServer.exe'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;2&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ArgumentList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sessionId&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I configured the deployment script in the same way as above. Upon successfully executing it, I could see the image server was up and running, with its GUI functionality initialized properly. It successfully bound the process to port number 3000, being able to accept http requests and respond with the expected payload.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fboihtww7nub1656i00ux.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fboihtww7nub1656i00ux.png" alt="Image description" width="800" height="175"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;(In this case, unlike above, the process is created as a 'foreground' process)&lt;/p&gt;

&lt;p&gt;wrote another PoC program in C# that does the same thing as the Powershell script above and made it run as a service via &lt;a href="https://nssm.cc/"&gt;NSSM&lt;/a&gt;, worked like a charm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There are several key considerations when running a Unity-built application as a Windows service:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Windows services run as background processes
&lt;/h3&gt;

&lt;p&gt;Windows services are primarily designed to run non-interactive processes in the background. Consequently, any process run under a Windows service is executed as a background process.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. To make use of Graphics API, it should be a foreground process
&lt;/h3&gt;

&lt;p&gt;If you run Unity output as a background process, it won't be able to get past the GUI initialization phase. Naturally, all the Unity functionalities will not be able to work, including network initialization.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Execute the process via &lt;code&gt;PsExec&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;PsExec&lt;/code&gt; will allow you to specify in which session your program will run. It's possible to write a server deployment script or a program that creates a logon session and executes the server via PsExec on the session. &lt;br&gt;
With the help of a program like NSSM, it'd be easy to make it into a Windows service.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>unity3d</category>
    </item>
  </channel>
</rss>
