<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Orsvärn</title>
    <link>https://orsvarn.com/</link>
    <description>Recent content on Orsvärn</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Sat, 31 May 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://orsvarn.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>Sphere shader</title>
      <link>https://orsvarn.com/sphere-plane/</link>
      <pubDate>Sat, 31 May 2025 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/sphere-plane/</guid>
      <description>
        
        &lt;p&gt;This is how I can use a shader to make a quad look like a sphere. This is cheaper to render in many cases, and can be indistinguishable from a real sphere.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/spot-the-difference.png&#34;
         alt=&#34;Three spheres, one is revealed to be a plane, but is indistinguishable from the real spheres&#34;/&gt;&lt;figcaption&gt;The left side is shaded, and the right side is a wireframe view of the same scene. One of the spheres is actually a plane!&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;The shader calculates tangent space normals on the plane which make it look similar to a real 3D sphere.&lt;/p&gt;
&lt;h2 id=&#34;drawbacks&#34;&gt;Drawbacks&lt;/h2&gt;
&lt;p&gt;I like seeing where a technique fails, which can help explain how it works as well, so let’s break it! 😁&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/light-distance.png&#34;
         alt=&#34;While the plane&amp;amp;rsquo;s normals are correct thanks to the shader, the 3D positions are not. This makes the shape of the received light look weird when the light source is close. Only the sphere on the right is fake.&#34;/&gt;&lt;figcaption&gt;While the plane&amp;rsquo;s normals are correct thanks to the shader, the 3D positions are not. This makes the shape of the received light look weird when the light source is close. Only the sphere on the right is fake.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;figure&gt;
    &lt;img src=&#34;images/intersect.png&#34;
         alt=&#34;Two spheres intersect with a cube, one of the spheres has a flat intersection with the cube.&#34;/&gt;&lt;figcaption&gt;It doesn&amp;rsquo;t handle intersections well. Real sphere on the left, fake sphere on the right.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;figure&gt;
    &lt;img src=&#34;images/shadow.png&#34;
         alt=&#34;Two spheres are partially shaded by a straight wall, one looks as expected, on the other sphere the shadow is like a straight line across it despite its curved surface.&#34;/&gt;&lt;figcaption&gt;It doesn&amp;rsquo;t receive shadows properly. Real sphere on the left, fake on the right.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;figure&gt;
    &lt;img src=&#34;images/texture.png&#34;
         alt=&#34;A texture has been applied to the plane, which breaks the illusion that it&amp;#39;s a sphere.&#34;/&gt;&lt;figcaption&gt;It also can’t have a regular texture on it because the texture follows the camera when it rotates around the sphere. Solutions include generating UV coordinates, or using a cubemap.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;h2 id=&#34;benefits&#34;&gt;Benefits&lt;/h2&gt;
&lt;p&gt;So what are the positive aspects of the technique?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s cheaper to render than a sphere of similar roundness built using more geometry.&lt;/li&gt;
&lt;li&gt;It looks perfectly round.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;use-cases&#34;&gt;Use cases&lt;/h2&gt;
&lt;p&gt;With real-time graphics, I need to balance several factors against each other to find a good use case. To do that, I&amp;rsquo;ll list some issues and potential solutions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Being too close to lights looks weird.
&lt;ul&gt;
&lt;li&gt;Keep point lights away.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Intersecting with other objects looks weird.
&lt;ul&gt;
&lt;li&gt;Ensure it doesn&amp;rsquo;t intersect with anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Receiving shadows looks weird.
&lt;ul&gt;
&lt;li&gt;Disable receiving shadows on the object?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A potential solution to all these issues is also to keep the object small or moving, this makes the drawbacks harder to notice. Maybe it&amp;rsquo;s useful for water droplets in a particle effect, for instance.&lt;/p&gt;
&lt;p&gt;Another potential solution is to develop the technique further to make the 3D position of the fragments correct. I have a link to a technique that does this at the end of the post.&lt;/p&gt;
&lt;p&gt;It could also work well for distant objects where it covers a small space on the screen, or I can control the lighting conditions and what geometry it intersects with. Maybe it could be used as the last level of detail for some object?&lt;/p&gt;
&lt;p&gt;It could work better if the game has certain properties. For instance, if the game doesn&amp;rsquo;t use point lights, or if the game is 2D, or it doesn&amp;rsquo;t use shadow maps.&lt;/p&gt;
&lt;h2 id=&#34;implementation&#34;&gt;Implementation&lt;/h2&gt;
&lt;p&gt;This shader is made to work with the default quad in Godot. If I&amp;rsquo;m using something else, I may need to make some changes. For the effect to work, the quad must always face the camera. I&amp;rsquo;ll include code for this in the shader as well.&lt;/p&gt;
&lt;h3 id=&#34;theory&#34;&gt;Theory&lt;/h3&gt;
&lt;p&gt;The amount that the normal faces to the right or up increases linearly with the x and y coordinates. This means I already have my x and y components in the UVs!&lt;/p&gt;
&lt;p&gt;That leaves me two things to figure out:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The Z component.
&lt;ul&gt;
&lt;li&gt;This can be calculated from the UV coordinates.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Discarding fragments in a circle.
&lt;ul&gt;
&lt;li&gt;I get what I need for this when calculating the Z component.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Let&amp;rsquo;s look at an implementation.&lt;/p&gt;
&lt;h3 id=&#34;implementation-1&#34;&gt;Implementation&lt;/h3&gt;
&lt;p&gt;This implementation is written in &lt;a href=&#34;https://godotengine.org&#34;&gt;Godot&lt;/a&gt;&amp;rsquo;s shading language, which is very similar to GLSL, which is fairly similar to HLSL. The shader is made to be applied to a standard Godot quad, which I get by creating a &lt;code&gt;MeshInstance3D&lt;/code&gt;, and setting its &lt;code&gt;Mesh&lt;/code&gt; to be a &lt;code&gt;New QuadMesh&lt;/code&gt;.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-glsl&#34; data-lang=&#34;glsl&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;shader_type spatial;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// We use the vertex shader to make the quad face the camera.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// This doesn&amp;#39;t have very detailed comments because it isn&amp;#39;t the focus.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; vertex() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Prepare our directions.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; plane_to_cam &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(CAMERA_POSITION_WORLD &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; NODE_POSITION_WORLD);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;const&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; WORLD_UP &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(&lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt;, &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; plane_right &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; normalize(cross(WORLD_UP, plane_to_cam));
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt; plane_up &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; cross(plane_to_cam, plane_right);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Build the rotation matrix.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	mat4 billboard &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; mat4(
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt;(plane_right, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt;(plane_up, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;vec4&lt;/span&gt;(plane_to_cam, &lt;span style=&#34;color:#ae81ff&#34;&gt;0.0&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		MODEL_MATRIX[&lt;span style=&#34;color:#ae81ff&#34;&gt;3&lt;/span&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Apply the matrix.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	MODELVIEW_MATRIX &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; VIEW_MATRIX &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; billboard;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;// This is where the sphere illusion is created.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;void&lt;/span&gt; fragment() {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Bring input UVs into the -1 to 1 range.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;vec2&lt;/span&gt; uv &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; UV &lt;span style=&#34;color:#f92672&#34;&gt;*&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;2.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Do the dot product separately so I can branch on the result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// and reuse it later.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; uv_dot &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; dot(uv, uv);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// If the dot product is over 1, it&amp;#39;s outside of the circle.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt;(uv_dot &lt;span style=&#34;color:#f92672&#34;&gt;&amp;gt;&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;discard&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Calculate the Z value from the dot product.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;float&lt;/span&gt; z &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; sqrt(&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt; uv_dot);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Flip UV.y because that&amp;#39;s what Godot wants for tangent space normals.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	uv.y &lt;span style=&#34;color:#f92672&#34;&gt;*=&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;-&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;1.0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;// Set the normal.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	NORMAL.rgb &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;vec3&lt;/span&gt;(uv, z);
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If the original plane is entirely outside the view, it will be frustum-culled even though our rotated plane is still inside the view, causing it to pop in and out of existence in some cases. We can solve this by setting &lt;code&gt;GeometryInstance3D&lt;/code&gt; -&amp;gt; &lt;code&gt;Geometry&lt;/code&gt; -&amp;gt; &lt;code&gt;Custom AABB&lt;/code&gt; to these values:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Set &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;z&lt;/code&gt; to &lt;code&gt;-0.5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;w&lt;/code&gt;, &lt;code&gt;h&lt;/code&gt;, and &lt;code&gt;d&lt;/code&gt; to &lt;code&gt;1&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These values will only work for uniformly scaled planes. If I want non-uniform scaling, more work needs to be done, but I&amp;rsquo;m not covering that in this post.&lt;/p&gt;
&lt;h2 id=&#34;more-spheres&#34;&gt;More spheres&lt;/h2&gt;
&lt;p&gt;This post showed the basics of using shaders to make a plane look like a sphere. If I want to go deeper, check out these posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://simonschreibt.de/gat/diablo-3-resource-bubbles/&#34;&gt;Diablo 3 — Resource Bubbles&lt;/a&gt; by Simon Trümpler is a treasure trove for sphere rendering.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c&#34;&gt;Rendering a Sphere on a Quad&lt;/a&gt; by Ben Golus uses more advanced techniques to create a sphere that doesn’t suffer from the drawbacks of the technique in this post.&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Fix Bouncing Emails</title>
      <link>https://orsvarn.com/bouncing-emails/</link>
      <pubDate>Sun, 30 Jun 2024 13:20:00 +0200</pubDate>
      
      <guid>https://orsvarn.com/bouncing-emails/</guid>
      <description>
        
        &lt;p&gt;An email I sent to a gmail address recently bounced, this is how I solved it.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/bouncing-emails/images/cover.png&#34; alt=&#34;A crying letter&#34;  /&gt;
&lt;/p&gt;
&lt;p&gt;I have my own domain that I use for email, so I need to set up certain DNS records to avoid my emails bouncing on for instance Google&amp;rsquo;s email servers.&lt;/p&gt;
&lt;p&gt;The relevant acronyms are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;SPF (Sender Policy Framework)&lt;/li&gt;
&lt;li&gt;DKIM (DomainKeys Identified Mail)&lt;/li&gt;
&lt;li&gt;DMARC (Domain-based Message Authentication, Reporting and Conformance)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I need to set up SPF and DKIM to avoid bouncing. DMARC is only necessary for sending email in bulk, so I don&amp;rsquo;t need that.&lt;/p&gt;
&lt;p&gt;The purpose of SPF and DKIM is to stop people from &amp;ldquo;spoofing&amp;rdquo; my domain, which is when someone sends an email that looks like it came from my domain when it actually didn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;SPF and DKIM are set up as DNS records on the domain.&lt;/p&gt;
&lt;h2 id=&#34;spf&#34;&gt;SPF&lt;/h2&gt;
&lt;p&gt;This DNS record lists which mail servers are allowed to send email on behalf of this domain.&lt;/p&gt;
&lt;p&gt;This is the SPF entry for my domain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type: &lt;code&gt;TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Subdomain: &lt;code&gt;@&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Data: &lt;code&gt;&amp;quot;v=spf1 include:hover.com -all&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For some reason I need to put &lt;code&gt;&amp;quot;&lt;/code&gt; around the data. Not sure if that&amp;rsquo;s specific to my DNS provider.&lt;/p&gt;
&lt;p&gt;hover.com hosts my email, so that&amp;rsquo;s the domain I include. I don&amp;rsquo;t write the entire domain which is &lt;code&gt;mail.hover.com&lt;/code&gt;, because it doesn&amp;rsquo;t care about subdomains.&lt;/p&gt;
&lt;p&gt;To check that it&amp;rsquo;s working I use an online SPF checker. I give it &lt;code&gt;orsvarn.com&lt;/code&gt; and it verifies if my SPF record is valid.&lt;/p&gt;
&lt;h2 id=&#34;dkim&#34;&gt;DKIM&lt;/h2&gt;
&lt;p&gt;This entry contains a public key that my email server provider created. When I send an email, the email server signs the email with a private key on the mail server. The receiver uses the public key on this DNS record to verify the signature.&lt;/p&gt;
&lt;p&gt;My email client doesn&amp;rsquo;t need to do anything, the signing happens on the mail server.&lt;/p&gt;
&lt;p&gt;I had to contact my email provider&amp;rsquo;s support to have them generate a key pair for me, and they gave me the public key. It seems like some providers allow you to generate your own key pair.&lt;/p&gt;
&lt;p&gt;This is the DKIM entry for my domain:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type: &lt;code&gt;TXT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Subdomain: &lt;code&gt;dkim1._domainkey&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Data: &lt;code&gt;v=DKIM1;t=s;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC3D3MoSCbPojBxQpXA0B5nKWi33uE+QmDlvtqfqBgFLVPj3hMQGMBTLKG9+VjZ60zy7waZZxHkpIGnqiP0q6kVRTE7oKTZyjSWjogaYp0Mxt9m21gjLbVqSE2gU+wJ1iGbunkNWU9ng0LLZJ6hT8j3DvAKoq0wlWGb5DuX/2ybYQIDAQAB&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At first I didn&amp;rsquo;t understand that I needed to create a new subdomain for this entry. I tried to put it under &lt;code&gt;@&lt;/code&gt; like with the SPF entry, but that didn&amp;rsquo;t work.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;t=s&lt;/code&gt; does this according to the &lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc6376#section-3.6.1&#34;&gt;specification&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Any DKIM-Signature header fields using the &amp;quot;i=&amp;quot; tag MUST have the same domain value on the right-hand side of the &amp;quot;@&amp;quot; in the &amp;quot;i=&amp;quot; tag and the value of the &amp;quot;d=&amp;quot; tag.  That is, the &amp;quot;i=&amp;quot; domain MUST NOT be a subdomain of &amp;quot;d=&amp;quot;.  Use of this flag is RECOMMENDED unless subdomaining is required.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I don&amp;rsquo;t understand what that means, but it&amp;rsquo;s recommended and seems to work so that&amp;rsquo;s what I&amp;rsquo;m doing.&lt;/p&gt;
&lt;p&gt;To check that it&amp;rsquo;s working I use an online DKIM checker. I give the checker &lt;code&gt;orsvarn.com:dkim1&lt;/code&gt; and it tells me if it&amp;rsquo;s valid.&lt;/p&gt;
&lt;h2 id=&#34;dmarc&#34;&gt;DMARC&lt;/h2&gt;
&lt;p&gt;Since I don&amp;rsquo;t send bulk email, I don&amp;rsquo;t have this set up. I read somewhere that having a &amp;ldquo;low level&amp;rdquo; DMARC entry can make it easier to spoof emails from my address than if I had no entry at all.&lt;/p&gt;
&lt;p&gt;It isn&amp;rsquo;t necessary to set this up to be able to send emails to Google&amp;rsquo;s mail servers. Though some sellers of DMARC online say otherwise.&lt;/p&gt;
&lt;h2 id=&#34;the-final-test&#34;&gt;The final test&lt;/h2&gt;
&lt;p&gt;To check that it&amp;rsquo;s working in practice:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Wait for the DNS to propagate.
&lt;ul&gt;
&lt;li&gt;I use online SPF and DKIM checkers to check if it has propagated. The way my DNS is set up, this takes up to an hour.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Send an email to a gmail address.&lt;/li&gt;
&lt;li&gt;If I don&amp;rsquo;t get an email telling me it bounced, it works!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Questions or comments? Contact me via &lt;a href=&#34;mailto:lukas@orsvarn.com&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Smooth low poly style</title>
      <link>https://orsvarn.com/video/smooth-normals/</link>
      <pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/video/smooth-normals/</guid>
      <description>
        
          




&lt;video controls width=&#34;100%&#34; poster=&#34;https://orsvarn.com/video/smooth-normals.jpg&#34; class=&#34;video&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/video/smooth-normals.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;

        
        &lt;p&gt;Creating a smooth low poly style is all about controlling the normals. This video shows the basic techniques for doing that.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Self Hosting Video</title>
      <link>https://orsvarn.com/self-hosting-video/</link>
      <pubDate>Fri, 07 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/self-hosting-video/</guid>
      <description>
        
        &lt;p&gt;I recently set up a low tech system for hosting videos on my website. Here is the first video I uploaded to test it!&lt;/p&gt;







&lt;video controls width=&#34;100%&#34; poster=&#34;https://orsvarn.com/video/street-smoke.jpg&#34; class=&#34;video&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/video/street-smoke.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;div class=&#34;article-width columns low-emphasis&#34;&gt;
    &lt;div class=&#34;left&#34;&gt;
        &lt;a href=&#34;https://orsvarn.com/video/street-smoke/&#34;&gt;
            Street Smoke
        &lt;/a&gt;
    &lt;/div&gt;
    &lt;div class=&#34;right&#34;&gt;
            &lt;a href=&#34;https://orsvarn.com/video/street-smoke.mp4&#34; download&gt;Download&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&#34;why&#34;&gt;Why?&lt;/h2&gt;
&lt;p&gt;I used to make videos on YouTube. But I dislike someone earning money from my videos. I have no intention of earning money from them, and almost nobody watches them anyway, so it just gives me a bad feeling.&lt;/p&gt;
&lt;p&gt;But I do want to be able to put videos online. I want to be able to present them to people without ads other distractions. I want people to be able to watch them through their RSS readers! I want to offer a direct download link, and original quality Torrent download next to the video.&lt;/p&gt;
&lt;p&gt;I want my visitors to feel like fellow humans, rather than a way to make money.&lt;/p&gt;
&lt;h2 id=&#34;goals&#34;&gt;Goals&lt;/h2&gt;
&lt;p&gt;These are the goals for my setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Low maintenance&lt;/strong&gt;, I don&amp;rsquo;t want to be forced to fix my server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Portable&lt;/strong&gt;, I don&amp;rsquo;t want to be stuck on a specific server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy to use&lt;/strong&gt;, both to set it up, and using the final system.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These goals align with a &lt;a href=&#34;https://www.tadiweb.com/&#34;&gt;slippy mindset&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All I want to have to do is upload a video somewhere over &lt;code&gt;ssh&lt;/code&gt;. When the video is uploaded it should automatically create a page for the video in my website repo, as well as transcode the video to a lighter format, and upload that to the website repo as well.&lt;/p&gt;
&lt;p&gt;Then I can add a title and description to it and publish it manually.&lt;/p&gt;
&lt;h2 id=&#34;how-i-did-it&#34;&gt;How I did it&lt;/h2&gt;
&lt;p&gt;I combined several smaller pieces into something that does exactly what I need, and nothing I don&amp;rsquo;t need. There&amp;rsquo;s low maintenance, because each program has been around forever, and won&amp;rsquo;t change with updates, and they are likely to keep being maintained for a long time. And if something breaks, everything is at a level where I can go in there and fix it myself, or swap out one of the components.&lt;/p&gt;
&lt;p&gt;Here are the key parts in this project:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://man.openbsd.org/scp.1&#34;&gt;&lt;code&gt;scp&lt;/code&gt;&lt;/a&gt; to send videos to a directory on a server.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;http://eradman.com/entrproject/&#34;&gt;&lt;code&gt;entr&lt;/code&gt;&lt;/a&gt; for reacting to a file being sent to that directory.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://ffmpeg.org/&#34;&gt;&lt;code&gt;ffmpeg&lt;/code&gt;&lt;/a&gt; for transcoding videos.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://git-scm.com/&#34;&gt;&lt;code&gt;git&lt;/code&gt;&lt;/a&gt; to push transcoded videos to the website repository.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.gnu.org/software/bash/&#34;&gt;&lt;code&gt;bash&lt;/code&gt;&lt;/a&gt; for putting everything together into a script.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I put all these pieces together into this ~100 line &lt;code&gt;bash&lt;/code&gt; script:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/usr/bin/env bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;transcode_video&lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Transcodes the input files to the $OUT directory.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Transcodes to one modern mainstream format.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# I&amp;#39;m using https://developer.mozilla.org/en-US/docs/Web/Media/Formats/Video_codecs to decide which formats&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# have good enough support.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;#&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# As of May 2024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Modern format: VP9 &amp;amp; Opus&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Legacy format: H.265 &amp;amp; Vorbis&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local IN&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local OUT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# 960 is half of 1920,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# so this is half of 1080p.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# The long side is decided instead of the short side,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# because this ensures the resolution doesn&amp;#39;t go above half of 1080p.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local RES&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;960&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local SCALING&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;scale=w=&amp;#39;min(&lt;/span&gt;$RES&lt;span style=&#34;color:#e6db74&#34;&gt;,iw)&amp;#39;:h=&amp;#39;min(&lt;/span&gt;$RES&lt;span style=&#34;color:#e6db74&#34;&gt;,ih)&amp;#39;:force_original_aspect_ratio=decrease&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# X265 constant rate factor (CRF) encoding&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ffmpeg &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-n &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-i $IN &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-vf $SCALING &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-c:v libx264 &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-b:v &lt;span style=&#34;color:#ae81ff&#34;&gt;0&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-crf &lt;span style=&#34;color:#ae81ff&#34;&gt;30&lt;/span&gt; &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-c:a aac &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	-b:a 128k &lt;span style=&#34;color:#ae81ff&#34;&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#ae81ff&#34;&gt;&lt;/span&gt;	&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;OUT&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.mp4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Create thumbnail&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ffmpeg -n -i &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$IN&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; -vf &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$SCALING&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt; -frames:v &lt;span style=&#34;color:#ae81ff&#34;&gt;1&lt;/span&gt; -q:v &lt;span style=&#34;color:#ae81ff&#34;&gt;5&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;OUT&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;.jpg&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;push_to_git &lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Adds and pushes all files in the repository.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Assumes the GIT_WORK_TREE and GIT_DIR environment variables are set.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	git fetch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	git merge
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	git add --all
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	git commit -m &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;push_to_git from &lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;hostname&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	git push
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;folder_transcode &lt;span style=&#34;color:#f92672&#34;&gt;()&lt;/span&gt; &lt;span style=&#34;color:#f92672&#34;&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# This script transcodes all files from one folder&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# and puts them into another folder.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local DIR_IN&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$1&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local HUGO_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$2&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	local DIR_OUT&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$HUGO_DIR/assets/video
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;for&lt;/span&gt; f in &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$DIR_IN&lt;span style=&#34;color:#e6db74&#34;&gt;/*&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#75715e&#34;&gt;# Continue if it isn&amp;#39;t a file.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ! test -f &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#66d9ef&#34;&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		filename&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;$(&lt;/span&gt;basename -- &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$f&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		out_filename_no_ext&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;filename%.*&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		out_filename_ext&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$out_filename_no_ext&lt;span style=&#34;color:#e6db74&#34;&gt;.mp4&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;if&lt;/span&gt; ! test -f &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$DIR_OUT&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$out_filename_ext&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;; &lt;span style=&#34;color:#66d9ef&#34;&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;# If the output file does not exist..&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;# Create and push a new draft page for the video,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;# so that can be edited while the video gets uploaded.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			$HUGO_DIR/hugo -s $HUGO_DIR new &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;video/&lt;/span&gt;$out_filename_no_ext&lt;span style=&#34;color:#e6db74&#34;&gt;.md&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			push_to_git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			echo &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Pushed video info file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			&lt;span style=&#34;color:#75715e&#34;&gt;# transcode and push the video.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			out_file_path&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;$DIR_OUT&lt;span style=&#34;color:#e6db74&#34;&gt;/&lt;/span&gt;$out_filename_no_ext&lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			transcode_video $f $out_file_path
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;			push_to_git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;		&lt;span style=&#34;color:#66d9ef&#34;&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#f92672&#34;&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# This script watches a folder,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# and transcodes all videos to the output folder when it changes.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Print everything.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;set -x
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare DIR_IN&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$1
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare HUGO_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$2
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Set the git repo we&amp;#39;re using.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export GIT_WORK_TREE&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$HUGO_DIR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;export GIT_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$HUGO_DIR/.git
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Ensure the out dir is up to date first.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git fetch
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git merge
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;while&lt;/span&gt; sleep 1; &lt;span style=&#34;color:#66d9ef&#34;&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Transcode all files in the directory.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	folder_transcode $DIR_IN $HUGO_DIR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	&lt;span style=&#34;color:#75715e&#34;&gt;# Wait until there is a change in the directory.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;	ls -d $DIR_IN | entr -npd -s &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;kill $PPID&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#66d9ef&#34;&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Feel free to copy and do whatever you want with this script!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve added the script as a &lt;code&gt;systemd&lt;/code&gt; service that starts when the computer starts. The script runs forever in a loop once it has started. &lt;code&gt;systemd&lt;/code&gt; is configured with &lt;a href=&#34;https://nixos.org/&#34;&gt;NixOS&lt;/a&gt;, and the configuration there looks like this.&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-nix&#34; data-lang=&#34;nix&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;systemd&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;services&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;folder-watch-transcode &lt;span style=&#34;color:#960050;background-color:#1e0010&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  description &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;Auto-transcode and upload videos&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  wantedBy &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; [ &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;multi-user.target&amp;#34;&lt;/span&gt; ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  path &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#66d9ef&#34;&gt;with&lt;/span&gt; pkgs; [
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    bash
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    openssh
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    entr
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ffmpeg_7-headless
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    gitMinimal
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    hostname
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    lsof
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  ];
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  serviceConfig &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    User &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;video&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Group &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;users&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    Type &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#34;simple&amp;#34;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;    ExecStart  &lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt; &lt;span style=&#34;color:#e6db74&#34;&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;${&lt;/span&gt;pkgs&lt;span style=&#34;color:#f92672&#34;&gt;.&lt;/span&gt;bash&lt;span style=&#34;color:#e6db74&#34;&gt;}&lt;/span&gt;&lt;span style=&#34;color:#e6db74&#34;&gt;/bin/bash /home/video/bin/folder-watch-transcode /home/video/upload /home/video/orsvarn&amp;#39;&amp;#39;&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;  };
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is much more than this to the NixOS configuration, but that&amp;rsquo;s for another blog post. I also had to modify my website to display the videos, that is also not covered in this post.&lt;/p&gt;
&lt;h2 id=&#34;nixos&#34;&gt;NixOS&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://nixos.org/&#34;&gt;NixOS&lt;/a&gt; enables me to have the setup for an entire system in no more than a few configuration files.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/self-hosting-video/images/nixos.svg&#34; alt=&#34;&#34;  /&gt;
&lt;/p&gt;
&lt;p&gt;If I want to have the same setup on another computer, I can copy the configuration files there, run &lt;code&gt;nixos-rebuild switch --flake .&lt;/code&gt; and I&amp;rsquo;ve got all the programs, users, systemd services, etc. all up and running within a few minutes.&lt;/p&gt;
&lt;p&gt;Some things still need to be done manually, like adding private SSH keys, and adding git repos where they&amp;rsquo;re supposed to be, but it reduces the amount of work by at least 80%!&lt;/p&gt;
&lt;p&gt;NixOS improves the &amp;ldquo;low maintenance&amp;rdquo; and &amp;ldquo;portability&amp;rdquo; goals. This comes at the cost of the &amp;ldquo;easy to use&amp;rdquo; goal though, because it&amp;rsquo;s hard!&lt;/p&gt;
&lt;h2 id=&#34;next-steps&#34;&gt;Next steps&lt;/h2&gt;
&lt;p&gt;I want to add a torrent download for the videos. I&amp;rsquo;m investigating if I need to run a tracker to not rely on a third party, and command line programs for creating torrent files.&lt;/p&gt;
&lt;h2 id=&#34;why-dont-i-use-peertube&#34;&gt;Why don&amp;rsquo;t I use PeerTube?&lt;/h2&gt;
&lt;p&gt;&lt;a href=&#34;https://joinpeertube.org/&#34;&gt;PeerTube&lt;/a&gt; has all the features I want. It transcodes videos, allows embedding them, and uses the Torrent protocol to alleviate server load.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/self-hosting-video/images/peertube.png&#34; alt=&#34;&#34;  /&gt;
&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t go with PeerTube though, because it doesn&amp;rsquo;t satisfy my goals.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s potentially high maintenance&lt;/strong&gt;, because I don&amp;rsquo;t know what is going to happen with it in the future. If it stops develoment I&amp;rsquo;d be forced to move to something else at some point. Since it&amp;rsquo;s a relatively new and complex project, the risk of it being left unmaintained increases. And when updates do arrive, that comes with an unknown amount of work for me.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s less portable&lt;/strong&gt; because it&amp;rsquo;s a bigger thing. What sort of hardware does it need to run? Can it run on a Raspberry Pi 4? What sort of database setup do I need? Once I&amp;rsquo;ve set up a system, I don&amp;rsquo;t want to feel like it would be a huge task to migrate it to another server.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s hard to use&lt;/strong&gt;, because it&amp;rsquo;s a big project that requires databases and certain power of hardware and other requirements to set up. If something goes wrong I don&amp;rsquo;t know how to fix it. If I want to upload something to it I need to have access to a web browser.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So even though PeerTube has the features I need, and I really like the project, it doesn&amp;rsquo;t satisfy my needs.&lt;/p&gt;
&lt;h2 id=&#34;small-web&#34;&gt;Small web!!&lt;/h2&gt;
&lt;p&gt;I hope that someday all this stuff can be available for less technical people too. It isn&amp;rsquo;t fair that only a few technologically capable people with free time can be truly free on the web.&lt;/p&gt;
&lt;p&gt;Have you built something similar? Do you have suggestions for how it can be improved? Contact me via &lt;a href=&#34;mailto:lukas@orsvarn.com&#34;&gt;email&lt;/a&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Street Smoke</title>
      <link>https://orsvarn.com/video/street-smoke/</link>
      <pubDate>Wed, 05 Jun 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/video/street-smoke/</guid>
      <description>
        
          




&lt;video controls width=&#34;100%&#34; poster=&#34;https://orsvarn.com/video/street-smoke.jpg&#34; class=&#34;video&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/video/street-smoke.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;

        
        &lt;p&gt;This is a test for my small web video setup!&lt;/p&gt;
&lt;p&gt;The setup allows me to upload a high quality video to my server, and it automatically transcodes it to a lower quality and uploads it to my website. Then I add a description and publish it. I&amp;rsquo;ll make a blog post about this soon.&lt;/p&gt;
&lt;p&gt;As for the video: I think they are relining pipes under the street. Looks pretty cool with all that steam!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Making a custom weather system</title>
      <link>https://orsvarn.com/weather-making-of/</link>
      <pubDate>Thu, 02 May 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/weather-making-of/</guid>
      <description>
        
        &lt;p&gt;The &amp;ldquo;Book of Beasts&amp;rdquo; has a weather system for The tabletop RPG &amp;ldquo;Forbidden Lands&amp;rdquo;. It didn’t fill my needs, so I made my own.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/book-of-beasts.jpg&#34;
         alt=&#34;Photograph of a book titled Forbidden Lands, Book of Beasts&#34;/&gt;&lt;figcaption&gt;Forbidden Lands, Book of Beasts.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;This post goes through my journey of creating this weather system. If you want to learn how it works and use it, &lt;a href=&#34;../weather&#34;&gt;download the system&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;what-do-i-want-to-change&#34;&gt;What do I want to change?&lt;/h2&gt;
&lt;p&gt;These are the things I want to change about the official weather system.&lt;/p&gt;
&lt;h3 id=&#34;it-feels-too-random&#34;&gt;It feels too random&lt;/h3&gt;
&lt;p&gt;You roll a six sided dice for the sky at the beginning of every day. A 1 means clear skies, a 5 means light rain, and a 6 means heavy rain. Numbers 2 to 4 has &amp;ldquo;none&amp;rdquo;, which I guess means it&amp;rsquo;s partially cloudy or cloudy.&lt;/p&gt;
&lt;p&gt;With a 1/3 chance of rainfall, the weather often changes between sunshine and rain and cloudy and rain, etc. It feels random.&lt;/p&gt;
&lt;h3 id=&#34;it-feels-too-predictable&#34;&gt;It feels too predictable&lt;/h3&gt;
&lt;p&gt;In the official system you roll new weather every morning. This means the weather is the same for an entire day. It couldn&amp;rsquo;t start clear and end with rain for instance. It&amp;rsquo;s too predictable.&lt;/p&gt;
&lt;h3 id=&#34;it-rains-too-much&#34;&gt;It rains too much&lt;/h3&gt;
&lt;p&gt;There is a 1/3 probability of rain, and half of that is probability of heavy rain. That means on average more than one day of heavy rain every week. I feel this makes the game too punishing, and less believable.&lt;/p&gt;
&lt;h3 id=&#34;unbelievable-weather-conditions&#34;&gt;Unbelievable weather conditions&lt;/h3&gt;
&lt;p&gt;It is possible to get heavy rain + storm winds + hot weather. That doesn&amp;rsquo;t make sense to me because I have never experienced it, and I wouldn&amp;rsquo;t know how to describe it as a GM.&lt;/p&gt;
&lt;h2 id=&#34;gathering-data&#34;&gt;Gathering data&lt;/h2&gt;
&lt;p&gt;Before thinking about how the system should work, I set out to gather weather data to base it on. I felt like having the data would help me build the system.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/vaxjo-map.png&#34;
         alt=&#34;A map of a part of Europe, with a pin in Växjö, Sweden&#34;/&gt;&lt;figcaption&gt;Växjö&amp;rsquo;s location. Copyright OpenStreetMap contributors.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;I interpret the &amp;ldquo;Ravenland&amp;rdquo; setting as Scandinavian. So I used &lt;a href=&#34;https://www.smhi.se/data/meteorologi/ladda-ner-meteorologiska-observationer/#param=totalCloudCover,stations=core,stationid=64520&#34;&gt;historical weather data for the city of Växjö&lt;/a&gt;, made available for download by the &lt;a href=&#34;https://smhi.se&#34;&gt;Swedish Meteorological and Hydrological Institute&lt;/a&gt; (SMHI).&lt;/p&gt;
&lt;p&gt;I chose Växjö because it&amp;rsquo;s..&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Far from the coast, coastal weather is different from inland weather.&lt;/li&gt;
&lt;li&gt;Located in Scandinavia, which is where I interpret the &amp;ldquo;Ravenland&amp;rdquo; setting to be.&lt;/li&gt;
&lt;li&gt;Fun to use data from a place close to where I live.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is the data I used:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Amount of clouds measured in percent several times per day from 1939 to 1996, to learn how much clear and cloudy weather there is.&lt;/li&gt;
&lt;li&gt;Rain amount measured in millimeters per hour from 1995 to 2024 to learn how much it rains.&lt;/li&gt;
&lt;li&gt;Average and maximum wind speeds measured from 1995 to 2024 to get an idea of how often the wind blows at different speeds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I downloaded these from SMHI as &lt;code&gt;.csv&lt;/code&gt; files which I opened in LibreOffice Calc. There I made calculations to learn how often different kinds of weather happens.&lt;/p&gt;
&lt;h2 id=&#34;interpreting-the-data&#34;&gt;Interpreting the data&lt;/h2&gt;
&lt;p&gt;I used the same three categories as the official system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sky&lt;/li&gt;
&lt;li&gt;Wind&lt;/li&gt;
&lt;li&gt;Temperature&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I looked up what words SMHI themselves use to describe different amounts of rainfall and wind. Coming up with my own threshold values would be an entire project on its own.&lt;/p&gt;
&lt;h3 id=&#34;sky&#34;&gt;Sky&lt;/h3&gt;
&lt;p&gt;The sky represents what you see when looking up: How many clouds are there? How much is it raining? In the game this affects &lt;code&gt;LEAD THE WAY&lt;/code&gt; rolls. These are the probabilities I found:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/cloud-cover-probabilities.svg&#34;
         alt=&#34;A bar chart titled Real cloud cover probabilities with these values: Clear 15%, partly cloudy 42%, cloudy 43%&#34;/&gt;&lt;figcaption&gt;Real cloud cover probabilities.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;The rain data says that there is some rain 13% of the time. If I subtract that from the probability of &amp;ldquo;cloudy&amp;rdquo;, I can combine it to the previous probabilities, giving me this:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/sky-probabilities.svg&#34;
         alt=&#34;A bar chart titled Real sky probabilities with these values: Clear 15%, partly cloudy 42%, cloudy 30%, rain 13%, deluge less than 1%&#34;/&gt;&lt;figcaption&gt;Real sky probabilities.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;The official system does not have &amp;ldquo;partly cloudy&amp;rdquo; and &amp;ldquo;cloudy&amp;rdquo;. Instead it&amp;rsquo;s simply &amp;ldquo;none&amp;rdquo;. I prefer having more information to go on when describing the weather, so I included these two states, even though they don&amp;rsquo;t have any gameplay impact.&lt;/p&gt;
&lt;h3 id=&#34;wind&#34;&gt;Wind&lt;/h3&gt;
&lt;p&gt;This is how fast the wind blows. In the game this affects &lt;code&gt;MAKE CAMP&lt;/code&gt; rolls.&lt;/p&gt;
&lt;p&gt;This is the data I ended up with:&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/wind-probabilities.svg&#34;
         alt=&#34;A chart titled Real wind probabilities in meters per second&#34;/&gt;&lt;figcaption&gt;Real wind probabilities.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;It seems like very strong and very weak winds happen less than 5% of the time, and otherwise the wind is weak or moderate about half the time, depending on if you&amp;rsquo;re looking at the maximum or average wind.&lt;/p&gt;
&lt;p&gt;I find this data pretty hard to use. Wind changes speed often, and I&amp;rsquo;m not sure if the maximum speed or the average speed matters more. In the final system I based my probabilities on the average wind speed because I liked that it made moderate winds less common.&lt;/p&gt;
&lt;h3 id=&#34;temperature&#34;&gt;Temperature&lt;/h3&gt;
&lt;p&gt;I couldn’t come up with how to use the data for anything useful. So instead I made up a temperature distribution, and paired them with the combinations of sky and wind that makes sense.&lt;/p&gt;
&lt;p&gt;So a clear day without wind has a high temperature, and a rainstorm has a low temperature.&lt;/p&gt;
&lt;p&gt;In the game, a high or low temperature gives the &lt;code&gt;COLD&lt;/code&gt; or &lt;code&gt;THIRSTY&lt;/code&gt; status effects in certain situations.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/weather-making-of/images/gamemasters-guide.jpg&#34; alt=&#34;&#34;  /&gt;
&lt;/p&gt;
&lt;h2 id=&#34;how-do-i-randomize-it&#34;&gt;How do I randomize it?&lt;/h2&gt;
&lt;p&gt;The official system uses one six-sided dice (D6) per weather aspect: wind, sky, and temperature. This is fast and easy.&lt;/p&gt;
&lt;p&gt;I want my system to be fast and easy too, but I also want it to feel believable. These two requirements go against each other. If it&amp;rsquo;s too easy, there isn&amp;rsquo;t enough input into the system to create something believable, and if it&amp;rsquo;s too complex it becomes annoying to use.&lt;/p&gt;
&lt;p&gt;First I tried tweaking the official system by adding another dice roll if you ended up on rain. My tweaks made it more believable, but it also added some complexity. I want a solution with the same or lower complexity, and more believability. I wanted a win-win, not a win-loss.&lt;/p&gt;
&lt;p&gt;So I tried using a deck of cards instead. I think this would be a good solution if I had cards with the weather printed on each card. I didn&amp;rsquo;t like the idea of requiring making or ordering a deck of cards however, and I couldn&amp;rsquo;t make a weather table that was easy to read. So I abandoned this idea because it was too complex to use.&lt;/p&gt;
&lt;p&gt;The solution I ended up with was rolling a &amp;ldquo;D66&amp;rdquo;. This means rolling two D6, but keeping track of which is which, for instance having one white and one red dice. This gives 36 different possible outcomes. 36 is enough outcomes to give a lot of variety, while keeping the table small.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/weather-making-of/images/table-preview.svg&#34; alt=&#34;&#34;  /&gt;
&lt;/p&gt;
&lt;p&gt;So I make a table with 36 rows, and each row has a set of sky, wind, and temperature. This gives us:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Roll two D6&lt;/li&gt;
&lt;li&gt;Look in one D66 table&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think this is at least as easy as the official system which goes like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Roll three D6&lt;/li&gt;
&lt;li&gt;Look in three D6 tables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since each weather condition is predefined, I can avoid unbelievable weather such as a warm rainstorm. This makes it more believable than the official system. I found the win-win I was looking for!&lt;/p&gt;
&lt;p&gt;The big drawback of this solution is that it has fewer possible combinations. For instance, since there&amp;rsquo;s a less than 1% chance of deluge in real life, I&amp;rsquo;ll map this to one of the outcomes, giving it a 1/36 chance, or 3%. However, this means the table can&amp;rsquo;t have a deluge with no wind, and a deluge with lots of wind, because I only have one deluge in the table.&lt;/p&gt;
&lt;p&gt;I make this tradeoff to keep the system simple. I don&amp;rsquo;t think it&amp;rsquo;ll be noticeable by the players in the game, and the game master can always spice things up if they get repetitive.&lt;/p&gt;
&lt;h2 id=&#34;creating-the-table&#34;&gt;Creating the table&lt;/h2&gt;
&lt;p&gt;This is a matter of mapping each aspect of the weather data to 36 possibilities.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/real-table-comparison.svg&#34;
         alt=&#34;Comparing real probabilities and dice probabilities for the sky.&#34;/&gt;&lt;figcaption&gt;Comparing real probabilities and dice probabilities for the sky.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;Then I put the mappings into a table, and compress duplicate entries to make it easier to read.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/final-table.svg&#34;
         alt=&#34;The final table.&#34;/&gt;&lt;figcaption&gt;The final table.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;That’s it, I now have an easy to use system that produces believable weather.&lt;/p&gt;
&lt;p&gt;I wanted to take it further, so I added two more aspects to the system.&lt;/p&gt;
&lt;h2 id=&#34;removing-the-daily-schedule&#34;&gt;Removing the daily schedule&lt;/h2&gt;
&lt;p&gt;In the official system, weather changes each morning. I want it to be less predictable.&lt;/p&gt;
&lt;p&gt;Forbidden Lands splits days up into Quarter Days. I found the sweet spot to be rolling a D6, and the result is the number of Quarter Days the weather stays.&lt;/p&gt;
&lt;p&gt;I keep track of this by keeping the rolled dice, and each Quarter Day I reduce it by one. When it hits zero, I roll for new weather.&lt;/p&gt;
&lt;h2 id=&#34;believable-temperature&#34;&gt;Believable temperature&lt;/h2&gt;
&lt;p&gt;I made a temperature scale going from one to five. When temperature is at 1 or 5, players risk getting &lt;code&gt;COLD&lt;/code&gt; or &lt;code&gt;THIRSTY&lt;/code&gt; according to the same rules as in the official system.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The month of the year determines the base temperature.&lt;/li&gt;
&lt;li&gt;The &amp;ldquo;high&amp;rdquo; and &amp;ldquo;low&amp;rdquo; temperature in the current weather adjusts this up or down by one.&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s dark outside, temperature is reduced by one. The official game has a simple system that determines which Quarter Days are dark depending on the month.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/temperature.svg&#34;
         alt=&#34;Left: What temperature each grade roughly feels like. Right: Temperature grade per season.&#34;/&gt;&lt;figcaption&gt;Left: What temperature each grade roughly feels like. Right: Temperature grade per season.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;To give a better understanding of the temperature scale, each point in the scale is roughly mapped to a real world temperature.&lt;/p&gt;
&lt;p&gt;This temperature scale doesn&amp;rsquo;t make sense on a technical level. For instance, a cold summer night won&amp;rsquo;t reach freezing temperatures. But my thought process is that if the weather is &amp;ldquo;cold&amp;rdquo;, that&amp;rsquo;s because it&amp;rsquo;s windy or rainy. And at night it can get quite cold, maybe so cold that it almost feels like zero degrees with enough rain and/or wind?&lt;/p&gt;
&lt;p&gt;Maybe not, but I&amp;rsquo;m fine with this bit of weirdness, because the system only needs to make the time of year and time of day impact the temperatures in a believable way. I think it does.&lt;/p&gt;
&lt;p&gt;This simple temperature system is the final puzzle piece that brings it all together.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Roll three dice to determine these aspects of the weather:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How cloudy and rainy it is&lt;/li&gt;
&lt;li&gt;How windy it is&lt;/li&gt;
&lt;li&gt;How warm it feels&lt;/li&gt;
&lt;li&gt;How long it is until the weather changes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The temperature is decided by the weather table, the time of day, and time of year.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;../weather&#34;&gt;Download the system&lt;/a&gt;&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Believable Weather for Forbidden Lands</title>
      <link>https://orsvarn.com/weather/</link>
      <pubDate>Thu, 18 Apr 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/weather/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;files/orsvarn-weather-system-0.1.0.pdf&#34;&gt;Download PDF&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;files/orsvarn-weather-system-0.1.0.ods&#34;&gt;Download ODS&lt;/a&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;This is a weather system for the tabletop roleplaying game &lt;a href=&#34;https://freeleaguepublishing.com/games/forbidden-lands/&#34;&gt;Forbidden Lands&lt;/a&gt;. It&amp;rsquo;s based on meteorological data from southern Sweden. The goal of the system is to feel believable and be easy to use. It decides three aspects in one roll of three D6:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How cloudy and rainy it is.&lt;/li&gt;
&lt;li&gt;How warm or cold it is.&lt;/li&gt;
&lt;li&gt;For how long the weather lasts.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/preview.svg&#34;
         alt=&#34;A document titled Orsvärn Weather System, with a table and some instructions&#34;/&gt;&lt;figcaption&gt;Preview of the PDF.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;h2 id=&#34;using-the-system&#34;&gt;Using the System&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Roll a D66 (explained below).&lt;/li&gt;
&lt;li&gt;Pick the row in the table with that number, that&amp;rsquo;s your weather.&lt;/li&gt;
&lt;li&gt;Calculate &lt;code&gt;TEMPERATURE&lt;/code&gt;.
&lt;ul&gt;
&lt;li&gt;Start with the base &lt;code&gt;TEMPERATURE&lt;/code&gt; for the current month of the year, from the Seasonal &lt;code&gt;TEMPERATURE&lt;/code&gt; table.&lt;/li&gt;
&lt;li&gt;Add the Temp difference from your row in the weather table.&lt;/li&gt;
&lt;li&gt;Subtract one &lt;code&gt;TEMPERATURE&lt;/code&gt; if it&amp;rsquo;s dark according to the &amp;ldquo;Light &amp;amp; Darkness&amp;rdquo; table on page 147 in the player&amp;rsquo;s handbook.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Roll another D6 to determine how many Quarter Days the weather lasts.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A &amp;ldquo;D66&amp;rdquo; is when you roll two D6 with one being the ones place, and the other being the tens place. So if you roll a 2 and a 4, you have 24. This works best with two different colored dice to make it easier to distinguish between them.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/weather/images/dice.jpg&#34; alt=&#34;Some dice&#34;  /&gt;
&lt;/p&gt;
&lt;h2 id=&#34;gameplay-effects&#34;&gt;Gameplay Effects&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Sky affects &lt;code&gt;LEAD THE WAY&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Clear: +1&lt;/li&gt;
&lt;li&gt;Rain: -1&lt;/li&gt;
&lt;li&gt;Deluge: -2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Wind affects &lt;code&gt;MAKE CAMP&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;Calm: +1&lt;/li&gt;
&lt;li&gt;Gale: -1&lt;/li&gt;
&lt;li&gt;Storm: -2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If &lt;code&gt;TEMPERATURE&lt;/code&gt; is 5, each &lt;code&gt;HIKING&lt;/code&gt; player character must succeed with an &lt;code&gt;ENDURANCE&lt;/code&gt; roll every Quarter Day or become &lt;code&gt;THIRSTY&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;TEMPERATURE&lt;/code&gt; is 1, all player characters who are not in a camp must succeed with an &lt;code&gt;ENDURANCE&lt;/code&gt; roll every Quarter Day or become &lt;code&gt;COLD&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;On 65 &amp;amp; 66&lt;/strong&gt;: For each Quarter Day, all player characters must succeed with an &lt;code&gt;ENDURANCE&lt;/code&gt; roll to keep moving. &lt;strong&gt;On 66&lt;/strong&gt;: modify the roll by -2.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/weather/images/dice-2.jpg&#34; alt=&#34;Some dice, a playing card&#34;  /&gt;
&lt;/p&gt;
&lt;h2 id=&#34;tips&#34;&gt;Tips&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;In the instructions above I split the duration roll into a separate roll to make it easier to explain. Roll all three D6 at once to make it faster.&lt;/li&gt;
&lt;li&gt;Keep the rolled dice for future reference.&lt;/li&gt;
&lt;li&gt;Roll for the next weather early and save those dice too, then you can hint to the players what&amp;rsquo;s coming next.&lt;/li&gt;
&lt;li&gt;Make it feel more natural by letting the weather sometimes change while the players are active, not just between quarter days.&lt;/li&gt;
&lt;li&gt;When you end a session, note down the numbers on the three D6 for the next session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;thank-you-for-reading&#34;&gt;Thank You For Reading&lt;/h2&gt;
&lt;p&gt;If you want to learn how I made the system, read &lt;a href=&#34;../weather-making-of&#34;&gt;this blog post&lt;/a&gt;.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Really Simple Syndication</title>
      <link>https://orsvarn.com/rss-page/</link>
      <pubDate>Tue, 02 Jan 2024 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/rss-page/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 12px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;https://orsvarn.com/index.xml&#34;&gt;RSS feed&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;With RSS you can get updated when I make a new blog post.&lt;/p&gt;
&lt;p&gt;To use it, install an RSS reader on your computer or phone, and add this link to your reader: &lt;a href=&#34;https://orsvarn.com/index.xml&#34;&gt;https://orsvarn.com/index.xml&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The RSS reader I use on my phone is &lt;a href=&#34;https://netnewswire.com/&#34;&gt;NetNewsWire&lt;/a&gt;. Unfortunately it&amp;rsquo;s only for Apple devices, but there are plenty of alternatives.&lt;/p&gt;
&lt;h2 id=&#34;what-websites-use-this&#34;&gt;What Websites Use This?&lt;/h2&gt;
&lt;p&gt;It feels like almost every website on the Internet has an RSS feed. Try adding some to your reader! Most readers automatically finds the feed for you if you give it the website&amp;rsquo;s address.&lt;/p&gt;
&lt;p&gt;These are some of the feeds I subscribe to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;People&amp;rsquo;s blogs.
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://www.todepond.com/wikiblogarden/&#34;&gt;Todepond&lt;/a&gt;, Lu Wilson&amp;rsquo;s daily personal blog.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://blog.demofox.org/&#34;&gt;The blog at the bottom of the sea&lt;/a&gt;, Programming, Graphics, Gamedev, Exotic Computation, Audio Synthesis.&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://www.jendrikillner.com/post/&#34;&gt;Jendrik Illner - 3D Programmer&lt;/a&gt;, posts &amp;ldquo;Graphics Programming weekly&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Blog feeds for game engines and other software, and game news sites.&lt;/li&gt;
&lt;li&gt;YouTube channels, this is how I follow YouTube channels without having a YouTube account!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy reading!&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Simple Website CI</title>
      <link>https://orsvarn.com/simple-website-ci/</link>
      <pubDate>Sun, 12 Nov 2023 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/simple-website-ci/</guid>
      <description>
        
        &lt;p&gt;This is how running &lt;code&gt;git push deploy&lt;/code&gt; automatically builds my website on the server and deploys it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I build my website with the static site generator Hugo.&lt;/li&gt;
&lt;li&gt;I host my website anywhere,
currently renting from Hetzner for €5 per month.&lt;/li&gt;
&lt;li&gt;I have the website Git repository on the server,
and I use that repo as a Git remote on my computer.&lt;/li&gt;
&lt;li&gt;The server has a Git hook that runs a script when I push to the repo.&lt;/li&gt;
&lt;li&gt;The script builds the website with Hugo, and copies it to the public html folder, making it go live.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I want to deploy the latest version of the website I just do &lt;code&gt;git push deploy&lt;/code&gt; to push to the server’s Git. My changes go live after a few seconds thanks to the hook and the script.&lt;/p&gt;
&lt;p&gt;Since the server builds the website, the website can be updated from any device with Git, even phones.&lt;/p&gt;
&lt;h1 id=&#34;details-about-my-setup&#34;&gt;Details About My Setup&lt;/h1&gt;
&lt;p&gt;Here are some details about my setup.&lt;/p&gt;
&lt;h2 id=&#34;pushing-to-the-server&#34;&gt;Pushing to the Server&lt;/h2&gt;
&lt;p&gt;This is how I set up so I can push to the server.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;On the server:
&lt;ol&gt;
&lt;li&gt;Add my public SSH key to the &lt;code&gt;~/.ssh/authorized\_keys&lt;/code&gt; file. This gives me the ability to push to the repos on this user.&lt;/li&gt;
&lt;li&gt;Set up a bare Git respository for the website source files with &lt;code&gt;git init --bare website.git&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;On my computer:
&lt;ol&gt;
&lt;li&gt;Do &lt;code&gt;git remote add deploy user@orsvarn.com:website.git&lt;/code&gt; to add the bare server repo as a remote.&lt;/li&gt;
&lt;li&gt;The bare repository on the server doesn&amp;rsquo;t have anything in it, so I push the website to it with &lt;code&gt;git push deploy&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now the Git remote on the server is set up.&lt;/p&gt;
&lt;h2 id=&#34;git-hook-to-build--deploy&#34;&gt;Git Hook to Build &amp;amp; Deploy&lt;/h2&gt;
&lt;p&gt;Let’s set up the Git hook and the script that builds and deploys the website. &lt;strong&gt;All these steps are done on the server.&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Download Hugo.&lt;/li&gt;
&lt;li&gt;Clone the Git repository with &lt;code&gt;git clone website.git website&lt;/code&gt;.
&lt;ol&gt;
&lt;li&gt;Note: The bare repository holds all the Git stuff, but does not have a working copy. This step allows the server to actually access the files in its repository. After this step there are two folders, one called &lt;code&gt;website.git&lt;/code&gt; containing the &amp;ldquo;bare&amp;rdquo; repo, and one called &lt;code&gt;website&lt;/code&gt; containing the &amp;ldquo;client&amp;rdquo; repo that pulls files from the &amp;ldquo;bare&amp;rdquo; repo.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Create the file &lt;code&gt;~/website.git/.git/hooks/post-receive&lt;/code&gt; &lt;strong&gt;in the &amp;ldquo;bare&amp;rdquo; repo.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Write a script in the new hook file to pull the latest version, build it, and copy it to the public website folder.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is the script I use:&lt;/p&gt;
&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; style=&#34;color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;&#34;&gt;&lt;code class=&#34;language-bash&#34; data-lang=&#34;bash&#34;&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Constants&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare REPO_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/home/git/website
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;declare DEPLOY_DIR&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;/var/www/website
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Checkout files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;git work-tree&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;$REPO_DIR --git-dir&lt;span style=&#34;color:#f92672&#34;&gt;=&lt;/span&gt;“$REPO_DIR.git” checkout -f
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;cd $REPO_DIR
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rm -rf public
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;./home/git/bin/hugo
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;&lt;span style=&#34;color:#75715e&#34;&gt;# Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style=&#34;display:flex;&#34;&gt;&lt;span&gt;rsync -a public/ $DEPLOY_DIR --delete-after
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With this post-receive script, pushing to the server will automatically build and deploy the website.&lt;/p&gt;
&lt;h1 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;This setup makes it easy to update my website. I don’t rely on any tools that can be taken from me, or services that can be shut down by someone else.&lt;/p&gt;
&lt;p&gt;I’m confident the setup will keep working for tens of years, which gives me peace of mind.&lt;/p&gt;
Contact me via &lt;a href=&#34;mailto:lukas@orsvarn.com&#34;&gt;email&lt;/a&gt;.
&lt;h1 id=&#34;thanks&#34;&gt;Thanks&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;This post is a response to &lt;a href=&#34;https://www.todepond.com/wikiblogarden/my-wikiblogarden/hosting/rubbish-options/&#34;&gt;Lu’s post about hosting options&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Thanks to iliazeus for &lt;a href=&#34;https://lor.sh/@iliazeus/111384898270207362&#34;&gt;posting&lt;/a&gt; that there are Git clients and Markdown editors for phones.&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Taped UV Seams</title>
      <link>https://orsvarn.com/uv-blend/</link>
      <pubDate>Sun, 28 May 2023 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/uv-blend/</guid>
      <description>
        
        &lt;p&gt;I was playing &lt;em&gt;The Legend of Zelda: Tears of the Kingdom&lt;/em&gt; and was wondering how they managed to create seamless textures on organic models.&lt;/p&gt;
&lt;p&gt;At first I thought they created two UV maps and blended between them. But then I found &lt;a href=&#34;https://twitter.com/Keegan_Keene/status/1160239838509064197&#34;&gt;this Twitter thread&lt;/a&gt; where Keegan Keene describes how materials are blended in Skyward Sword. They don&amp;rsquo;t name the technique, so I&amp;rsquo;ll call them &amp;ldquo;Taped UV Seams&amp;rdquo; based on their tweets.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/keegan-keene.png&#34;
         alt=&#34;A tweet from Keegan Keene reads &amp;#34;This is also used to hide seams with &amp;#34;tapes&amp;#34; of mesh. You can see how these mesh tapes hide seams just were it is needed to blend things together. Also will notice all the shadows are vertex painted in too.&amp;#34;&#34;/&gt;
&lt;/figure&gt;


&lt;p&gt;The idea is to create a mesh that covers the seam, and apply a material to it that fades in over the seam to hide it. This simple and lightweight technique has apparently been used in many games. I&amp;rsquo;m surprised that I haven&amp;rsquo;t stumbled across this before!&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; loop poster=&#34;/uv-blend/videos/taped-uv.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/uv-blend/videos/taped-uv.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;p&gt;A variation on the technique is to instead of covering the seam entirely, place a tape on one side of the seam that continues and fades out the UV unwrap from the other side. I think this is the technique they use in the new Zelda game.&lt;/p&gt;
&lt;h2 id=&#34;benefits-for-height-maps&#34;&gt;Benefits For Height Maps&lt;/h2&gt;
&lt;p&gt;If you use a height map for the terrain you only have to store the height of each point. This is possible because the location of each height point can be inferred by its location in the list of data. This takes up only a third of the data that true 3D data would, but it means you can&amp;rsquo;t create overhangs and caves.&lt;/p&gt;
&lt;p&gt;But wait a second, Tears of the Kingdom uses a height map, but also has caves that blend together with the terrain, so how do they do it?&lt;/p&gt;
&lt;p&gt;I believe they use taped UV seams to allow them to use the same materials for the terrain and their cave meshes, making everything blend really well together.&lt;/p&gt;
&lt;p&gt;So if they want to have an overhanging cliff, which is impossible with a height map, they can use the same tiling grass and rock textures they use on the terrain on the cliff.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;ve managed to create the feeling that they&amp;rsquo;re using a voxel-based terrain solution that some games use, but with simpler technology, a smaller memory cost, and while giving more control to artists than a voxel-based solution can.&lt;/p&gt;
&lt;p&gt;This is a very nice solution from a technology standpoint.&lt;/p&gt;
&lt;h2 id=&#34;triplanar-mapping-comparison&#34;&gt;Triplanar Mapping Comparison&lt;/h2&gt;
&lt;p&gt;Triplanar mapping is when you project a texture from the three axis directions and blend between them based on the direction the surface is facing (the normal of the surface).&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s worth comparing these techniques, because they solve some of the same problems.&lt;/p&gt;
&lt;p&gt;Here is a comparison of the similarities and differences between UV blending and triplanar mapping.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Similarities
&lt;ul&gt;
&lt;li&gt;The seams are faded so it looks more or less seamless.&lt;/li&gt;
&lt;li&gt;It works with tiling textures.&lt;/li&gt;
&lt;li&gt;Assets can be scaled up and down without affecting texel density since UVs can be scaled with it to compensate.&lt;/li&gt;
&lt;li&gt;UVs can be automatically offset for each instance to create variety.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advantages of taped seams
&lt;ul&gt;
&lt;li&gt;There are fewer seams than with triplanar.&lt;/li&gt;
&lt;li&gt;You get to pick where the fading seams go.&lt;/li&gt;
&lt;li&gt;You can have sharp seams if you want to.&lt;/li&gt;
&lt;li&gt;You can have larger continuous areas without seams.&lt;/li&gt;
&lt;li&gt;You can have more directionality in the unwrap, which works well for things like trees, where you might want the grain of the wood to follow a certain direction.&lt;/li&gt;
&lt;li&gt;You don&amp;rsquo;t &lt;em&gt;have&lt;/em&gt; to use tiling textures, it can be used as a general unwrap technique as well.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Advantages of triplanar
&lt;ul&gt;
&lt;li&gt;It requires less work, as you don&amp;rsquo;t have to UV unwrap or create tape meshes.&lt;/li&gt;
&lt;li&gt;It needs less data in the shader since UVs aren&amp;rsquo;t used.&lt;/li&gt;
&lt;li&gt;It can be used in entirely dynamic scenarios where geometry is created and destroyed on the fly.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Taped UV Seams is a cheap way to get seamless textures.
It looks better than triplanar mapping,
but requires more work.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Vismut 0.6</title>
      <link>https://orsvarn.com/vismut-0-6/</link>
      <pubDate>Wed, 12 Oct 2022 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/vismut-0-6/</guid>
      <description>
        
        &lt;p&gt;Vismut is the open source procedural texturing tool for everyone.
&lt;a href=&#34;../introducing-vismut&#34;&gt;Read the introduction&lt;/a&gt; to learn more.&lt;/p&gt;
&lt;p&gt;Version 0.6 comes with a few important features,
and marks two milestones for the project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;We received our first contributions,
from &lt;a href=&#34;https://gitlab.com/theorr&#34;&gt;@theorr&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;We now have a &lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/CONTRIBUTING.md&#34;&gt;CONTRIBUTING.md&lt;/a&gt; and a &lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/CODE_OF_CONDUCT.md&#34;&gt;CODE_OF_CONDUCT.md&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;contributingmd&#34;&gt;&lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/CONTRIBUTING.md&#34;&gt;CONTRIBUTING.md&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The project is still starting up,
and as the founder of the project I&amp;rsquo;m still learning a lot.
With our first contributions,
I learned that the common &amp;ldquo;merge request&amp;rdquo; process can be cumbersome for both contributors and maintainers.
This made me think about if it could be done differently.&lt;/p&gt;
&lt;p&gt;I found a development process called &lt;a href=&#34;https://rfc.zeromq.org/spec/42/&#34;&gt;C4&lt;/a&gt;,
which was partly created to solve this problem.
&lt;a href=&#34;https://rfc.zeromq.org/spec/42/&#34;&gt;C4&lt;/a&gt; was not entirely compatible with my project,
and I thought it was a bit too complicated,
so I wrote a new process based on it:
The &lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/docs/PROCESS.md&#34;&gt;Vismut Contribution Process&lt;/a&gt; (VCP).&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re interested you can read the entire &lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/docs/PROCESS.md&#34;&gt;process&lt;/a&gt; document.
Here is a short summary:&lt;/p&gt;
&lt;p&gt;The VCP tries to make it easy to contribute to the project.
Making it easy to contribute makes it&amp;rsquo;s more fun to contrinute,
less work for maintainers,
and the project can develop faster.
The tradeoff is that maintainers have less say in what gets merged.
I don&amp;rsquo;t know how well the process will work,
because I couldn&amp;rsquo;t find many projects that use a process like this.
But there are some big potential benefits,
so I think it&amp;rsquo;s worth trying it to see if it works.&lt;/p&gt;
&lt;h2 id=&#34;code_of_conductmd&#34;&gt;&lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/blob/main/CODE_OF_CONDUCT.md&#34;&gt;CODE_OF_CONDUCT.md&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Vismut code of conduct is based on the &lt;a href=&#34;https://www.rust-lang.org/policies/code-of-conduct&#34;&gt;Rust code of conduct&lt;/a&gt;,
but with some changes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s shorter&lt;/strong&gt;:
I think a shorter text is easier to understand,
and people are more likely to read it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It has simpler language&lt;/strong&gt;:
Simpler language is easier to understand and more accessible.
Not everyone can easily understand written English.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It contains a list of encouraged behavior&lt;/strong&gt;:
Similarly to how it&amp;rsquo;s useful to have a list of rules to help reduce bad behavior,
it&amp;rsquo;s also useful to have a list of encouraged behavior to help promote desired behavior.
We don&amp;rsquo;t want to create a community that just &amp;ldquo;isn&amp;rsquo;t bad&amp;rdquo;,
we want to create one that is good!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;new-features&#34;&gt;New Features&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Save, Open &amp;amp; New&lt;/strong&gt;:
You can now save a node graph into a .json file,
which can then be opened.
These are of course foundational features,
so I am very grateful to &lt;a href=&#34;https://gitlab.com/theorr&#34;&gt;@theorr&lt;/a&gt; for implementing this!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Grid&lt;/strong&gt;:
All nodes now live on a grid,
and you can&amp;rsquo;t place a node on top of another node.&lt;/p&gt;
&lt;p&gt;In node editors that don&amp;rsquo;t force a grid,
some people spend time aligning their nodes to the grid.
They maybe press a button in the user interface to align the nodes automatically,
or they align them by hand.
This takes time and makes it harder to get into a good flow when working.&lt;/p&gt;
&lt;p&gt;By forcing everything to be on a grid we eliminate this unnecessary work,
and make it easier and more predictable to automatically moves nodes around.
The grid makes it easier to implement features like moving nodes out of the way when a node is placed somewhere,
or to automatically formatting the node graph.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Other Improvements&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Click connections and press &lt;code&gt;X&lt;/code&gt; or &lt;code&gt;delete&lt;/code&gt; to remove them.
Thanks &lt;a href=&#34;https://gitlab.com/theorr&#34;&gt;@theorr&lt;/a&gt;!&lt;/li&gt;
&lt;li&gt;Redesigned nodes to get them to fit in the grid.&lt;/li&gt;
&lt;li&gt;Reduced CPU usage when application is in the background.&lt;/li&gt;
&lt;li&gt;Reduced latency between input and visuals.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;whats-next&#34;&gt;What&amp;rsquo;s Next&lt;/h2&gt;
&lt;p&gt;Vismut still only supports shuffling channels,
and now it&amp;rsquo;s time to do something about that.
Vismut is missing four nodes that are foundational for procedural texturing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mix&lt;/strong&gt;:
perform math with images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Noise&lt;/strong&gt;:
generate images to build textures from&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offset&lt;/strong&gt;:
shift pixels around in an image&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Graph&lt;/strong&gt;:
re-use node graphs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The addition of these four nodes will mark another major milestone for Vismut:
The ability to generate many types of materials.&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.com/vismut-org/vismut&#34;&gt;Check out the code&lt;/a&gt; on GitLab. &lt;a href=&#34;https://gitlab.com/vismut-org/vismut/-/releases/v0.6.0&#34;&gt;Check out the release&lt;/a&gt; to download the latest binaries for Windows or Linux.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Vismut Core Rewritten</title>
      <link>https://orsvarn.com/vismut-architecture/</link>
      <pubDate>Sat, 23 Apr 2022 00:00:00 +0000</pubDate>
      
      <guid>https://orsvarn.com/vismut-architecture/</guid>
      <description>
        
        &lt;p&gt;For version 0.5 of &lt;a href=&#34;https://orsvarn.com/introducing-vismut&#34;&gt;Vismut&lt;/a&gt;, I rewrote &lt;strong&gt;Vismut Core&lt;/strong&gt;, the backend library which handles all the node processing. The rewrite made Vismut Core easier to use, less buggy, and easier to maintain.&lt;/p&gt;
&lt;p&gt;Before getting started, let&amp;rsquo;s define some words. These definitions are specific to Vismut Core, and may mean slightly different things in other contexts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DAG&lt;/strong&gt; - Short for &amp;ldquo;directed acyclic graph&amp;rdquo;. It&amp;rsquo;s a collection of &amp;ldquo;nodes&amp;rdquo; and &amp;ldquo;edges&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node&lt;/strong&gt; - An operation that is performed as part of generating a texture. For instance &amp;ldquo;generate a noise image&amp;rdquo;, or &amp;ldquo;multiply two images together&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge&lt;/strong&gt; - A connection between two nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/dag.png&#34;
         alt=&#34;A directed acyclic graph&#34;/&gt;
&lt;/figure&gt;


&lt;h2 id=&#34;the-old-architecture&#34;&gt;The Old Architecture&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s how the old architecture worked:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The library provided a struct called &amp;ldquo;Engine&amp;rdquo;, which spawned a thread where it ran an update loop to calculate nodes.&lt;/li&gt;
&lt;li&gt;All the Engine&amp;rsquo;s data, like DAGs, edges, and nodes, were wrapped in &lt;code&gt;Arc&lt;/code&gt;s and &lt;code&gt;RwLock&lt;/code&gt;s so they could be read and written to without needing to own the data. Accessing data like this between threads is called shared memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/old-architecture.svg&#34;
         alt=&#34;A relational diagram showing two-way connections between the GUI thread and the Engine data, and the Engine thread and the Engine data&#34;/&gt;&lt;figcaption&gt;In the old architecture, the Engine&amp;rsquo;s data was always accessible through shared memory.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;Running the Engine and the GUI on separate threads like this resulted in a GUI that was always responsive. However, there were significant downsides to this approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It wasn&amp;rsquo;t flexible.&lt;/strong&gt; The library user wants to choose when the library runs. Running Vismut Core in its own thread makes sense for Vismut, but it isn&amp;rsquo;t a good choice in every case, like for the future command-line interface.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The types were annoying to work with.&lt;/strong&gt; For instance, to read a node you needed to do something like this:
&lt;code&gt;let my_node = engine.lock()?.node(node_id)?.node_type().lock()?;&lt;/code&gt;. That&amp;rsquo;s a lot of code for just getting a node, in the new architecture it&amp;rsquo;s more similar to this: &lt;code&gt;let my_node = engine.node(node_id)?;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I never managed to fix all timing bugs.&lt;/strong&gt; Since changes could be made to the Engine at any point in the execution, changes could sneak in at unexpected moments, causing bugs. To avoid this, I tried to ensure I held locks in the Engine&amp;rsquo;s loop for longer, but these issues were very hard to debug. Holding locks for longer also meant the GUI had a harder time to get a lock, making the next issue worse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Getting locks was unreliable.&lt;/strong&gt; I never wanted to block when executing the user interface, so when getting a lock on the Engine, I just &amp;ldquo;tried&amp;rdquo; to get it and aborted if I couldn&amp;rsquo;t. However, the GUI only updates 60 times per second usually, so sometimes several frames would pass without being able to get a lock, leading to delays in the GUI.&lt;/p&gt;
&lt;p&gt;So while the GUI was always responsive, sometimes you had to wait to see the results because it got unlucky with the locks. This didn&amp;rsquo;t play well with &lt;code&gt;bevy_egui&lt;/code&gt; either, which is an immediate mode GUI library I&amp;rsquo;m using. If I couldn&amp;rsquo;t get a lock on the Engine to read data from it, I didn&amp;rsquo;t have any data to give to Egui, which caused parts of the GUI to flicker depending on if it could get a lock.&lt;/p&gt;
&lt;h2 id=&#34;the-new-architecture&#34;&gt;The New Architecture&lt;/h2&gt;
&lt;p&gt;Instead of spawning a thread in the library and running a loop there, the Engine is just a regular owned struct. We aren&amp;rsquo;t using any &amp;ldquo;shared memory&amp;rdquo; types like &lt;code&gt;Arc&lt;/code&gt; and &lt;code&gt;RwLock&lt;/code&gt;, except for a few &lt;code&gt;Mutex&lt;/code&gt;es to ensure the Engine &amp;ldquo;is &lt;code&gt;sync&lt;/code&gt;&amp;rdquo; so it can be sent between threads. The only time the Engine itself uses threads in the new architecture is when processing nodes.&lt;/p&gt;
&lt;p&gt;Instead of forcing a constant loop, you get to manually call a &amp;ldquo;run&amp;rdquo; function to start calculating nodes. This gives the user control over &lt;em&gt;when&lt;/em&gt; the Engine runs, and direct access to the Engine&amp;rsquo;s data. If you want to run it in a thread you can, and if you want to use shared memory you can, these choices are now left to the library user.&lt;/p&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/new-architecture.svg&#34;
         alt=&#34;A diagram showing what the Engine&amp;#39;s and GUI&amp;#39;s threads do in a frame&#34;/&gt;&lt;figcaption&gt;In Vismut, the Engine is sent back and forth between a thread every frame to achieve a responsive interface.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;In the old architecture, the user was interacting directly with the same nodes and edges that the Engine used for processing. In the new architecture, there are three different &amp;ldquo;Worlds&amp;rdquo;. Each World is a different representation of all the nodes and edges in the Engine, each with its own purpose:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Public World&lt;/strong&gt; - the library&amp;rsquo;s interface&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flat World&lt;/strong&gt; - a world with only one DAG&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Live World&lt;/strong&gt; - for processing nodes&lt;/li&gt;
&lt;/ul&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/data-flow.svg&#34;
         alt=&#34;A diagram of the relationships between the different worlds&#34;/&gt;&lt;figcaption&gt;The user interacts with the Public World, and The Flat and Live Worlds are generated from that.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;The &lt;strong&gt;Public World&lt;/strong&gt; is the most straightforward representation. This is the public interface, so this is where you add and edit your DAGs. It&amp;rsquo;s meant to be easy to understand and work with, and can hold several DAGs as well as nodes with DAGs inside them. Before any changes can be processed you need to call &lt;code&gt;engine.prepare()&lt;/code&gt;, doing so creates a Flat World from this Public World.&lt;/p&gt;
&lt;p&gt;In the &lt;strong&gt;Flat World&lt;/strong&gt;, all the different DAGs in the Public World have been combined into a single DAG. Dealing with things like connections between DAGs, and DAGs within nodes introduces a lot of complexity in the code. The purpose of the Flat World is to reduce that complexity by putting all nodes and edges into a single DAG. &lt;strong&gt;Instead of trying to solve the complex problem of nested and sibling DAGs, we&amp;rsquo;ve simply removed the problem!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;After the Public World has been transformed into a Flat World, a Live World is immediately created from it.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;Live World&lt;/strong&gt; is what actually gets processed in the end. Like the Flat World, this also has only one DAG, but unlike the Flat World, it has its own set of nodes. Each node in the Flat World can become several nodes in the Live World. Having a separate set of nodes for the Live World enables more reuse of logic and is more flexible.&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;engine.prepare()&lt;/code&gt; is finished the Engine is ready to be processed. To do this, the user calls &lt;code&gt;engine.run()&lt;/code&gt; repeatedly if they want to be able to pause processing, or &lt;code&gt;engine.run_until_done()&lt;/code&gt; once if they want to wait until it&amp;rsquo;s done.&lt;/p&gt;
&lt;p&gt;This post only describes the basic structure of the new architecture in broad strokes. There are many other interesting aspects, but those are posts for another day.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Here are the two biggest lessons I learned from this rewrite:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Think twice before using shared memory.&lt;/li&gt;
&lt;li&gt;Instead of solving a complex problem, consider simplifying it.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I&amp;rsquo;m excited to have a much more promising architecture now, which will hopefully allow the project to proceed smoothly. The next update will focus on grid snapping!&lt;/p&gt;
&lt;p&gt;&lt;a href=&#34;https://gitlab.com/vismut-org/vismut&#34;&gt;Code&lt;/a&gt; and &lt;a href=&#34;https://orsvarn.com/fileshare/vismut&#34;&gt;binaries&lt;/a&gt; are available. Do you have ideas for how the architecture could be improved further? Or maybe you have questions? If so, ping &lt;code&gt;@**Lukas Orsvärn**&lt;/code&gt; in the &lt;a href=&#34;https://vismut.zulipchat.com&#34;&gt;Vismut Zulip&lt;/a&gt;. 😄&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Introducing Vismut</title>
      <link>https://orsvarn.com/introducing-vismut/</link>
      <pubDate>Sun, 06 Mar 2022 09:44:59 +0100</pubDate>
      
      <guid>https://orsvarn.com/introducing-vismut/</guid>
      <description>
        
        &lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/introducing-vismut/images/wide_icon.svg&#34; alt=&#34;Vismut Icon&#34;  /&gt;
&lt;/p&gt;
&lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;https://orsvarn.com/fileshare/vismut&#34;&gt;Download&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;https://gitlab.com/vismut-org/vismut&#34;&gt;Git Repository&lt;/a&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href=&#34;http://vismut.org&#34;&gt;Vismut&lt;/a&gt; is a procedural texturing tool I&amp;rsquo;ve been
developing on and off since around 2017.&lt;/strong&gt; After many hiatuses, much learning
and several rewrites, it now finally supports its first use case: manual channel
packing. 🎉&lt;/p&gt;
&lt;p&gt;The only other free and open source tool you can do channel packing in as far as
I know is Blender. But the complexity of Blender makes it complicated to use,
while Vismut makes it really fast and easy!&lt;/p&gt;
&lt;p&gt;Channel packing means taking the red, green, blue and alpha channels from one or
more images, and creating one or more new images from them. Games often do this
to reduce the number of images needed to represent a material, and thereby
reduce memory consumption.&lt;/p&gt;
&lt;p&gt;Channel packing is of course just the first small step towards a full procedural
texturing tool.&lt;/p&gt;
&lt;h2 id=&#34;why-am-i-making-this&#34;&gt;Why Am I Making This?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/first-try.jpg&#34;
         alt=&#34;My first ever procedural textures, created in 2009.&#34;/&gt;&lt;figcaption&gt;My first ever procedural textures, created in 2009.&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;It&amp;rsquo;s been a long time since I first discovered procedural texturing, and I loved
it right from the start. Since then, I&amp;rsquo;ve looked for an open source procedural
texturing tool. I found a few, but none that align with my priorities.&lt;/p&gt;
&lt;p&gt;A few years ago I was learning the programming language Rust, and wanted to
build something in it, so I decided to build a procedural texturing tool. To my
surprise it seemed like Rust would enable me to actually build something like
this, which was amazing to me as I&amp;rsquo;m far from being a master programmer. As time
went on I spent an increasing amount of time working on it. Now I&amp;rsquo;m confident I
can turn this into a useful tool, so that&amp;rsquo;s what I&amp;rsquo;m doing! :)&lt;/p&gt;
&lt;h2 id=&#34;why-use-nodes&#34;&gt;Why Use Nodes?&lt;/h2&gt;
&lt;figure&gt;
    &lt;img src=&#34;images/screenshot.png&#34;
         alt=&#34;Vismut 0.4&amp;amp;rsquo;s graphical user interface&#34;/&gt;&lt;figcaption&gt;Vismut 0.4&amp;rsquo;s graphical user interface&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;What makes nodes so powerful is that you can go back to any part of the node
graph at any time and make changes that then propagate through the node tree to
generate a new material.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;ve created a &amp;ldquo;bathroom tile&amp;rdquo; material. When you&amp;rsquo;re done with it,
you decide to make the tiles half as big. You have a node that controls the
pattern of the tiles, so you just change one or two values in that node to make
all the tiles half as big, and that&amp;rsquo;s it, this takes seconds. &lt;strong&gt;In a traditional
digitally painted workflow, a simple change like this would involve a
significant amount of time and effort.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It also allows for a ton of reuse. Let&amp;rsquo;s say you have created a &amp;ldquo;painted wall&amp;rdquo;
material, and you want something similar but with the paint more flaked off. You
could use the existing material as a base and create the &amp;ldquo;flaked off paint&amp;rdquo;
effect that adapts to the underlying surface. And that effect could itself be
improved and reused in other materials.&lt;/p&gt;
&lt;p&gt;This is made even more powerful by the ability to take an entire node graph and
turn it into a single node that can be used in other graphs. The more time you
invest into this type of software, the more you get out of it as you build up
your personal library of nodes.&lt;/p&gt;
&lt;h2 id=&#34;what-makes-vismut-special&#34;&gt;What Makes Vismut Special?&lt;/h2&gt;
&lt;p&gt;The interface for an application is crucial. A bad interface can make a program
incredibly frustrating to use, or even cause work to be lost. &lt;strong&gt;A good interface
enables the user to work as close to the speed of thought as possible.&lt;/strong&gt; A lot
of thought and effort is being put into Vismut&amp;rsquo;s interface, here are the main
goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easy to learn&lt;/li&gt;
&lt;li&gt;Allow for mastery&lt;/li&gt;
&lt;li&gt;Always give clear feedback&lt;/li&gt;
&lt;li&gt;Always respond to input&lt;/li&gt;
&lt;li&gt;Gets out of the way, so you can focus on the task&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Vismut will use physical measurements&lt;/strong&gt; rather than &amp;ldquo;percentage of image&amp;rdquo;
values by default where possible. This allows you to zoom out of a material
after creation. Physical measurements also give materials a common ground to
stand on, making it effortless to combine them.&lt;/p&gt;
&lt;p&gt;The backend library &lt;strong&gt;Vismut Core&lt;/strong&gt; is separate from the frontend, so it can be
integrated in a game for instance. This means you can take a saved graph—which
is no larger than a few kilobytes—and generate textures from it on the user&amp;rsquo;s
computer. This can be used to achieve reduced size of game downloads, greater
customization possibilities, and more.&lt;/p&gt;
&lt;h2 id=&#34;the-future&#34;&gt;The Future&lt;/h2&gt;
&lt;p&gt;The plan is to iterate on Vismut towards a useful set of features while keeping
the tool as stable and useful as possible along the way. Feedback from users
will be an important factor, which is the reason I&amp;rsquo;m releasing it as early as
possible. I&amp;rsquo;m not only making this for myself, but also for you!&lt;/p&gt;
&lt;p&gt;The goal of the next release—version 0.5—is to rewrite the backend to use a new
architecture that vastly simplifies the code, is more robust, and makes it
easier to create a fluid interface.&lt;/p&gt;
&lt;p&gt;After that comes &amp;ldquo;the grid&amp;rdquo;. Nodes are always locked to the grid, freeing the
user from decisions that make no difference. It also allows for smarter
automatic node positioning in the future.&lt;/p&gt;
&lt;p&gt;Other upcoming features include saving and loading graphs, a ton of nodes, 2D
and 3D previews, and support for variables in the graph. All work is done in the
project&amp;rsquo;s
&lt;a href=&#34;https://gitlab.com/vismut-org/vismut&#34;&gt;GitLab repository&lt;/a&gt;, so look there for an
up-to-date roadmap and task list.&lt;/p&gt;
&lt;h2 id=&#34;conclusion&#34;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve just scratched the surface of a lot of the topics in this post, so look
forward to more detailed thoughts in the future! If you&amp;rsquo;re looking for a program
to pack channels with, or you&amp;rsquo;re just
interested, &lt;a href=&#34;https://orsvarn.com/fileshare/vismut&#34;&gt;download Vismut&lt;/a&gt; and try it
out!&lt;/p&gt;
&lt;p&gt;I love working on this project on my own, but I think working on projects
together with others enables more fun, learning and productivity. So if you&amp;rsquo;re
interested in working on Vismut, join the
&lt;a href=&#34;https://vismut.zulipchat.com&#34;&gt;Vismut Zulip&lt;/a&gt; and introduce yourself! I would be
delighted to get on a voice or video call to show you around the code, do pair
programming, discuss implementations etc. Everyone is welcome, not just
programmers! 😀&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Receiver 2</title>
      <link>https://orsvarn.com/receiver-2/</link>
      <pubDate>Mon, 09 Mar 2020 12:00:00 +0200</pubDate>
      
      <guid>https://orsvarn.com/receiver-2/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;https://www.receiver2.com/&#34;&gt;Website&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;https://store.steampowered.com/app/1129310/Receiver_2/&#34;&gt;Steam&lt;/a&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;











&lt;video controls width=&#34;100%&#34; poster=&#34;../shared/videos/receiver-2-launch-trailer.jpg&#34;&gt;
    &lt;source src=&#34;../shared/videos/receiver-2-launch-trailer.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;p&gt;Receiver 2 simulates every internal part of each firearm based on manufacturer schematics and gunsmithing resources.
Learn exactly how each sidearm works,
including how to load and unload them,
clear malfunctions,
and operate their safety features.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /receiver-2/images/gallery/promo_1_hu10a96a3b28b85f75be62017593980bc8_344978_237x0_resize_q90_box.jpg,
                        /receiver-2/images/gallery/promo_1_hu10a96a3b28b85f75be62017593980bc8_344978_474x0_resize_q90_box.jpg 2x,
                        /receiver-2/images/gallery/promo_1_hu10a96a3b28b85f75be62017593980bc8_344978_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_1_hu10a96a3b28b85f75be62017593980bc8_344978_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /receiver-2/images/gallery/promo_2_hu3dac9029b48324d0ea8ccd1a186e2ede_306563_237x0_resize_q90_box.jpg,
                        /receiver-2/images/gallery/promo_2_hu3dac9029b48324d0ea8ccd1a186e2ede_306563_474x0_resize_q90_box.jpg 2x,
                        /receiver-2/images/gallery/promo_2_hu3dac9029b48324d0ea8ccd1a186e2ede_306563_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_2_hu3dac9029b48324d0ea8ccd1a186e2ede_306563_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_3.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /receiver-2/images/gallery/promo_3_hu9612594288a613885889b4eb6fbdace0_275835_237x0_resize_q90_box.jpg,
                        /receiver-2/images/gallery/promo_3_hu9612594288a613885889b4eb6fbdace0_275835_474x0_resize_q90_box.jpg 2x,
                        /receiver-2/images/gallery/promo_3_hu9612594288a613885889b4eb6fbdace0_275835_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_3_hu9612594288a613885889b4eb6fbdace0_275835_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_4.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /receiver-2/images/gallery/promo_4_hu17850c62fc98fd99bb65a8f2f6030cd6_196143_237x0_resize_q90_box.jpg,
                        /receiver-2/images/gallery/promo_4_hu17850c62fc98fd99bb65a8f2f6030cd6_196143_474x0_resize_q90_box.jpg 2x,
                        /receiver-2/images/gallery/promo_4_hu17850c62fc98fd99bb65a8f2f6030cd6_196143_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/receiver-2/images/gallery/promo_4_hu17850c62fc98fd99bb65a8f2f6030cd6_196143_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Optimization and other technical work on levels&lt;/li&gt;
&lt;li&gt;Advised gun texturers on physically based rendering&lt;/li&gt;
&lt;li&gt;Some level design, modeling, texturing&lt;/li&gt;
&lt;li&gt;Some lighting&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Overgrowth</title>
      <link>https://orsvarn.com/overgrowth/</link>
      <pubDate>Fri, 01 Dec 2017 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/overgrowth/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;https://www.wolfire.com/overgrowth&#34;&gt;Website&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;https://store.steampowered.com/app/25000/Overgrowth&#34;&gt;Steam&lt;/a&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;











&lt;video controls width=&#34;100%&#34; poster=&#34;../shared/videos/overgrowth-1.jpg&#34;&gt;
    &lt;source src=&#34;../shared/videos/overgrowth-1.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;p&gt;Overgrowth is a fast-paced action game created by Wolfire Games over 9 years. I joined the team two years before the game&amp;rsquo;s release.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /overgrowth/images/gallery/promo_1_hue61d08429adace097d96d028055c52ed_233676_237x0_resize_q90_box.jpg,
                        /overgrowth/images/gallery/promo_1_hue61d08429adace097d96d028055c52ed_233676_474x0_resize_q90_box.jpg 2x,
                        /overgrowth/images/gallery/promo_1_hue61d08429adace097d96d028055c52ed_233676_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_1_hue61d08429adace097d96d028055c52ed_233676_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /overgrowth/images/gallery/promo_2_huca3ac706cabeabaf2875687dbcac9d83_161156_237x0_resize_q90_box.jpg,
                        /overgrowth/images/gallery/promo_2_huca3ac706cabeabaf2875687dbcac9d83_161156_474x0_resize_q90_box.jpg 2x,
                        /overgrowth/images/gallery/promo_2_huca3ac706cabeabaf2875687dbcac9d83_161156_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_2_huca3ac706cabeabaf2875687dbcac9d83_161156_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_3.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /overgrowth/images/gallery/promo_3_hudf5a4c1cc82c64602a02b1192f5fd36f_327039_237x0_resize_q90_box.jpg,
                        /overgrowth/images/gallery/promo_3_hudf5a4c1cc82c64602a02b1192f5fd36f_327039_474x0_resize_q90_box.jpg 2x,
                        /overgrowth/images/gallery/promo_3_hudf5a4c1cc82c64602a02b1192f5fd36f_327039_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_3_hudf5a4c1cc82c64602a02b1192f5fd36f_327039_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_4.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /overgrowth/images/gallery/promo_4_hu8fc13dcc03a04f685c223baf6e1869fe_329448_237x0_resize_q90_box.jpg,
                        /overgrowth/images/gallery/promo_4_hu8fc13dcc03a04f685c223baf6e1869fe_329448_474x0_resize_q90_box.jpg 2x,
                        /overgrowth/images/gallery/promo_4_hu8fc13dcc03a04f685c223baf6e1869fe_329448_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/overgrowth/images/gallery/promo_4_hu8fc13dcc03a04f685c223baf6e1869fe_329448_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Gameplay design for some levels&lt;/li&gt;
&lt;li&gt;Testing and polishing of levels&lt;/li&gt;
&lt;li&gt;Created changelog videos&lt;/li&gt;
&lt;li&gt;Many smaller tasks, for instance I put together the art for the Steam Trading Cards&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Binding of Souls</title>
      <link>https://orsvarn.com/binding-of-souls/</link>
      <pubDate>Fri, 01 Jan 2016 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/binding-of-souls/</guid>
      <description>
        
        &lt;p&gt;Binding of Souls is a third person action game created by 13 students at The Game Assembly over 10 weeks of full-time work.&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/binding-of-souls/videos/binding-of-souls-gameplay.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/binding-of-souls/videos/binding-of-souls-gameplay.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Helped create the level generator&lt;/li&gt;
&lt;li&gt;Helped create the weapon generator&lt;/li&gt;
&lt;li&gt;Created shaders for the character and more&lt;/li&gt;
&lt;/ul&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/binding-of-souls/images/gallery-1/promo_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /binding-of-souls/images/gallery-1/promo_1_hu21e742e6a2db46d26478bc4605c77d29_569733_237x0_resize_q90_box.jpg,
                        /binding-of-souls/images/gallery-1/promo_1_hu21e742e6a2db46d26478bc4605c77d29_569733_474x0_resize_q90_box.jpg 2x,
                        /binding-of-souls/images/gallery-1/promo_1_hu21e742e6a2db46d26478bc4605c77d29_569733_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/binding-of-souls/images/gallery-1/promo_1_hu21e742e6a2db46d26478bc4605c77d29_569733_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/binding-of-souls/images/gallery-1/promo_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /binding-of-souls/images/gallery-1/promo_2_hu9afd2f7f30bcbc195665f768429cdbc5_470671_237x0_resize_q90_box.jpg,
                        /binding-of-souls/images/gallery-1/promo_2_hu9afd2f7f30bcbc195665f768429cdbc5_470671_474x0_resize_q90_box.jpg 2x,
                        /binding-of-souls/images/gallery-1/promo_2_hu9afd2f7f30bcbc195665f768429cdbc5_470671_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/binding-of-souls/images/gallery-1/promo_2_hu9afd2f7f30bcbc195665f768429cdbc5_470671_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;character-shader&#34;&gt;Character shader&lt;/h2&gt;
&lt;p&gt;Since you spend a lot of time looking at the main character, we decided to put some extra work into his shader.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you start out the game the character is covered in snow that slowly melts as time passes.&lt;/li&gt;
&lt;li&gt;The more you roll around on the ground, the dirtier you get.&lt;/li&gt;
&lt;li&gt;As you take damage you get bloodier and your clothes rip.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;level-generator&#34;&gt;Level generator&lt;/h2&gt;
&lt;p&gt;Me and another student created the procedural level generator together. It took in any number of hand-made rooms with one or more possible doorways. The generator would find the doorways and attempt to place another room there so that it does not collide with any of the already placed rooms. It was a pretty simple system, but worked well for our purposes.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/binding-of-souls/images/gallery-2/promo_3.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /binding-of-souls/images/gallery-2/promo_3_hu9573c933933cb1b644526249c9a98b5a_481295_237x0_resize_q90_box.jpg,
                        /binding-of-souls/images/gallery-2/promo_3_hu9573c933933cb1b644526249c9a98b5a_481295_474x0_resize_q90_box.jpg 2x,
                        /binding-of-souls/images/gallery-2/promo_3_hu9573c933933cb1b644526249c9a98b5a_481295_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/binding-of-souls/images/gallery-2/promo_3_hu9573c933933cb1b644526249c9a98b5a_481295_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/binding-of-souls/images/gallery-2/promo_4.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /binding-of-souls/images/gallery-2/promo_4_hub542fd9f8b1c34ed1e51f3088770cd93_438535_237x0_resize_q90_box.jpg,
                        /binding-of-souls/images/gallery-2/promo_4_hub542fd9f8b1c34ed1e51f3088770cd93_438535_474x0_resize_q90_box.jpg 2x,
                        /binding-of-souls/images/gallery-2/promo_4_hub542fd9f8b1c34ed1e51f3088770cd93_438535_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/binding-of-souls/images/gallery-2/promo_4_hub542fd9f8b1c34ed1e51f3088770cd93_438535_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;weapon-generator&#34;&gt;Weapon generator&lt;/h2&gt;
&lt;p&gt;To increase the number of weapons in the game I created a weapon generator. It took in several sword blades, hilts and handles, and put them together randomly. It also generated a name for the weapon as well as stats, based on which parts were used.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Cell</title>
      <link>https://orsvarn.com/cell/</link>
      <pubDate>Thu, 01 Oct 2015 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/cell/</guid>
      <description>
        
        &lt;p&gt;Cell is a first person shooter game created by 13 students at The Game Assembly over 10 weeks of half time work.&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/cell/videos/cell-trailer.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/cell/videos/cell-trailer.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Updated the asset pipeline to accommodate for texture reuse (see below)&lt;/li&gt;
&lt;li&gt;Helped updating the level editor for the project&lt;/li&gt;
&lt;li&gt;Trained an artist in Substance Designer&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;material-editor&#34;&gt;Material editor&lt;/h2&gt;
&lt;p&gt;For this project we decided that using atlases for the textures would be a good idea to reduce vram usage and simplify texture edits. To make this possible I designed a material system and created a material editor. A material is made up of several textures (color, normal, roughness and so on).&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/cell/../shared/images/cell-material-editor.png&#34; alt=&#34;Material editor&#34;  /&gt;
&lt;/p&gt;
&lt;p&gt;Previously, each model had its own set of textures. With this new system several models can use the same material, making it easier to make changes to all objects sharing a material.&lt;/p&gt;
&lt;p&gt;Our model exporting process had been really simple so far. The user would create a model, bring up a dialog that was used to associate a number of textures with the model, and then they would press one button to export the model together with its textures to the correct location in the game folder.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/cell/images/gallery-1/promo_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /cell/images/gallery-1/promo_1_hu7ebe7c46d664d09333cf0b3d471ca135_531813_237x0_resize_q90_box.jpg,
                        /cell/images/gallery-1/promo_1_hu7ebe7c46d664d09333cf0b3d471ca135_531813_474x0_resize_q90_box.jpg 2x,
                        /cell/images/gallery-1/promo_1_hu7ebe7c46d664d09333cf0b3d471ca135_531813_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/cell/images/gallery-1/promo_1_hu7ebe7c46d664d09333cf0b3d471ca135_531813_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/cell/images/gallery-1/promo_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /cell/images/gallery-1/promo_2_hu747c2a98605844d59a76bdf3957795bb_338911_237x0_resize_q90_box.jpg,
                        /cell/images/gallery-1/promo_2_hu747c2a98605844d59a76bdf3957795bb_338911_474x0_resize_q90_box.jpg 2x,
                        /cell/images/gallery-1/promo_2_hu747c2a98605844d59a76bdf3957795bb_338911_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/cell/images/gallery-1/promo_2_hu747c2a98605844d59a76bdf3957795bb_338911_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;p&gt;It is important that we do not make this process slower or more complicated since that might frustrate the artists. With that in mind the material editor was created to make it easier to work with materials. This also allowed us to keep the texture exporting process automated. I wrote the material editor in C++ and Qt.&lt;/p&gt;
&lt;p&gt;In the end the model exporting process was made simpler since the user no longer need to assign textures before exporting, just click a button. The material editor lists all existing materials for easy editing, and it is really fast and easy to create new ones. Exporting a material to the game folders is only one click as well.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/cell/images/gallery-2/promo_3.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /cell/images/gallery-2/promo_3_hud7247d09227a9fe5a365213710e10795_447361_237x0_resize_q90_box.jpg,
                        /cell/images/gallery-2/promo_3_hud7247d09227a9fe5a365213710e10795_447361_474x0_resize_q90_box.jpg 2x,
                        /cell/images/gallery-2/promo_3_hud7247d09227a9fe5a365213710e10795_447361_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/cell/images/gallery-2/promo_3_hud7247d09227a9fe5a365213710e10795_447361_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/cell/images/gallery-2/promo_4.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /cell/images/gallery-2/promo_4_hu2c542f3ad98fe2748acf62d5efbf0d31_396742_237x0_resize_q90_box.jpg,
                        /cell/images/gallery-2/promo_4_hu2c542f3ad98fe2748acf62d5efbf0d31_396742_474x0_resize_q90_box.jpg 2x,
                        /cell/images/gallery-2/promo_4_hu2c542f3ad98fe2748acf62d5efbf0d31_396742_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/cell/images/gallery-2/promo_4_hu2c542f3ad98fe2748acf62d5efbf0d31_396742_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;p&gt;We decided to take this opportunity to pack our textures in the material exporting step. This makes it easier for artists as well as reduces memory usage, game size and the number of texture samples in shaders. This results in that our artists do not have to know where each texture should be packed, as this is fully automated (my &lt;a href=&#34;https://orsvarn.com/channel-shuffle/&#34;&gt;Channel Shuffle&lt;/a&gt; tool is used for the packing). We save what textures were used to create each material, so if we wanted we could change the packing scheme at any point and batch export all materials.&lt;/p&gt;
&lt;p&gt;I had planned to further improve the material editor to give the user more information regarding if a material was missing source files, if it was not exported, if the source textures had been changed since last export and so on. It would also be really cool to create an interface for associating models with materials as that part had to be done manually in an XML file. In the end there was not enough time to add these features.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>PBR shader</title>
      <link>https://orsvarn.com/pbr-shader/</link>
      <pubDate>Sat, 11 Apr 2015 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/pbr-shader/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 12px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;pbr-shader-source.glsl&#34;&gt;View shader source code&lt;/a&gt;
&lt;/div&gt;

&lt;p&gt;I created this shader to get a deeper understanding for how physically based rendering works.&lt;/p&gt;
&lt;p&gt;
    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/pbr-shader/images/gallery-character/balder_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /pbr-shader/images/gallery-character/balder_0_hue641fd64f5296a9a607c8508a8e5015b_131156_237x0_resize_q90_box.jpg,
                        /pbr-shader/images/gallery-character/balder_0_hue641fd64f5296a9a607c8508a8e5015b_131156_474x0_resize_q90_box.jpg 2x,
                        /pbr-shader/images/gallery-character/balder_0_hue641fd64f5296a9a607c8508a8e5015b_131156_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/pbr-shader/images/gallery-character/balder_0_hue641fd64f5296a9a607c8508a8e5015b_131156_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/pbr-shader/images/gallery-character/balder_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /pbr-shader/images/gallery-character/balder_1_hudbb9234ca389048e2ae988475d5e22d8_121843_237x0_resize_q90_box.jpg,
                        /pbr-shader/images/gallery-character/balder_1_hudbb9234ca389048e2ae988475d5e22d8_121843_474x0_resize_q90_box.jpg 2x,
                        /pbr-shader/images/gallery-character/balder_1_hudbb9234ca389048e2ae988475d5e22d8_121843_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/pbr-shader/images/gallery-character/balder_1_hudbb9234ca389048e2ae988475d5e22d8_121843_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


Character model created by &lt;a href=&#34;https://www.linkedin.com/in/jockehaggstrom/&#34;&gt;Joachim Häggström&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id=&#34;features&#34;&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Written in HLSL&lt;/li&gt;
&lt;li&gt;Directional lights utilizing GGX distribution&lt;/li&gt;
&lt;li&gt;Image-based lighting&lt;/li&gt;
&lt;li&gt;Metallic/roughness workflow&lt;/li&gt;
&lt;li&gt;Allows for overlapping and mirrored normal maps&lt;/li&gt;
&lt;/ul&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /pbr-shader/images/gallery-mats/mats_0_hu5f972915b08246abf75f5aa9ed74a454_129243_237x0_resize_q90_box.jpg,
                        /pbr-shader/images/gallery-mats/mats_0_hu5f972915b08246abf75f5aa9ed74a454_129243_474x0_resize_q90_box.jpg 2x,
                        /pbr-shader/images/gallery-mats/mats_0_hu5f972915b08246abf75f5aa9ed74a454_129243_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_0_hu5f972915b08246abf75f5aa9ed74a454_129243_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /pbr-shader/images/gallery-mats/mats_1_hu7f90117867a02ac7a24b177f3b67cbd8_114212_237x0_resize_q90_box.jpg,
                        /pbr-shader/images/gallery-mats/mats_1_hu7f90117867a02ac7a24b177f3b67cbd8_114212_474x0_resize_q90_box.jpg 2x,
                        /pbr-shader/images/gallery-mats/mats_1_hu7f90117867a02ac7a24b177f3b67cbd8_114212_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_1_hu7f90117867a02ac7a24b177f3b67cbd8_114212_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /pbr-shader/images/gallery-mats/mats_2_hu17771a643ab1ae27d8f6e421844f130c_127179_237x0_resize_q90_box.jpg,
                        /pbr-shader/images/gallery-mats/mats_2_hu17771a643ab1ae27d8f6e421844f130c_127179_474x0_resize_q90_box.jpg 2x,
                        /pbr-shader/images/gallery-mats/mats_2_hu17771a643ab1ae27d8f6e421844f130c_127179_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/pbr-shader/images/gallery-mats/mats_2_hu17771a643ab1ae27d8f6e421844f130c_127179_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;problems-encountered&#34;&gt;Problems Encountered&lt;/h2&gt;
&lt;p&gt;I used ShaderTool to pipe all the data into the shader. While this allowed me to create the shader in a short time span, it also came with a few drawbacks, such as no support for HDR cubemaps and the fact that mipmaps are recomputed for DDS textures when they are loaded. This stopped me from using HDR cubemaps and pre-convolved GGX cubemaps that are synced between the authoring program (Knald Lys) and the shader.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Channel Shuffle</title>
      <link>https://orsvarn.com/channel-shuffle/</link>
      <pubDate>Sat, 28 Feb 2015 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/channel-shuffle/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;downloads/channel-shuffle-gui_2015-01-30.zip&#34;&gt;Download ZIP&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;https://github.com/lukors/channel_shuffle&#34;&gt;Source on GitHub&lt;/a&gt;
    &lt;p&gt;Windows | Version 0.10 beta | 2015-01-30&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;The download comes with both the GUI version (16MB) and the command line version (200kB). The command line version can be used entirely on its own.&lt;/p&gt;
&lt;p&gt;
&lt;img src=&#34;https://orsvarn.com/channel-shuffle/images/demo.gif&#34; alt=&#34;Channel Shuffle demo&#34;  /&gt;
&lt;/p&gt;
&lt;h2 id=&#34;introduction&#34;&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Channel Shuffle can be used to create images from specified channels (red, green, blue and alpha) from two images. It is written with the goals of being lightweight, fast, easy to use and portable.&lt;/p&gt;
&lt;p&gt;There are two parts to this application, first there is channel_shuffle, which is a command line application that can be used independently. Then there is channel_shuffle_gui, which is a graphical front end for channel_shuffle created in Qt.&lt;/p&gt;
&lt;p&gt;The application and its source are in the public domain. Find the source on &lt;!-- raw HTML omitted --&gt;GitHub&lt;!-- raw HTML omitted --&gt;. Feel free to do whatever you want with the source code.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s written in C++, it uses the cross platform library &lt;!-- raw HTML omitted --&gt;SOIL&lt;!-- raw HTML omitted --&gt; to load and save images, and the GUI is made in Qt.&lt;/p&gt;
&lt;p&gt;I have only compiled it for Windows, but since it was made with portability in mind it&amp;rsquo;s hopefully easy to get it to compile on other platforms.&lt;/p&gt;
&lt;h2 id=&#34;easy-and-fast-to-use&#34;&gt;Easy and fast to use&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Drag and drop an image to one of the inputs on the left to import it.&lt;/li&gt;
&lt;li&gt;Select your channels and if you want to invert any of them in the output image using the radio buttons in the middle and the buttons at the top.&lt;/li&gt;
&lt;li&gt;Drag and drop the output image to either a folder on your hard drive to save out the final image, or to one of the inputs to keep shuffling.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id=&#34;example-use-cases&#34;&gt;Example use cases&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Flip or invert channels in a normal map without having to open up something huge like Photoshop or Gimp.&lt;/li&gt;
&lt;li&gt;Easily and quickly pack many separate textures into fewer ones to lower the number of texture samples in your game.&lt;/li&gt;
&lt;li&gt;Execute the command line version somewhere in your pipeline to pack those textures automatically.&lt;/li&gt;
&lt;li&gt;Turn four separate grayscale images into one channel each in a single image in just a few seconds.&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Claiming Ymir</title>
      <link>https://orsvarn.com/claiming-ymir/</link>
      <pubDate>Sun, 11 Jan 2015 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/claiming-ymir/</guid>
      <description>
        
        &lt;p&gt;Claiming Ymir is a real time strategy game created by 13 students at The Game Assembly over 10 weeks of half time work.&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/claiming-ymir/videos/claiming-ymir-trailer.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/claiming-ymir/videos/claiming-ymir-trailer.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Created the water shader&lt;/li&gt;
&lt;li&gt;Created the terrain shader&lt;/li&gt;
&lt;li&gt;Other shader work&lt;/li&gt;
&lt;li&gt;Updated the pipeline to fit the project&lt;/li&gt;
&lt;/ul&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/claiming-ymir/images/gallery-1/promo_1.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /claiming-ymir/images/gallery-1/promo_1_hu3e4802d5bdf623b6c9cb188814e34bd2_547434_237x0_resize_q90_box.jpg,
                        /claiming-ymir/images/gallery-1/promo_1_hu3e4802d5bdf623b6c9cb188814e34bd2_547434_474x0_resize_q90_box.jpg 2x,
                        /claiming-ymir/images/gallery-1/promo_1_hu3e4802d5bdf623b6c9cb188814e34bd2_547434_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/claiming-ymir/images/gallery-1/promo_1_hu3e4802d5bdf623b6c9cb188814e34bd2_547434_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/claiming-ymir/images/gallery-1/promo_2.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /claiming-ymir/images/gallery-1/promo_2_hu381ed557c59c1d2b89aaa7b745db4e42_589135_237x0_resize_q90_box.jpg,
                        /claiming-ymir/images/gallery-1/promo_2_hu381ed557c59c1d2b89aaa7b745db4e42_589135_474x0_resize_q90_box.jpg 2x,
                        /claiming-ymir/images/gallery-1/promo_2_hu381ed557c59c1d2b89aaa7b745db4e42_589135_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/claiming-ymir/images/gallery-1/promo_2_hu381ed557c59c1d2b89aaa7b745db4e42_589135_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;terrain-shader&#34;&gt;Terrain shader&lt;/h2&gt;
&lt;p&gt;This shader was created and implemented by me during the project to make transitions between different ground textures look nice.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Support for up to 5 albedo + normal map pairs.&lt;/li&gt;
&lt;li&gt;Uses a splat map where each texture is used based on the R, G, B and A channels, if all those channels are zero, the fifth texture is used.&lt;/li&gt;
&lt;li&gt;The alpha channel in the albedo maps contain height data that is used to make the transitions, enabling grass to stick out of sand etc.&lt;/li&gt;
&lt;li&gt;Persistent blood appears on the ground during battles.&lt;/li&gt;
&lt;/ul&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/claiming-ymir/images/gallery-2/promo_3.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /claiming-ymir/images/gallery-2/promo_3_huf7d35bad4aab1fcdeb9ad09882b9a2e3_588300_237x0_resize_q90_box.jpg,
                        /claiming-ymir/images/gallery-2/promo_3_huf7d35bad4aab1fcdeb9ad09882b9a2e3_588300_474x0_resize_q90_box.jpg 2x,
                        /claiming-ymir/images/gallery-2/promo_3_huf7d35bad4aab1fcdeb9ad09882b9a2e3_588300_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/claiming-ymir/images/gallery-2/promo_3_huf7d35bad4aab1fcdeb9ad09882b9a2e3_588300_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/claiming-ymir/images/gallery-2/promo_4.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /claiming-ymir/images/gallery-2/promo_4_hu387879141d5b7e5e571ae7c4f8ef47ea_577000_237x0_resize_q90_box.jpg,
                        /claiming-ymir/images/gallery-2/promo_4_hu387879141d5b7e5e571ae7c4f8ef47ea_577000_474x0_resize_q90_box.jpg 2x,
                        /claiming-ymir/images/gallery-2/promo_4_hu387879141d5b7e5e571ae7c4f8ef47ea_577000_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/claiming-ymir/images/gallery-2/promo_4_hu387879141d5b7e5e571ae7c4f8ef47ea_577000_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;h2 id=&#34;water-shader&#34;&gt;Water shader&lt;/h2&gt;
&lt;p&gt;We decided to create this water shader to increase visual fidelity while also reducing the workload on artists and level designers, this allowed us to better focus our efforts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Has foam at the edges.&lt;/li&gt;
&lt;li&gt;Changes color based on water depth.&lt;/li&gt;
&lt;li&gt;Vertex animation.&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Galaxia</title>
      <link>https://orsvarn.com/galaxia/</link>
      <pubDate>Sat, 11 Oct 2014 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/galaxia/</guid>
      <description>
        
        &lt;p&gt;Galaxia is a space shooter created by 13 students at The Game Assembly over 10 weeks of half time work.&lt;/p&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Designed and implemented a pipeline for the technical artists (more info below)&lt;/li&gt;
&lt;li&gt;Helped set up an art and level design pipeline from scratch&lt;/li&gt;
&lt;li&gt;Minor shader work&lt;/li&gt;
&lt;/ul&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/galaxia/videos/galaxia-trailer.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/galaxia/videos/galaxia-trailer.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;h2 id=&#34;technical-artist-pipeline-for-maya-scripts&#34;&gt;Technical artist pipeline for Maya scripts&lt;/h2&gt;
&lt;p&gt;During this first of our projects, I designed and created a framework for us technical artists to distribute Maya scripts to our artists and level designers. It allowed us to ensure that everyone was using the latest version of each tool, and was used throughout all of the rest of our projects.&lt;/p&gt;
&lt;p&gt;The pipeline came with the following benifits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Simple to set up for users, just set up a single environent variable and restart Maya.&lt;/li&gt;
&lt;li&gt;Script updates take effect immediately, without having to restart Maya.&lt;/li&gt;
&lt;li&gt;Safe and easy for script creators to release their scripts to users, a script was created to synchronize a release directory on our SVN with the location on the network where the users ran their scripts from.&lt;/li&gt;
&lt;li&gt;New scripts could be very easily added to users shelves by updating a list in a script with an icon, a script to run and a tool tip.&lt;/li&gt;
&lt;li&gt;Crash logging was automatically handled for all scripts. So if a user experienced a crash, we didn&amp;rsquo;t always need to hog their work station to figure out what went wrong, but could instead look at their crash report from our work station. We could also use this data to help identify and fix bugs that the user might not even have noticed.&lt;/li&gt;
&lt;li&gt;We logged who used what script when. This data was useful to spot scripts that weren&amp;rsquo;t used for instance. This data was then used to improve tools further and help focus our efforts.&lt;/li&gt;
&lt;/ul&gt;

      </description>
    </item>
    
    <item>
      <title>Kravall</title>
      <link>https://orsvarn.com/kravall/</link>
      <pubDate>Fri, 11 Apr 2014 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/kravall/</guid>
      <description>
        
        &lt;p&gt;Kravall is a real time strategy game created by 11 students at Blekinge Institute of Technology over five months.&lt;/p&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Created the level editor (more info below)&lt;/li&gt;
&lt;li&gt;Lead technical artist&lt;/li&gt;
&lt;li&gt;Various art tasks such as modeling and texturing&lt;/li&gt;
&lt;li&gt;Level design&lt;/li&gt;
&lt;/ul&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/kravall/videos/kravall-gameplay.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/kravall/videos/kravall-gameplay.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;h2 id=&#34;level-editor&#34;&gt;Level editor&lt;/h2&gt;
&lt;p&gt;The level editor was written as a Python plugin for Blender and had the following features.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An asset handling system, allowing for quickly reloading the asset library&lt;/li&gt;
&lt;li&gt;Manual nav mesh creation using a mesh&lt;/li&gt;
&lt;li&gt;Editing the world lighting&lt;/li&gt;
&lt;li&gt;Assigning functions to objects&lt;/li&gt;
&lt;li&gt;Trigger creation&lt;/li&gt;
&lt;li&gt;Quick iteration times due to fast exports, hotkey for exporting and camera view from the editor being retained in the game&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also wrote the level exporter and the nav mesh exporter for this level editor.&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/kravall/videos/kravall-editor.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/kravall/videos/kravall-editor.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;

      </description>
    </item>
    
    <item>
      <title>Spotlights for global illumination</title>
      <link>https://orsvarn.com/bachelor-thesis/</link>
      <pubDate>Thu, 30 Jan 2014 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/bachelor-thesis/</guid>
      <description>
        
        &lt;div style=&#34;margin-top: 24px;&#34;&gt;
    &lt;a class=&#34;button primary&#34; href=&#34;downloads/lukas-orsvarn_automatic-spotlight-distribution-for-indirect-illumination.pdf&#34;&gt;Download PDF&lt;/a&gt; &lt;a class=&#34;button secondary&#34; href=&#34;https://urn.kb.se/resolve?urn=urn:nbn:se:bth-3729&#34;&gt;More Information&lt;/a&gt;
    &lt;p&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;This is the bachelor thesis I did as part of my technical artist education at Blekinge Institute of Technology. I created a simple OpenGL rendering engine for this project. In it I explore a technique using a reflective shadow map to place and configure spotlights in an environment to approximate global illumination.&lt;/p&gt;

    &lt;div class=&#34;gallery&#34;&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_all_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/ii_all_0_hu59203254e12e0296c5e6535ce5b44f65_88739_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/ii_all_0_hu59203254e12e0296c5e6535ce5b44f65_88739_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/ii_all_0_hu59203254e12e0296c5e6535ce5b44f65_88739_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_all_0_hu59203254e12e0296c5e6535ce5b44f65_88739_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_all_gray_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/ii_all_gray_0_hu5b1da9fe9d5bf506c013cc0c71ed83f9_79843_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/ii_all_gray_0_hu5b1da9fe9d5bf506c013cc0c71ed83f9_79843_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/ii_all_gray_0_hu5b1da9fe9d5bf506c013cc0c71ed83f9_79843_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_all_gray_0_hu5b1da9fe9d5bf506c013cc0c71ed83f9_79843_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_sim_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/ii_sim_0_hu546fb5afdf580132a81665fefaa47fcf_88994_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/ii_sim_0_hu546fb5afdf580132a81665fefaa47fcf_88994_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/ii_sim_0_hu546fb5afdf580132a81665fefaa47fcf_88994_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_sim_0_hu546fb5afdf580132a81665fefaa47fcf_88994_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_sim_gray_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/ii_sim_gray_0_hua62d38fbd86d162dd0186a441ad0fcf2_80296_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/ii_sim_gray_0_hua62d38fbd86d162dd0186a441ad0fcf2_80296_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/ii_sim_gray_0_hua62d38fbd86d162dd0186a441ad0fcf2_80296_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/ii_sim_gray_0_hua62d38fbd86d162dd0186a441ad0fcf2_80296_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/spt_all_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/spt_all_0_hu9cad5772941dfbb5154223cb24da45b8_243558_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/spt_all_0_hu9cad5772941dfbb5154223cb24da45b8_243558_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/spt_all_0_hu9cad5772941dfbb5154223cb24da45b8_243558_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/spt_all_0_hu9cad5772941dfbb5154223cb24da45b8_243558_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
            
            
            
            &lt;a href=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/spt_sim_0.jpg&#34;&gt;
                &lt;picture&gt;
                    &lt;source srcset=&#34;
                        /bachelor-thesis/images/gallery/spt_sim_0_hu205afc498b2be5b1782514432edb892d_91711_237x0_resize_q90_box.jpg,
                        /bachelor-thesis/images/gallery/spt_sim_0_hu205afc498b2be5b1782514432edb892d_91711_474x0_resize_q90_box.jpg 2x,
                        /bachelor-thesis/images/gallery/spt_sim_0_hu205afc498b2be5b1782514432edb892d_91711_948x0_resize_q90_box.jpg 4x,
                    &#34;&gt;
                    &lt;img src=&#34;https://orsvarn.com/bachelor-thesis/images/gallery/spt_sim_0_hu205afc498b2be5b1782514432edb892d_91711_237x0_resize_q90_box.jpg&#34; alt=&#34;&#34; /&gt;
                &lt;/picture&gt;
            &lt;/a&gt;
        
    &lt;/div&gt;


&lt;p&gt;The spotlight distribution technique is implemented in a delimited real time graphics engine, and the results are compared to a naive spotlight distribution method. The technique could be used in its current state for applications where the view can be controlled by the developer such as in 3D side scrolling games or as a tool to generate editable indirect illumination. Further research needs to be conducted to make it more broadly viable.&lt;/p&gt;

      </description>
    </item>
    
    <item>
      <title>Semest</title>
      <link>https://orsvarn.com/semest/</link>
      <pubDate>Mon, 30 Jan 2012 16:38:03 +0200</pubDate>
      
      <guid>https://orsvarn.com/semest/</guid>
      <description>
        
        &lt;p&gt;I wanted more game development in my education. So me and a friend worked on this project full time for two months over the summer. It was never finished, but we both learned a lot and had a lot of fun!&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/semest/videos/semest-demo.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/semest/videos/semest-demo.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;
&lt;p&gt;It was supposed to be an action adventure game inspired by games like &lt;em&gt;The Legend of Zelda: Ocarina of Time&lt;/em&gt; and &lt;em&gt;Psychonauts&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;My friend did the programming and I did the art.&lt;/p&gt;
&lt;h2 id=&#34;my-contributions&#34;&gt;My contributions&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Level design&lt;/li&gt;
&lt;li&gt;Modeling&lt;/li&gt;
&lt;li&gt;Texturing&lt;/li&gt;
&lt;li&gt;Rigging&lt;/li&gt;
&lt;li&gt;Animation&lt;/li&gt;
&lt;li&gt;Concept art&lt;/li&gt;
&lt;li&gt;Game design&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&#34;rigging-and-animation&#34;&gt;Rigging and animation&lt;/h2&gt;
&lt;p&gt;I learned rigging and animation for this project, and this is the result. It contains basic conveniences such as ik/fk switches, foot roll controls and controls for the eyes.&lt;/p&gt;













&lt;video controls width=&#34;100%&#34; poster=&#34;/semest/videos/semest-rig.jpg&#34;&gt;
    &lt;source src=&#34;https://orsvarn.com/semest/videos/semest-rig.mp4&#34;&gt;
    Unable to play video
&lt;/video&gt;

      </description>
    </item>
    
  </channel>
</rss>

