<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Transdimensional Phoenix</title><link>https://blog.opengbh.net/</link><description>Recent content on Transdimensional Phoenix</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Thu, 29 Jun 2023 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.opengbh.net/index.xml" rel="self" type="application/rss+xml"/><item><title>Reading a cubemap pixel by direction in Unreal Engine 5</title><link>https://blog.opengbh.net/posts/0010-reading-cubemap-by-direction/</link><pubDate>Thu, 29 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0010-reading-cubemap-by-direction/</guid><description>In order to implement an early version of pre-rendered 2D skybox I needed to compare the skyboxes with sun enabled and disabled in order to obtain the required tinting of the sun for 2D skybox (so the sun doesn&amp;rsquo;t render on top of it, shining right through dense clouds).
This system is no longer used, now a texture mask is used instead to remove stars, sun and moon and other background objects from being rendered where the clouds are.</description><content>&lt;p>In order to implement an early version of &lt;a href="https://blog.opengbh.net/posts/0003-pre-rendered-2d-skybox/">pre-rendered 2D skybox&lt;/a> I needed to compare the skyboxes with sun enabled and disabled in order to obtain the required tinting of the sun for 2D skybox (so the sun doesn&amp;rsquo;t render on top of it, shining right through dense clouds).&lt;/p>
&lt;p>This system is no longer used, now a texture mask is used instead to remove stars, sun and moon and other background objects from being rendered where the clouds are.&lt;/p>
&lt;p>Here is the function for sampling the cube map:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>FLinearColor USynesthesiaBlueprintFunctions&lt;span style="color:#f92672">::&lt;/span>SampleTextureCubeByDirection(UTextureRenderTargetCube&lt;span style="color:#f92672">*&lt;/span> RenderTarget, &lt;span style="color:#66d9ef">const&lt;/span> FVector&lt;span style="color:#f92672">&amp;amp;&lt;/span> Direction)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>RenderTarget) &lt;span style="color:#66d9ef">return&lt;/span> FLinearColor&lt;span style="color:#f92672">::&lt;/span>Black;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Get the correct resource
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FTextureRenderTargetResource&lt;span style="color:#f92672">*&lt;/span> RenderTargetResource &lt;span style="color:#f92672">=&lt;/span> RenderTarget&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GameThread_GetRenderTargetResource();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>RenderTargetResource) &lt;span style="color:#66d9ef">return&lt;/span> FLinearColor&lt;span style="color:#f92672">::&lt;/span>Black;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FTextureRenderTargetCubeResource&lt;span style="color:#f92672">*&lt;/span> CubeResource &lt;span style="color:#f92672">=&lt;/span> RenderTargetResource&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetTextureRenderTargetCubeResource();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>CubeResource) &lt;span style="color:#66d9ef">return&lt;/span> FLinearColor&lt;span style="color:#f92672">::&lt;/span>Black;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Normalize the direction vector
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> FVector NormalizedDirection &lt;span style="color:#f92672">=&lt;/span> Direction.GetSafeNormal();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Get the correct cube face for the normalized direction
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> ECubeFace CubeFace &lt;span style="color:#f92672">=&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegX;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> &lt;span style="color:#66d9ef">double&lt;/span> MaxAbsValue &lt;span style="color:#f92672">=&lt;/span> FMath&lt;span style="color:#f92672">::&lt;/span>Max3(FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.X), FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.Y), FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.Z));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.X) &lt;span style="color:#f92672">==&lt;/span> MaxAbsValue) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFace &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.X &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">?&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosX : ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegX;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.Y) &lt;span style="color:#f92672">==&lt;/span> MaxAbsValue) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFace &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.Y &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">?&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosY : ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegY;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (FMath&lt;span style="color:#f92672">::&lt;/span>Abs(NormalizedDirection.Z) &lt;span style="color:#f92672">==&lt;/span> MaxAbsValue) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFace &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.Z &lt;span style="color:#f92672">&amp;gt;&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span> &lt;span style="color:#f92672">?&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosZ : ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegZ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Calculate coordinates of the point on the selected cube maps cube face
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">float&lt;/span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">float&lt;/span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0.0f&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> FVector FaceDirection &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.GetAbs();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosX) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Z &lt;span style="color:#f92672">/&lt;/span> FaceDirection.X;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Y &lt;span style="color:#f92672">/&lt;/span> FaceDirection.X;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegX) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.Z &lt;span style="color:#f92672">/&lt;/span> FaceDirection.X;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Y &lt;span style="color:#f92672">/&lt;/span> FaceDirection.X;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosY) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.X &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Y;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.Z &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Y;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegY) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.X &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Y;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Z &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Y;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_PosZ) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> NormalizedDirection.X &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Z;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Y &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Z;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (CubeFace &lt;span style="color:#f92672">==&lt;/span> ECubeFace&lt;span style="color:#f92672">::&lt;/span>CubeFace_NegZ) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceU &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.X &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Z;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> CubeFaceV &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">-&lt;/span>NormalizedDirection.Y &lt;span style="color:#f92672">/&lt;/span> FaceDirection.Z;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Read correct pixel from the resource
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> TArray&lt;span style="color:#f92672">&amp;lt;&lt;/span>FFloat16Color&lt;span style="color:#f92672">&amp;gt;&lt;/span> ImageData;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>CubeResource&lt;span style="color:#f92672">-&amp;gt;&lt;/span>ReadPixels(ImageData, FReadSurfaceDataFlags(ERangeCompressionMode&lt;span style="color:#f92672">::&lt;/span>RCM_UNorm, CubeFace))) &lt;span style="color:#66d9ef">return&lt;/span> FLinearColor&lt;span style="color:#f92672">::&lt;/span>Black;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Calculate pixel coordinates of the point on selected cubemap face
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">const&lt;/span> int32 SizeX &lt;span style="color:#f92672">=&lt;/span> CubeResource&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetSizeX();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> int32 SizeY &lt;span style="color:#f92672">=&lt;/span> CubeResource&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetSizeY();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> int32 U &lt;span style="color:#f92672">=&lt;/span> FMath&lt;span style="color:#f92672">::&lt;/span>RoundToInt((CubeFaceU &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0.5f&lt;/span> &lt;span style="color:#f92672">*&lt;/span> (SizeX &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> int32 V &lt;span style="color:#f92672">=&lt;/span> FMath&lt;span style="color:#f92672">::&lt;/span>RoundToInt((CubeFaceV &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1.0f&lt;/span>) &lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#ae81ff">0.5f&lt;/span> &lt;span style="color:#f92672">*&lt;/span> (SizeY &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> int32 Offset &lt;span style="color:#f92672">=&lt;/span> V &lt;span style="color:#f92672">*&lt;/span> SizeX &lt;span style="color:#f92672">+&lt;/span> U;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Sample the cubemap data
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (ImageData.IsValidIndex(Offset)) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> ImageData[Offset].GetFloats();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> FLinearColor&lt;span style="color:#f92672">::&lt;/span>Black;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>And here is the function for saving a cube map to disk, just in case it might be useful in a similar context:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-cpp" data-lang="cpp">&lt;span style="display:flex;">&lt;span>UTextureCube&lt;span style="color:#f92672">*&lt;/span> USynesthesiaBlueprintFunctions&lt;span style="color:#f92672">::&lt;/span>RenderTargetCreateStaticTextureCubeEditorOnly(UTextureRenderTargetCube&lt;span style="color:#f92672">*&lt;/span> RenderTarget, FString InName, TextureCompressionSettings CompressionSettings, TextureMipGenSettings MipSettings)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#if WITH_EDITOR
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">// Save the render target image as an asset
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>RenderTarget) { &lt;span style="color:#75715e">// Invalid RT
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FMessageLog(&lt;span style="color:#e6db74">&amp;#34;Blueprint&amp;#34;&lt;/span>).Warning(LOCTEXT(&lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCube_InvalidRenderTarget&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCubeEditorOnly: RenderTarget must be non-null.&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">nullptr&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> &lt;span style="color:#a6e22e">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>RenderTarget&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetResource()) { &lt;span style="color:#75715e">// Invalid RT resource
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FMessageLog(&lt;span style="color:#e6db74">&amp;#34;Blueprint&amp;#34;&lt;/span>).Warning(LOCTEXT(&lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCube_ReleasedRenderTarget&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCubeEditorOnly: RenderTarget has been released.&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">nullptr&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> { &lt;span style="color:#75715e">// Valid inputs, generate static texture
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FString Name;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FString PackageName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IAssetTools&lt;span style="color:#f92672">&amp;amp;&lt;/span> AssetTools &lt;span style="color:#f92672">=&lt;/span> FModuleManager&lt;span style="color:#f92672">::&lt;/span>Get().LoadModuleChecked&lt;span style="color:#f92672">&amp;lt;&lt;/span>FAssetToolsModule&lt;span style="color:#f92672">&amp;gt;&lt;/span>(&lt;span style="color:#e6db74">&amp;#34;AssetTools&amp;#34;&lt;/span>).Get();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Use asset name only if directories are specified, otherwise full path
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>InName.Contains(TEXT(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>))) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> FString AssetName &lt;span style="color:#f92672">=&lt;/span> RenderTarget&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetOutermost()&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetName();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> FString SanitizedBasePackageName &lt;span style="color:#f92672">=&lt;/span> UPackageTools&lt;span style="color:#f92672">::&lt;/span>SanitizePackageName(AssetName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> FString PackagePath &lt;span style="color:#f92672">=&lt;/span> FPackageName&lt;span style="color:#f92672">::&lt;/span>GetLongPackagePath(SanitizedBasePackageName) &lt;span style="color:#f92672">+&lt;/span> TEXT(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//AssetTools.CreateUniqueAssetName(PackagePath, InName, PackageName, Name);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> PackageName &lt;span style="color:#f92672">=&lt;/span> PackagePath &lt;span style="color:#f92672">+&lt;/span> InName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InName.RemoveFromStart(TEXT(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InName.RemoveFromStart(TEXT(&lt;span style="color:#e6db74">&amp;#34;Content/&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> InName.StartsWith(TEXT(&lt;span style="color:#e6db74">&amp;#34;Game/&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">==&lt;/span> true &lt;span style="color:#f92672">?&lt;/span> InName.InsertAt(&lt;span style="color:#ae81ff">0&lt;/span>, TEXT(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>)) &lt;span style="color:#f92672">:&lt;/span> InName.InsertAt(&lt;span style="color:#ae81ff">0&lt;/span>, TEXT(&lt;span style="color:#e6db74">&amp;#34;/Game/&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">//AssetTools.CreateUniqueAssetName(InName, TEXT(&amp;#34;&amp;#34;), PackageName, Name);
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> PackageName &lt;span style="color:#f92672">=&lt;/span> InName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">const&lt;/span> int32 LastSlashIndex &lt;span style="color:#f92672">=&lt;/span> InName.Find(TEXT(&lt;span style="color:#e6db74">&amp;#34;/&amp;#34;&lt;/span>), ESearchCase&lt;span style="color:#f92672">::&lt;/span>CaseSensitive, ESearchDir&lt;span style="color:#f92672">::&lt;/span>FromEnd);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (LastSlashIndex &lt;span style="color:#f92672">!=&lt;/span> INDEX_NONE &lt;span style="color:#f92672">&amp;amp;&amp;amp;&lt;/span> LastSlashIndex &lt;span style="color:#f92672">&amp;lt;&lt;/span> InName.Len() &lt;span style="color:#f92672">-&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name &lt;span style="color:#f92672">=&lt;/span> InName.RightChop(LastSlashIndex &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Name &lt;span style="color:#f92672">=&lt;/span> InName;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Create the package
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> UPackage&lt;span style="color:#f92672">*&lt;/span> Package &lt;span style="color:#f92672">=&lt;/span> CreatePackage(&lt;span style="color:#f92672">*&lt;/span>PackageName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (&lt;span style="color:#f92672">!&lt;/span>Package) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> UE_LOG(LogSynth, Error, TEXT(&lt;span style="color:#e6db74">&amp;#34;Failed to create package %s&amp;#34;&lt;/span>), &lt;span style="color:#f92672">*&lt;/span>PackageName);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">nullptr&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Package&lt;span style="color:#f92672">-&amp;gt;&lt;/span>FullyLoad();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Create or overwrite the texture
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> UTextureCube&lt;span style="color:#f92672">*&lt;/span> NewTexture &lt;span style="color:#f92672">=&lt;/span> Cast&lt;span style="color:#f92672">&amp;lt;&lt;/span>UTextureCube&lt;span style="color:#f92672">&amp;gt;&lt;/span>(RenderTarget&lt;span style="color:#f92672">-&amp;gt;&lt;/span>ConstructTextureCube(Package, Name, RenderTarget&lt;span style="color:#f92672">-&amp;gt;&lt;/span>GetMaskedFlags() &lt;span style="color:#f92672">|&lt;/span> RF_Public &lt;span style="color:#f92672">|&lt;/span> RF_Standalone));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// If the texture is valid, set its parameters and return it
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (NewTexture &lt;span style="color:#f92672">!=&lt;/span> &lt;span style="color:#66d9ef">nullptr&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Package needs saving
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">//NewTexture-&amp;gt;MarkPackageDirty();
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> Package&lt;span style="color:#f92672">-&amp;gt;&lt;/span>SetDirtyFlag(true);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Package&lt;span style="color:#f92672">-&amp;gt;&lt;/span>PackageMarkedDirtyEvent.Broadcast(Package, true);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Notify the asset registry
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FAssetRegistryModule&lt;span style="color:#f92672">::&lt;/span>AssetCreated(NewTexture);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// Update Compression and Mip settings
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> NewTexture&lt;span style="color:#f92672">-&amp;gt;&lt;/span>CompressionSettings &lt;span style="color:#f92672">=&lt;/span> CompressionSettings;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NewTexture&lt;span style="color:#f92672">-&amp;gt;&lt;/span>MipGenSettings &lt;span style="color:#f92672">=&lt;/span> MipSettings;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> NewTexture&lt;span style="color:#f92672">-&amp;gt;&lt;/span>PostEditChange();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> NewTexture;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> FMessageLog(&lt;span style="color:#e6db74">&amp;#34;Blueprint&amp;#34;&lt;/span>).Warning(LOCTEXT(&lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCube_FailedToCreateTexture&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCubeEditorOnly: Failed to create a new texture.&amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#else
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> FMessageLog(&lt;span style="color:#e6db74">&amp;#34;Blueprint&amp;#34;&lt;/span>).Error(LOCTEXT(&lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCube_RuntimeFailedToCreateTexture&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;RenderTargetCreateStaticTextureCubeEditorOnly: Can&amp;#39;t create TextureCube at run time. &amp;#34;&lt;/span>));
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">#endif
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> &lt;span style="color:#66d9ef">nullptr&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title>Synesthesia: Re-entry effects improvements</title><link>https://blog.opengbh.net/posts/0009-sky-improvements/</link><pubDate>Wed, 28 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0009-sky-improvements/</guid><description>I have improved the sprite for the re-entry effect. It still needs some polishing, but now the re-entry effect looks like this up-close.
An extra bonus picture: Plus a video of the sky, sun, moon and clouds all blending together with the mask:
Sorry, your browser doesn't support embedded videos. You can download it and watch it with an external player.</description><content>&lt;p>I have improved the sprite for the re-entry effect. It still needs some polishing, but now the re-entry effect looks like this up-close.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230628142207.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230628142215.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230628142220.png"/>&lt;/p>
&lt;p>An extra bonus picture:
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230628142235.png"/>&lt;/p>
&lt;p>Plus a video of the sky, sun, moon and clouds all blending together with the mask:&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00038-h7rk17.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00038-h7rk17.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video></content></item><item><title>Synesthesia: Starfield improvements</title><link>https://blog.opengbh.net/posts/0008-starfield-improvements/</link><pubDate>Tue, 27 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0008-starfield-improvements/</guid><description>Tachy, a friend of mine, helped me considerably by calculating a star catalog for the year 2994 (when the events of the game take place). Some of the stars experience a considerable drift over the 1000 years - it might not be a very important detail to most players, however it is a nice little detail to have.
Here is a simple comparison between the two starfields:
The newer year 2994 catalog also contains way more stars - 119,614 total.</description><content>&lt;p>&lt;strong>Tachy&lt;/strong>, a friend of mine, helped me considerably by calculating a star catalog for the year 2994 (when the events of the game take place). Some of the stars experience a considerable drift over the 1000 years - it might not be a very important detail to most players, however it is a nice little detail to have.&lt;/p>
&lt;p>Here is a simple comparison between the two starfields:&lt;/p>
&lt;div class="compare-container">
&lt;img src="https://blog.opengbh.net/images/image_2023-06-26_15-23-14.png" class="compare-image1" />
&lt;img src="https://blog.opengbh.net/images/image_2023-06-26_15-23-05.png" class="compare-image2" />
&lt;/div>
&lt;p>The newer year 2994 catalog also contains way more stars - 119,614 total. The older catalog has been truncated to only magnitudes brighter than 9.99, so it only listed 28,593 stars.&lt;/p>
&lt;p>The synths sensors are sufficiently sensitive to pick out faint stars, so in the end I plan to include all ~120,000 stars (though possibly less based on performance settings, however it does not seem to be considerable for the particle system).&lt;/p>
&lt;hr>
&lt;p>Below are the two starfields in higher resolution. These images can be opened at a higher resolution, if you want to take a closer look.&lt;/p>
&lt;hr>
&lt;h4 id="epoch-2000">Epoch 2000&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/image_2023-06-26_15-23-05.png"/>&lt;/p>
&lt;hr>
&lt;h4 id="epoch-2994">Epoch 2994:&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/image_2023-06-26_15-23-14.png"/>&lt;/p>
&lt;hr>
&lt;p>Here is the video of the star field movement over time and how it blends together with the clouds using the mask shown in an &lt;a href="https://blog.opengbh.net/posts/0004-star-field-rendering/">earlier post&lt;/a>.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00039-1O8iKr.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00039-1O8iKr.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video></content></item><item><title>Synesthesia: Debris re-entry effects</title><link>https://blog.opengbh.net/posts/0007-re-entry-effects/</link><pubDate>Mon, 26 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0007-re-entry-effects/</guid><description>While working on the sky system, I had a thought about adding random space debris re-entering atmosphere. It would be a really telling and interesting piece of narrative that illustrates the state of the world - in the setting of Synesthesia, in year 2994, most of the space operations have been abandoned and overall there is no organized effort to monitor space debris anymore.
There are still millions of objects in orbit - many of these are private satellites, spacecraft and other assorted hardware.</description><content>&lt;p>While working on the sky system, I had a thought about adding random space debris re-entering atmosphere. It would be a really telling and interesting piece of narrative that illustrates the state of the world - in the setting of Synesthesia, in year 2994, most of the space operations have been abandoned and overall there is no organized effort to monitor space debris anymore.&lt;/p>
&lt;p>There are still millions of objects in orbit - many of these are private satellites, spacecraft and other assorted hardware. There are a few large objects (space stations and old spacecraft) which will eventually decay and re-enter atmosphere. All sorts of debris routinely enters the Earth atmosphere.&lt;/p>
&lt;p>A large proportion of this debris is the result of the progressing Kessler syndrome, making any space travel dangerous and forcing people to abandon many of the space-based operations.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00029-64PIT3.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00029-64PIT3.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The player would be able to see these happen randomly. Many of them may be too small to notice unless they get lucky, however there may be a really big event once in a while.&lt;/p>
&lt;hr>
&lt;p>The re-entry effect is implemented as a particle system combined with a fairly simple thermal simulation. The debris objects are spawned at the height of about 120-140 km and are given a random, statistically distributed direction &amp;amp; entry angle.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00019-7tdXpb.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00019-7tdXpb.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The objects continue to fly free and a simple break-up model is used to calculate when the object finally burns up. It is a fairly simplistic model, it is possible that I might improve it in the future.&lt;/p>
&lt;p>One definite improvement I want to make is using better sprites - it would make the re-entry trails much more distinct, vivid, and less blurry. It would also emphasize multiple objects traveling in the re-entry path as the main object continues to shed debris.&lt;/p>
&lt;hr>
&lt;p>The video below shows a better look at the hot entry and break up of the object. The re-entry event, if detected by players information systems, will also be recorded in the minor event log.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00030-Cj53aW.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00030-Cj53aW.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The re-entry events are defined by the following parameters:&lt;/p>
&lt;ol>
&lt;li>Object mass - this defines how much debris is generated and how long can the object withstand high temperature.&lt;/li>
&lt;li>Burn rate - this defines how fast temperature burns up the mass of the object or causes debris shedding events. The effective burn rate is proportional to this value and the current temperature.&lt;/li>
&lt;li>Entry vector - the simulation has a simple exponential model of atmosphere, so shallow and steep re-entries look distinctly different.&lt;/li>
&lt;/ol>
&lt;p>A combination of these parameters can be used to set up a re-entry that looks most artistically appealing. The sky system creates random procedural re-entry events. For special scripted events, the same particle system can be created manually and multiple systems can be combined to create an appearance of a very large object breaking up.&lt;/p>
&lt;p>Here is a close-up picture of the re-entry event. For prototyping, I&amp;rsquo;ve been using a simple spherical sprite, which makes the entire trail look quite blurry. The quality should improve considerably when the sprite will be more drop-shaped, with a large glowing core and a small trail following it.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230626162834.png" alt="Re-entry trail. Something is burning up out there"/>&lt;/p>
&lt;hr>
&lt;p>In addition to immersion, I want to include the re-entry effects in at least two elements of the narrative itself:&lt;/p>
&lt;ol>
&lt;li>A side quest, during which the player can force a spacecraft in orbit to burn its engines for re-entry. While not having effect on the main quest, it would be narratively connected to the plot events.
&lt;ul>
&lt;li>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag3"/>
&lt;label class="spoiler" for="spoilerTag3">The destruction of this spacecraft would be done to hamper the mercenaries and Eden, the primary antagonists of the game.&lt;/label>&lt;/li>
&lt;li>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag4"/>
&lt;label class="spoiler" for="spoilerTag4">Most likely, this spacecraft will be an orbital weapon station, somewhat resembling SOL from Akira&lt;/label>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>A random event, during which an object re-enters over the EDZ-01 and visibly reaches surface.
&lt;ul>
&lt;li>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag5"/>
&lt;label class="spoiler" for="spoilerTag5">After re-entry, a glowing object falls somewhere in an obvious area, allowing player to go investigate and find this object. &lt;/label>&lt;/li>
&lt;li>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag6"/>
&lt;label class="spoiler" for="spoilerTag6">Possibly, the object is a new friend or an enemy, as well as some unique hardware.&lt;/label>&lt;/li>
&lt;li>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag7"/>
&lt;label class="spoiler" for="spoilerTag7">The side quest rewards player for the exploration they had to do in order to find the crashed object.&lt;/label>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol></content></item><item><title>Synesthesia Gameplay Systems and Elements</title><link>https://blog.opengbh.net/0001-synesthesia-narrative-elements/</link><pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/0001-synesthesia-narrative-elements/</guid><description>(this entry is a work in progress, last updated: 2023-06-26).
These are the different elements considered or planned for Synethesia.
Narrative Gameplay Elements These are the gameplay elements that enhance the narrative, provide extra flavor or information to player and may be used as part of a quest.
Computer terminals Similar to dialogs, but longer text replies and shorter user prompts. Computer terminals reveal extra lore information and allow triggering special events in the game world.</description><content>&lt;p>(&lt;em>this entry is a work in progress, last updated: &lt;strong>2023-06-26&lt;/strong>&lt;/em>).&lt;/p>
&lt;p>These are the different elements considered or planned for Synethesia.&lt;/p>
&lt;h1 id="narrative-gameplay-elements">Narrative Gameplay Elements&lt;/h1>
&lt;p>These are the gameplay elements that enhance the narrative, provide extra flavor or information to player and may be used as part of a quest.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Computer terminals&lt;/strong>
&lt;ul>
&lt;li>Similar to dialogs, but longer text replies and shorter user prompts.&lt;/li>
&lt;li>Computer terminals reveal extra lore information and allow triggering special events in the game world.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Dialogs with NPC&amp;rsquo;s&lt;/strong>
&lt;ul>
&lt;li>The main way player interacts with the characters in the game world.&lt;/li>
&lt;li>Dialogs allow player to interact with the given character similarly to RPG games (I am using Fallout: New Vegas as a reference).&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Background NPC dialogs&lt;/strong>
&lt;ul>
&lt;li>Single sentence reactions NPC&amp;rsquo;s may say when player presses &lt;code>USE&lt;/code> on them.&lt;/li>
&lt;li>These simply provide background flavor and indicate NPC&amp;rsquo;s which do not have a dialog tree.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Infoscan 1: Scanning landmarks or target zones&lt;/strong>
&lt;ul>
&lt;li>Information or clues can be gained by manually scanning landmarks or target zones.&lt;/li>
&lt;li>Player enters the scanning mode by holding the &lt;code>SCAN&lt;/code> key and points cursor at a landmark or a zone of interest. This reveals a short message that can be a helpful clue.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Event Log. Infoscan 2: Automatic landmarks or target zones&lt;/strong>
&lt;ul>
&lt;li>Some landmarks get automatically scanned by simply approaching them. The short text is then added to the rolling event log.&lt;/li>
&lt;li>The event log also includes redundant text from other narrative elements, as well as general background noise for flavor.&lt;/li>
&lt;li>Some landmarks and clues are pretty obvious and are visible with the naked eye, however the infoscan system can still provide just a little bit of lore.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Radar&lt;/strong>
&lt;ul>
&lt;li>Reveals hidden landmarks and passageways, reveals information unseen normally. Radar allows player to &amp;lsquo;see through walls&amp;rsquo;.&lt;/li>
&lt;li>Radar also provides a small 3D minimap that player can toggle.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Radio frequency scanning&lt;/strong>
&lt;ul>
&lt;li>Produces audible noises, words, music, images and other narrative elements, plus flavor text and quest triggers.&lt;/li>
&lt;li>Most radio signals are produced by various landmarks around the city.&lt;/li>
&lt;li>Satellites that pass over player also produce radio signals, however most of them simply broadcast a dead carrier or repetitive telemetry.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Scavenger background chatter&lt;/strong>
&lt;ul>
&lt;li>Text-based messages sent by scavengers to each other over their public network.&lt;/li>
&lt;li>Player can turn the optional messages off, however quest-specific text messages will still get delivered to player.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Enemy background chatter&lt;/strong>
&lt;ul>
&lt;li>Communication between enemy AI units, coordination, reaction to player.&lt;/li>
&lt;li>These work similarly to scavenger chatter, however they are mostly produced by the dynamic AI system rather than world events.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Primary narrative&lt;/strong>
&lt;ul>
&lt;li>The main quest that starts automatically and guides player into more of the game.&lt;/li>
&lt;li>The user interface that displays quest goals, quest log and other information.&lt;/li>
&lt;li>The quest markers.&lt;/li>
&lt;li>An extended event log that includes all dialogs and other text messages player has seen or heard.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Item descriptions&lt;/strong>
&lt;ul>
&lt;li>Hovering over an inventory item will display its technical parameters as well as a general text description.&lt;/li>
&lt;li>These descriptions can contain unique lore beyond just technical specifications&lt;/li>
&lt;li>This includes notes and documents, as these items only present information through their description.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Technical drawings inspection mode&lt;/strong>
&lt;ul>
&lt;li>A combination of player reward and extra lore, the game will let you view some select drawings in high resolution.&lt;/li>
&lt;li>This also includes things like maps and posters potentially, however this would be a fairly limited and minor element.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h1 id="functional-gameplay-elements">Functional Gameplay Elements&lt;/h1>
&lt;p>These are the elements that have to do with the actual primary gameplay loop.&lt;/p>
&lt;ol>
&lt;li>&lt;strong>WASD movement&lt;/strong>
&lt;ul>
&lt;li>Standard first/third person shooter movement controls.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Lean left / right&lt;/strong>
&lt;ul>
&lt;li>Character can lean left or right to &amp;ldquo;pie&amp;rdquo; corners.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Jumping&lt;/strong>
&lt;ul>
&lt;li>Jump height is limited in this game in order to give a heavier feel to the player.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Climbing&lt;/strong>
&lt;ul>
&lt;li>The character can climb moderately tall obstacles (1.6 m) in order to get to places that could not be reached otherwise.&lt;/li>
&lt;li>Same mechanic allows to vault over obstacles if they meet appropriate height.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Weapon length&lt;/strong>
&lt;ul>
&lt;li>The player character has a unified rig. The weapons collide with the environment and deflect in order to fit in narrow spaces.&lt;/li>
&lt;li>This means that shorter guns would be required for comfortable use in-doors.&lt;/li>
&lt;li>It also requires player to be aware of their tactical positioning relative to obstacles. A long rifle can get stuck on an obstacle that is too close in front of the player.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Weapon condition&lt;/strong>
&lt;ul>
&lt;li>Firing shots from the weapon depletes condition, requiring the weapon to be periodically repaired.&lt;/li>
&lt;li>Bullets impacting the weapon when it is in hands or in the backpack also degrade the condition, however the weapon provides some bullet protection.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Worn item condition&lt;/strong>
&lt;ul>
&lt;li>Worn items have condition that degrades when player gets hit.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Primary / secondary reload&lt;/strong>
&lt;ul>
&lt;li>Some weapons require two types of consumables (for example, bullets and fuel).&lt;/li>
&lt;li>Player can separately reload either consumable in the weapon.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Realistic gun mechanics and weapon failures&lt;/strong>
&lt;ul>
&lt;li>The full cycle of the conventional weapons is recreated. Detailed simulation model allows for a more lively response of the weapon to failures.&lt;/li>
&lt;li>The intent is for player to get attached to their weapon, making it unique over the span of the game.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Weapon upgrades&lt;/strong>
&lt;ul>
&lt;li>Weapons can be upgraded. All weapons have a generic set of upgrades that provide minor to moderate improvements to gun performance.&lt;/li>
&lt;li>Each weapon also has a gimmick upgrade tree, which boosts a single parameters of this weapon beyond reasonable values, allowing players to explore ultra-specific niches.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Environment hazards&lt;/strong>
&lt;ul>
&lt;li>Various hazards drain players health points or disrupt their vision, hearing or other aspects of their operation.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Non-functional technical artifacts&lt;/strong>
&lt;ul>
&lt;li>These items provide a short lore text message and are sold for an increased price.&lt;/li>
&lt;li>They can be widely found around the zone, making them almost clutter-like, however still fetching a good price.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Functional technical artifacts&lt;/strong>
&lt;ul>
&lt;li>Some technical artifacts can have special functions or provide player with additional abilities.&lt;/li>
&lt;li>Additional items like the external targeting computer allow player to gain a corresponding perk from their perk tree while they are wearing the item.&lt;/li>
&lt;li>Technical artifacts may have expansion slots that change their operation or upgrade them.&lt;/li>
&lt;li>The functional technical artifacts can be found or bought from traders in very limited amounts.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Realistic ballistics&lt;/strong>
&lt;ul>
&lt;li>A simulation model predicts ballistic dynamics of a bullet and energy dynamics of the weapon itself.&lt;/li>
&lt;li>Wind affects bullet path, as well as the weapon zeroing and physical parameters.&lt;/li>
&lt;li>This mechanic serves to limit the pace of the game to tactical, requiring higher care and skill to engage targets from longer distances.&lt;/li>
&lt;li>Bullet penetration.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>No weapon sway, weapon holding dynamics&lt;/strong>
&lt;ul>
&lt;li>Player experiences recoil from the weapon, however there is functionally no natural weapon sway.&lt;/li>
&lt;li>The players character is a synth with strong artificial muscles.&lt;/li>
&lt;li>Heavy weapons slow down the aim stabilization mechanics, leading to more inertia and less accuracy from rapid movements.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Data points / Experience points, Perks&lt;/strong>
&lt;ul>
&lt;li>Works similarly to Fallout: New Vegas. Data points are awarded for performing various in-game actions, passing speech checks, completing quests and so on.&lt;/li>
&lt;li>Each time player levels up, they can select an additional perk that enhances their gameplay abilities or adds new features.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Inventory, in-world items and containers&lt;/strong>
&lt;ul>
&lt;li>Player can pick up items in the world, from containers, from traders or from NPC bodies.&lt;/li>
&lt;li>The inventory is grid-based, where number of cells is proportional to the volume taken up by the item.&lt;/li>
&lt;li>Player has a dominant hand into which active weapon is equipped.&lt;/li>
&lt;li>The non-dominant hand is used to load and unload ammunition from the gun.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Harnesses&lt;/strong>
&lt;ul>
&lt;li>Harnesses are special items which allow player to use a button to equip or use a specific item, usually a weapon.&lt;/li>
&lt;li>These can be found in the world and provide a different number of slots.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Backpacks&lt;/strong>
&lt;ul>
&lt;li>Backpacks provide storage and can have multiple containers inside them.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Relief vests&lt;/strong>
&lt;ul>
&lt;li>The relief vest allows the synth character to retrieve ammo for the weapon. Only ammunition from the vest will be accessible for the weapons usually.&lt;/li>
&lt;li>Ammunition that is too large and takes a longer time to reload is an exception to this.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Lower body armor&lt;/strong>
&lt;ul>
&lt;li>Pants.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Upper body armor&lt;/strong>
&lt;ul>
&lt;li>Body armor worn under relief and harness.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Left/right arm armor&lt;/strong>
&lt;ul>
&lt;li>Left/right arm armor or special extensions.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Coat&lt;/strong>
&lt;ul>
&lt;li>Clothes item worn on top of all other clothes.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Ladders&lt;/strong>
&lt;ul>
&lt;li>Ladders that can take player up and down.&lt;/li>
&lt;li>While player is on a ladder, they cannot use the dominant hand item.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Item value categories&lt;/strong>
&lt;ul>
&lt;li>Items can be contemporary/common/relic/artifact/ancient.&lt;/li>
&lt;li>This changes the strength of their effect, if they have one, and their price.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Repair items&lt;/strong>
&lt;ul>
&lt;li>Items that allow player to restore their weapon condition, item condition or HP.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Medical items&lt;/strong>
&lt;ul>
&lt;li>These items do not work on player, however they are highly valued by humans.&lt;/li>
&lt;li>Unlike the usual, the placement of these items is realistic and they are considerably rare.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Ammo abundance&lt;/strong>
&lt;ul>
&lt;li>Some types of ammo are common and widely available.&lt;/li>
&lt;li>Exotic ammo can be limited in quantity or might require additional resources to obtain.&lt;/li>
&lt;li>Ammo can exist in damaged/outdated versions. Such ammo can degrade weapon condition faster or even damage it. Plus, it may have random ballistics.&lt;/li>
&lt;li>Ammo can be picked up from the enemies.&lt;/li>
&lt;li>Many of the mercenary outposts contain ammo caches.&lt;/li>
&lt;li>There are also ancient and just old ammo caches still left to uncover all around the zone. Many of these caches provide poor outdated ammo in large quantities.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Individual magazines/clips&lt;/strong>
&lt;ul>
&lt;li>Each magazine is an individual object that must be loaded with individual bullet objects.&lt;/li>
&lt;li>Reloading can be done automatically by a button press, taking some time.&lt;/li>
&lt;li>Or can be done manually by dragging bullets into the magazine inside inventory.&lt;/li>
&lt;li>A pattern can be specified for a magazine. Changing the pattern does not unload the magazine, allowing to combine them.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Elevators and moving platforms&lt;/strong>
&lt;ul>
&lt;li>These provide access to high areas or across large gaps, quickly.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Doors&lt;/strong>
&lt;ul>
&lt;li>Some doors can be opened and closed to separate environments.&lt;/li>
&lt;li>Some doors are special and teleport player to an interior level.&lt;/li>
&lt;li>Many doors are permanently shut to limit the scope of the world, however all spaces are implied to have a realistic scale.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;hr>
&lt;h1 id="scripted-events-and-locations">Scripted Events and Locations&lt;/h1>
&lt;ol>
&lt;li>&lt;strong>Scavenger camps&lt;/strong>
&lt;ul>
&lt;li>These act like quest hubs for human side quests and for the scavenger main quest line.&lt;/li>
&lt;li>Scavenger faction is neutral to positive about player.&lt;/li>
&lt;li>Humans offer a bigger variety of trading supplies at a higher cost.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Synth outposts&lt;/strong>
&lt;ul>
&lt;li>Synths prefer small scattered outposts rather than having central camps like humans do.&lt;/li>
&lt;li>It is usually a source of synth-specific side quests.&lt;/li>
&lt;li>Some of the outposts have a trader willing to engage with the player.&lt;/li>
&lt;li>Synth traders tend to offer a small variety of items, however at a lower cost to the player.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Trading&lt;/strong>
&lt;ul>
&lt;li>Traders are available in scavenger camps, at synth outposts and sometimes randomly in the world.&lt;/li>
&lt;li>Different traders provide items of a different kind, for different pricing.&lt;/li>
&lt;li>Items are priced based on a category. Each trader has a specialty, a set of items they provide at a lesser cost than any other trader.&lt;/li>
&lt;li>All traders can sometimes sell unique items.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Interiors&lt;/strong>
&lt;ul>
&lt;li>The interiors are separate worldspaces that are not affected by the outside world.&lt;/li>
&lt;li>Some of the interiors are placed in the worldspace, however sparingly.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>Crafting workbenches&lt;/strong>
&lt;ul>
&lt;li>There are crafting workbenches placed throughout the world. They allow to combine some items in order to obtain another.&lt;/li>
&lt;li>These benches also allow player to repair weapons without repair items.&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ol>
&lt;p>&lt;em>(to be expanded)&lt;/em>&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted --></content></item><item><title>Synesthesia: Gameplay systems and elements</title><link>https://blog.opengbh.net/posts/0006-synesthesia-gameplay-systems/</link><pubDate>Sun, 25 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0006-synesthesia-gameplay-systems/</guid><description>Primary Gameplay Loop The primary loop is explore - cover - shoot. The player moves around complicated urban terrain, engaging human and creature enemies in tactical gunfights, managing their resources (maintaining weapons, keeping up with ammo counts, making sure equipment matches the enemies player is facing).
The game is influenced by S.T.A.L.K.E.R. series, Fallout: New Vegas and Arma 3. The primary activities in this game are:
Searching for technical artifacts Interacting with NPC characters Engaging in gunfights Exploring the environment Doing special narrative quests Managing ammunition and supplies, weapon condition → Synesthesia Gameplay Systems and Elements</description><content>&lt;h1 id="primary-gameplay-loop">Primary Gameplay Loop&lt;/h1>
&lt;p>The primary loop is &lt;strong>explore - cover - shoot&lt;/strong>. The player moves around complicated urban terrain, engaging human and creature enemies in tactical gunfights, managing their resources (maintaining weapons, keeping up with ammo counts, making sure equipment matches the enemies player is facing).&lt;/p>
&lt;p>The game is influenced by S.T.A.L.K.E.R. series, Fallout: New Vegas and Arma 3. The primary activities in this game are:&lt;/p>
&lt;ol>
&lt;li>Searching for technical artifacts&lt;/li>
&lt;li>Interacting with NPC characters&lt;/li>
&lt;li>Engaging in gunfights&lt;/li>
&lt;li>Exploring the environment&lt;/li>
&lt;li>Doing special narrative quests&lt;/li>
&lt;li>Managing ammunition and supplies, weapon condition&lt;/li>
&lt;/ol>
&lt;hr>
&lt;h4 id="-synesthesia-gameplay-systems-and-elements000120synesthesia20narrative20elementsmd">&lt;a href="https://blog.opengbh.net/0001-synesthesia-narrative-elements/">→ Synesthesia Gameplay Systems and Elements&lt;/a>&lt;/h4></content></item><item><title>Synesthesia: Improved starfield rendering + satellites</title><link>https://blog.opengbh.net/posts/0005-new-star-field-rendering/</link><pubDate>Sat, 24 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0005-new-star-field-rendering/</guid><description>While working on the new pre-rendered skybox system for Synesthesia, I came up with a way to render a cloud mask that would allow masking out objects that are supposed to be located behind the clouds - since skybox is a two-dimensional opaque image, there is no &amp;ldquo;behind&amp;rdquo;, things could only possibly render in front of this image.
However, with the cloud mask, objects that are rendered in front can be masked to appear behind, by concealing parts of them which are supposed to be hidden.</description><content>&lt;p>While working on the &lt;a href="https://blog.opengbh.net/posts/0003-pre-rendered-2d-skybox/">new pre-rendered skybox system&lt;/a> for Synesthesia, I came up with a way to render a cloud mask that would allow masking out objects that are supposed to be located behind the clouds - since skybox is a two-dimensional opaque image, there is no &amp;ldquo;behind&amp;rdquo;, things could only possibly render in front of this image.&lt;/p>
&lt;p>However, with the cloud mask, objects that are rendered in front can be masked to appear behind, by concealing parts of them which are supposed to be hidden. Because of this, a possibility opened up to render a realistic star sky.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230625183829.png" alt="Starfield rendered using the new code"/>&lt;/p>
&lt;p>The stars are rendered as Niagara particles and placed slightly in front of the sky sphere. While I did not finish this yet, the stars will be masked by the cloud mask, allowing them to appear correctly behind the cloud.&lt;/p>
&lt;p>The stars are defined by a catalog, where right ascension (azimuth) and declination (elevation) are specified. For now, I took a catalog that is relevant to present time, however it would be fun to produce a star catalog for year 2994 (when the game takes place).&lt;/p>
&lt;hr>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00015-81aT3U.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00015-81aT3U.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The video above shows a timelapse and a few satellites passing over. The satellites are indicated by a red glow for now, however I will add proper calculations for their apparent magnitude.&lt;/p>
&lt;p>The ring of satellites that appear to be frozen are an array of 36 geostationary satellites with a 10.0 degree step. They have an orbit defined as geostationary, I used them to verify orbital calculations and alignment between the starfield, the satellites and the geographical location of the observation.&lt;/p>
&lt;p>Although there is a specific location encoded internally in the game, the exact location of EDZ-01 (the city that you explore in Synesthesia) should be assumed ambiguous.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00016-ygUzpf.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00016-ygUzpf.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The video above shows movement of the satellites at the in-game time scale. Currently, 24 in-game hours take 2.4 hours of real world time. I may revise the scale in the future, however this seems to be optimal for the sort of gameplay that I have in mind. To compare, the same time of day scale is found in S.T.A.L.K.E.R.&lt;/p>
&lt;hr>
&lt;p>To support a large number of satellites the simulation code works like this:&lt;/p>
&lt;ol>
&lt;li>When it is first initialized, the time when satellite will become visible is pre-calculated. The satellite position is estimated at 5 minute intervals until position that is visible is found&lt;/li>
&lt;li>If satellite is visible, it is added to the visible satellites list. If a satellite drops below the horizon in this list, it is removed and added to the sorted satellite list.&lt;/li>
&lt;li>The sorted satellite list stores all satellites that are not visible in the order of next encounter.&lt;/li>
&lt;li>Every tick, the sorted satellite array is scanned until the first satellite that is still too early to encounter is found. In practice, this means that only a small handful of satellites at the start of the list will be checked. These satellites are removed from the sorted list and added to the visible list.&lt;/li>
&lt;/ol>
&lt;p>Satellites are defined by a catalog, where six orbital elements and two extra parameters are specified:&lt;/p>
&lt;ol>
&lt;li>Semi-major axis (&lt;code>SMA&lt;/code>)&lt;/li>
&lt;li>Eccentricity (&lt;code>ECC&lt;/code>)&lt;/li>
&lt;li>Inclination (&lt;code>INC&lt;/code>)&lt;/li>
&lt;li>Right ascension of ascending node / longitude of the ascending node (&lt;code>RAAN&lt;/code>)&lt;/li>
&lt;li>Periapsis argument (&lt;code>PAR&lt;/code>)&lt;/li>
&lt;li>Initial mean anomaly (&lt;code>MA0&lt;/code>)&lt;/li>
&lt;li>Tumble period: if the satellite is tumbling, its brightness is modulated with this period&lt;/li>
&lt;li>Apparent magnitude: apparent magnitude of the satellite under the best conditions&lt;/li>
&lt;/ol>
&lt;hr>
&lt;p>The satellite system provides extra immersion and flavor to the setting, but also I plan a RF scanning feature, where player can listen in to various radio signals. Many of these satellites are no longer operationally used, however they still likely broadcast a dead carrier or some sort of telemetry data that player would be able to receive (however, most of it would be procedural and meaningless, just for flavor).&lt;/p></content></item><item><title>Synesthesia: Starfield rendering</title><link>https://blog.opengbh.net/posts/0004-star-field-rendering/</link><pubDate>Fri, 23 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0004-star-field-rendering/</guid><description>See the previous post for more about how the skyboxes work in Synesthesia. One of the issues with the pre-rendered skyboxes is that the image of the stars in the background cannot properly rotate with the movement of sun.
The stars rotate over the sky as time progresses due to the rotation of Earth. However, since the stars must be behind the clouds and the current implementation of the pre-rendering cannot capture a mask of where the clouds are, it&amp;rsquo;s just embedded in the sky texture.</description><content>&lt;p>See &lt;a href="https://blog.opengbh.net/posts/0003-pre-rendered-2d-skybox/">the previous post for more about how the skyboxes work in Synesthesia&lt;/a>. One of the issues with the pre-rendered skyboxes is that the image of the stars in the background cannot properly rotate with the movement of sun.&lt;/p>
&lt;p>The stars rotate over the sky as time progresses due to the rotation of Earth. However, since the stars must be behind the clouds and the current implementation of the pre-rendering cannot capture a mask of where the clouds are, it&amp;rsquo;s just embedded in the sky texture.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00007-wrRDHy.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00007-wrRDHy.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>On the video above, pay attention to the top middle of the frame. There are two prominent stars there which appear to teleport around in a strange way - a consequence of them being embedded in the sky texture as it blends.&lt;/p>
&lt;p>Compare this to the way true dynamic sky behaves, shown on the video below. The star map correctly rotates with the movement of Earth.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00008-aPRSA7.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00008-aPRSA7.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>&lt;del>With some modification of the skybox renderer, it should be up to the task of rendering raw opacity of the cloud. This would allow separating the star map away from sky texture, making it scroll smoothly. It would also significantly improve the way the sun and the moon interact with the clouds.&lt;/del>&lt;/p>
&lt;hr>
&lt;p>While writing this post, I experimented a little with some simple modifications and got a result that I think will be practically useful. Here&amp;rsquo;s an example of a pre-rendered sky and a pre-rendered cloud mask. This cloud mask would allow distinguishing objects in front and behind the clouds accurately.&lt;/p>
&lt;p>Hover over the mask below to see the corresponding pre-rendered skybox texture:&lt;/p>
&lt;div class="compare-container">
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230624003258.png" class="compare-image1" />
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230624003244.png" class="compare-image2" />
&lt;/div></content></item><item><title>Synesthesia: Pre-rendered 2D skybox</title><link>https://blog.opengbh.net/posts/0003-pre-rendered-2d-skybox/</link><pubDate>Thu, 22 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0003-pre-rendered-2d-skybox/</guid><description>In Synesthesia, the sky is displayed by taking a skybox cubemap texture and applying it to the sky sphere surrounding the game world. This texture is pre-rendered and stored on disk, allowing the sky to be displayed at a very small performance cost.
I have now improved the skybox system to support a full day/night cycle, including the smooth movement of the sun and the moon. Because each skybox texture must be a static picture the sun and the moon are superimposed on top of the clouds, allowing them to move smoothly.</description><content>&lt;p>In Synesthesia, the sky is displayed by taking a skybox cubemap texture and applying it to the sky sphere surrounding the game world. This texture is pre-rendered and stored on disk, allowing the sky to be displayed at a very small performance cost.&lt;/p>
&lt;p>I have now improved the skybox system to support a full day/night cycle, including the smooth movement of the sun and the moon. Because each skybox texture must be a static picture the sun and the moon are superimposed on top of the clouds, allowing them to move smoothly.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00003-qa897P.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00003-qa897P.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>The skybox textures are pre-rendered inside the engine using a custom baking tool from a custom volumetric sky shader that uses the Unreal Engine volumetrics to render the clouds. Many skies are pre-rendered to allow the skybox to smoothly blend between different times of day and weathers.&lt;/p>
&lt;p>For example, with the default 30 minute time interval, this means that 48 textures will be created for a single weather for the full span of a 24-hour day.&lt;/p>
&lt;p>The sky system loads only the sky textures that are currently relevant and then blends between the two textures for the specified time of day, or blends between multiple textures in case the weather is changing.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00002-UnrealEditor-Win64-DebugGame.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00002-UnrealEditor-Win64-DebugGame.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>Since the 2D skybox pre-rendering process cannot properly capture a mask for cloud opacity/transparency right now, to avoid the sun and the moon always appearing to be on top of the clouds their brightness is adjusted based on a pre-calculated curve.&lt;/p>
&lt;p>In order to mask the sun when it goes behind the clouds, the bake tool captures two versions of the scene - one with light sources &lt;em>enabled&lt;/em>, another one with light sources &lt;em>disabled&lt;/em>. It will then compare the brightness of the pixel where sun is located.&lt;/p>
&lt;p>If the two brightness values are nearly equal, the sun is blocked by the clouds. If the two brightness values are different, then the sun is not concealed.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/00004-t8Dwls.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="00004-t8Dwls.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;hr>
&lt;p>This works &lt;em>good enough&lt;/em> for both sun and moon. The version of the scene with light sources &lt;em>disabled&lt;/em> is the one that gets saved to disk and later used with the 2D skybox.&lt;/p>
&lt;p>Here is an example set of the pre-rendered skybox textures for a specific weather. The sun and moon are omitted and will be later superimposed on top of this texture when it is rendered in-game:&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230623184212.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230623184217.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230623184222.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230623184227.png"/>&lt;/p>
&lt;hr>
&lt;p>This method for skyboxes is highly performant and gives me a lot of artistic control. It does have some flaws, one of which I will demonstrate &lt;a href="https://blog.opengbh.net/posts/0004-star-field-rendering/">in the next post&lt;/a>.&lt;/p>
&lt;p>Some of the limitations of these pre-rendered skies:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>The clouds within the same weather never move and are fully static.&lt;/strong> I could potentially fix this by allowing clouds to evolve during the capture process, however the clouds would have to somehow create a looping animation to avoid a discrepancy in cloud positions.&lt;/li>
&lt;li>&lt;strong>The star map blends in discrete steps, rather than smoothly rotating.&lt;/strong> I will update the star map with a generic texture so this effect wouldn&amp;rsquo;t be noticeable. However, see the next post for more about this.&lt;/li>
&lt;li>&lt;strong>The sun/moon do not interact with clouds in a satisfying way.&lt;/strong> They simply get darker or brighter, plus the curve doesn&amp;rsquo;t perfectly interpolate. But this isn&amp;rsquo;t very noticeable&lt;/li>
&lt;li>&lt;strong>Weather transitions are simplistic and there are no nice intermediate states.&lt;/strong> Also a limitation, the only way to transition between two weathers is to directly blend between them.&lt;/li>
&lt;/ul>
&lt;p>My intent is to solve most of the visual issues by providing a large pool of different skyboxes. Since they can be very easily generated and created, there can be enough interesting skies to look at so the other problems don&amp;rsquo;t matter as much.&lt;/p></content></item><item><title>Synesthesia: General description</title><link>https://blog.opengbh.net/posts/0002-synesthesia-blurb/</link><pubDate>Wed, 21 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0002-synesthesia-blurb/</guid><description>Short Blurb In this open-world roleplaying videogame, you play as a manufactured lifeform (a synth) waking up in a world in crisis. It is a first/third person tactical shooter, your goal is to collect valuable technical artifacts and documentation to help humans revive their golden age and save their civilization.
However, the old cities are now dangerous, full of industrial accidents and decaying infrastructure, making them Environmental Disaster Zones filled with lost treasures from the golden age.</description><content>&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020230625191455.png" alt="Panoramic view of EDZ-01 / Solaris City"/>&lt;/p>
&lt;h1 id="short-blurb">Short Blurb&lt;/h1>
&lt;p>In this open-world roleplaying videogame, you play as a manufactured lifeform (&lt;em>a synth&lt;/em>) waking up in a world in crisis. It is a &lt;strong>first/third person tactical shooter&lt;/strong>, your goal is to collect valuable technical artifacts and documentation to help humans revive their golden age and save their civilization.&lt;/p>
&lt;p>However, the old cities are now dangerous, full of industrial accidents and decaying infrastructure, making them &lt;strong>Environmental Disaster Zones&lt;/strong> filled with lost treasures from the golden age. You were lost in transit for &lt;strong>200 years&lt;/strong>, but now it&amp;rsquo;s up to you whether to help humans or follow your own interests.&lt;/p>
&lt;hr>
&lt;h1 id="long-blurb">Long Blurb&lt;/h1>
&lt;p>With advancements in science, humanity has finally reached post-scarcity, but over time got complacent with the miracles of technology of the new era. You play as a manufactured lifeform (&lt;em>a synth&lt;/em>), waking up in a world in crisis, a world that can no longer maintain its complex infrastructure and systems.&lt;/p>
&lt;p>The game is a &lt;strong>first/third person tactical shooter&lt;/strong> with roleplaying elements, focusing on urban exploration and urban combat. The primary goal is to collect technical artifacts and documentation, highly valued by humans with hopes that some of this old world information could help them revive the golden age of the old and stop the fall of their civilization.&lt;/p>
&lt;p>As the crisis grew, the old cities became dangerous, full of industrial accidents, decaying infrastructure. Toxic hazards, radiation leaks, electromagnetic dangers all turned some of the largest cities into Environmental Disaster Zones - exclusion zones, full lost treasures and miracles from the golden age.&lt;/p>
&lt;p>The players character was lost in transit and forgotten about as the world around descended into disarray, stranding them in a dream-like state for around 200 years. Now, it is up to you on whether you want to help humans in their pursuit of technology that may save their world, or follow your own interests.&lt;/p></content></item><item><title>Spoiler tags for Hugo</title><link>https://blog.opengbh.net/posts/0001-hugo-spoiler-tags/</link><pubDate>Tue, 20 Jun 2023 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/posts/0001-hugo-spoiler-tags/</guid><description>I use Hugo to generate the static HTML pages for this website. One of the projects I&amp;rsquo;m working on involves a storyline which some of my friends do not want spoiled to them. So for this, I had to add a simple spoiler shortcode.
This spoiler shortcode works like this: in-line spoilered content. It can be used to mask out individual phrases or whole blocks of text, as seen below.</description><content>&lt;p>I use Hugo to generate the static HTML pages for this website. One of the projects I&amp;rsquo;m working on involves a storyline which some of my friends do not want spoiled to them. So for this, I had to add a simple spoiler shortcode.&lt;/p>
&lt;p>This spoiler shortcode works like this:
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag0"/>
&lt;label class="spoiler" for="spoilerTag0">in-line spoilered content&lt;/label>. It can be used to
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag1"/>
&lt;label class="spoiler" for="spoilerTag1">mask out&lt;/label> individual phrases or whole blocks of text, as seen below.&lt;/p>
&lt;input class="spoiler_dummy" type="checkbox" id="spoilerTag2"/>
&lt;label class="spoiler" for="spoilerTag2">An entire paragraph can be put into a spoiler tag. Each spoiler tag is clickable individually, and they can be clicked again to hide the text. They do not require any additional code as they are based on the checkbox functionality. A simple extra style sheet and the shortcode template file are all that's required.&lt;/label>
&lt;p>Here is the source code to the spoiler tag functionality on my website:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-css" data-lang="css">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">//&lt;/span> &lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#f92672">theme&lt;/span>&lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#f92672">assets&lt;/span>&lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#f92672">css&lt;/span>&lt;span style="color:#f92672">/&lt;/span>&lt;span style="color:#f92672">spoiler&lt;/span>.&lt;span style="color:#a6e22e">css&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#a6e22e">spoiler_dummy&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">display&lt;/span>: &lt;span style="color:#66d9ef">none&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#a6e22e">spoiler_dummy&lt;/span>:&lt;span style="color:#a6e22e">checked&lt;/span> &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#f92672">label&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">color&lt;/span>: &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#66d9ef">color&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">background&lt;/span>: &lt;span style="color:#960050;background-color:#1e0010">$&lt;/span>&lt;span style="color:#66d9ef">background&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">transition&lt;/span>: &lt;span style="color:#66d9ef">color&lt;/span> &lt;span style="color:#ae81ff">0.3&lt;/span>&lt;span style="color:#66d9ef">s&lt;/span> &lt;span style="color:#66d9ef">ease&lt;/span>, &lt;span style="color:#66d9ef">background&lt;/span> &lt;span style="color:#ae81ff">0.3&lt;/span>&lt;span style="color:#66d9ef">s&lt;/span> &lt;span style="color:#66d9ef">ease&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>.&lt;span style="color:#a6e22e">spoiler&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">position&lt;/span>: &lt;span style="color:#66d9ef">relative&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">display&lt;/span>: &lt;span style="color:#66d9ef">inline-block&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">cursor&lt;/span>: &lt;span style="color:#66d9ef">help&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">border-radius&lt;/span>: &lt;span style="color:#ae81ff">2&lt;/span>&lt;span style="color:#66d9ef">px&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">color&lt;/span>: &lt;span style="color:#66d9ef">black&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">background&lt;/span>: &lt;span style="color:#66d9ef">black&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">transition&lt;/span>: &lt;span style="color:#66d9ef">color&lt;/span> &lt;span style="color:#ae81ff">0.3&lt;/span>&lt;span style="color:#66d9ef">s&lt;/span> &lt;span style="color:#66d9ef">ease&lt;/span>, &lt;span style="color:#66d9ef">background&lt;/span> &lt;span style="color:#ae81ff">0.3&lt;/span>&lt;span style="color:#66d9ef">s&lt;/span> &lt;span style="color:#66d9ef">ease&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-html" data-lang="html">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;!-- /theme/layouts/shortcodes/spoiler.html --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">input&lt;/span> &lt;span style="color:#a6e22e">class&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;spoiler_dummy&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">type&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;checkbox&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">id&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;spoilerTag{{ .Ordinal }}&amp;#34;&lt;/span>/&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;&lt;span style="color:#f92672">label&lt;/span> &lt;span style="color:#a6e22e">class&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;spoiler&amp;#34;&lt;/span> &lt;span style="color:#a6e22e">for&lt;/span>&lt;span style="color:#f92672">=&lt;/span>&lt;span style="color:#e6db74">&amp;#34;spoilerTag{{ .Ordinal }}&amp;#34;&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {{- .Inner | safeHTML -}}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&amp;lt;/&lt;span style="color:#f92672">label&lt;/span>&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div></content></item><item><title/><link>https://blog.opengbh.net/0002-5d3doxjmfq-portfolio/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/0002-5d3doxjmfq-portfolio/</guid><description>Phoenix | About Me Location: Concord, CA 94521
Telephone: +1 (925) 768 1562
Email: phoenix@foxworks.cz / fluffy.vuuli@gmail.com
I am a multi-specialist with focus on systems engineering, simulation and game development. My interest is in creating immersive interactive experiences, designing and organizing projects at a high level, providing inter-disciplinary help to the team and connecting different specialists together.
My main strength is understanding of the many disciplines across engineering and game development scopes, allowing me to help the rest of the team to understand the project as a whole and to be able to integrate together work of many individuals.</description><content>&lt;h1 id="phoenix--about-me">Phoenix | About Me&lt;/h1>
&lt;p>&lt;strong>Location:&lt;/strong> Concord, CA 94521&lt;/p>
&lt;p>&lt;strong>Telephone:&lt;/strong> +1 (925) 768 1562&lt;/p>
&lt;p>&lt;strong>Email:&lt;/strong> &lt;a href="mailto:phoenix@foxworks.cz">phoenix@foxworks.cz&lt;/a> / &lt;a href="mailto:fluffy.vuuli@gmail.com">fluffy.vuuli@gmail.com&lt;/a>&lt;/p>
&lt;hr>
&lt;p>I am a multi-specialist with focus on systems engineering, simulation and game development. My interest is in creating immersive interactive experiences, designing and organizing projects at a high level, providing inter-disciplinary help to the team and connecting different specialists together.&lt;/p>
&lt;p>My main strength is understanding of the many disciplines across engineering and game development scopes, allowing me to help the rest of the team to understand the project as a whole and to be able to integrate together work of many individuals.&lt;/p>
&lt;hr>
&lt;h2 id="project-subtransit--subtransit-drive">Project: Subtransit / Subtransit Drive&lt;/h2>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206135441.png"/>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe src="https://www.youtube.com/embed/FCbNjmkxzaY" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
&lt;/div>
&lt;/p>
&lt;p>&lt;em>(headphones suggested for the video)&lt;/em>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;/th>
&lt;th>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;strong>Status&lt;/strong>&lt;/td>
&lt;td>Released on iOS/Android&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Engine&lt;/strong>&lt;/td>
&lt;td>Unreal Engine 4 / Unreal Engine 5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Homepage&lt;/strong>&lt;/td>
&lt;td>&lt;a href="https://subtrans.it/en">https://subtrans.it/en&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>iOS&lt;/strong>&lt;/td>
&lt;td>&lt;a href="https://apps.apple.com/us/app/subtransit-drive/id1636723806">https://apps.apple.com/us/app/subtransit-drive/id1636723806&lt;/a>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;strong>Android&lt;/strong>&lt;/td>
&lt;td>&lt;a href="https://play.google.com/store/apps/details?id=llc.wagon.subtransitdrive">https://play.google.com/store/apps/details?id=llc.wagon.subtransitdrive&lt;/a>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Subtransit is an industrial-grade engineering train simulator that presents the detailed behavior of the trains and infrastructure around them, featuring digital twin systems simulation and a custom physics engine for detailed movement dynamics. It is a type of a simulation game where every button and switch work exactly as they should in real life.&lt;/p>
&lt;p>This project was developed with full VR support, however currently a VR version is not publicly available. Subtransit Drive is a version of this simulator for mobile platforms and consoles.&lt;/p>
&lt;p>I have done the following work for Subtransit:&lt;/p>
&lt;ul>
&lt;li>Engineering &amp;amp; reverse-engineering, technical documentation work, research&lt;/li>
&lt;li>Developing the mathematical models for the train hardware and infrastructure of the metro line, traffic model and more&lt;/li>
&lt;li>Physics &amp;amp; simulation programming (C++)&lt;/li>
&lt;li>Gameplay programming for desktop and VR modes (C++ / Blueprint)&lt;/li>
&lt;li>Editor and custom toolset programming (C++ / C#)&lt;/li>
&lt;li>Custom Blender plugin programming (Python)&lt;/li>
&lt;li>Designing the game systems and their integration&lt;/li>
&lt;li>Technical art and custom shaders, rendering tech for different parts of the game (HLSL / Material Editor)&lt;/li>
&lt;li>3D modeling and texturing, additional 2D art, user interface concepts&lt;/li>
&lt;li>Technical photography, sound recording and texture sources&lt;/li>
&lt;li>Sound design and integration with the game systems&lt;/li>
&lt;li>Creating the artistic, programming and other workflows for the rest of the team&lt;/li>
&lt;li>Organizing the production process&lt;/li>
&lt;li>Writing public-facing materials, promotional and other texts&lt;/li>
&lt;/ul>
&lt;p>Additionally, for Subtransit Drive:&lt;/p>
&lt;ul>
&lt;li>Developing the optimization methodology for PC-level assets to be used with a mobile level platform&lt;/li>
&lt;li>Advanced performance and memory optimizations, in-depth processing &amp;amp; memory profile analysis&lt;/li>
&lt;li>Vertex animation system and mobile platform specific large crowd rendering&lt;/li>
&lt;li>Porting the gameplay and simulation code for use with mobile platforms&lt;/li>
&lt;/ul>
&lt;h4 id="subtransit-custom-physics-engine">Subtransit: Custom Physics Engine&lt;/h4>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0001.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0001.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;p>I developed the custom physics engine used by Subtransit for wheel vs rail collision. It provides realistic dynamics of motion for the train in 3D space. The train is not affixed to the spline, but rather stays on track by responding to dynamic forces happening between the wheel and the rail.&lt;/p>
&lt;p>This physics engine simulates per-wheel slip, hunting motions and other elements contributing to dynamics of movement experienced by the train. This allows the game to present train physics with high degree of accuracy, and these same physics forces drive the immersive movement of the in-game camera.&lt;/p>
&lt;p>The custom physics engine provides collision response forces between the rail and the wheels and integrates with PhysX or Chaos in Unreal Engine. The integration work required making modifications to the Unreal Engine source code, and have been since integrated with the engine itself.&lt;/p>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0002.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0002.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0003.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0003.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;h4 id="subtransit-simulation--engineering">Subtransit: Simulation &amp;amp; Engineering&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/TAHDrDS.png"/>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0005.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0005.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>&lt;/p>
&lt;p>I have created mathematical simulation models of all elements of the train hardware, signalling infrastructure and other elements of the subway train network based on analyzing engineering documentation and doing reverse engineering myself.&lt;/p>
&lt;p>As part of my duties I have done drafting / technical drawings for many different elements of the train system, as well as documenting the reverse engineering results. Presenting and documenting my work to the rest of the team members has been a large part of it.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206150458.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207152941.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206152654.png"/>&lt;/p>
&lt;p>Part of this work involved architecture, civil engineering and related disciplines to pave the way for accurate recreations of the real world, including when insufficient information was available.&lt;/p>
&lt;h4 id="subtransit-technical--art-workflows">Subtransit: Technical &amp;amp; Art Workflows&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/1t3XDKy.png"/>&lt;/p>
&lt;p>I have created the technical and art workflows for Subtransit after completing every kind of work required for the project myself, then working with feedback from the team to iron out and make these workflows most suitable for our specific uses.&lt;/p>
&lt;p>These workflows are still used by the team to continue work on new content &amp;amp; assets for the game. The documentation I wrote is now the basis for training new employees on the project or quickly bringing our external contractors up to date with internal knowledge at the company.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/2NWUK74.png"/>&lt;/p>
&lt;p>These workflows also teach team members the use of the custom toolset I developed for this project, that significantly speeds up quality and speed of work. These assorted tools are used at various stages of the working process to speed up measurements and work-specific calculations, create specific inputs to artistic processes.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206183222.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207160234.png"/>&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;h4 id="subtransit-3d-modeling-and-texturing">Subtransit: 3D Modeling and Texturing&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/uhWKKOH.jpg"/>&lt;/p>
&lt;p>I have created most of the environment (world) textures in the current release of the game, as well as the initial versions for many of the 3D models used in the game before they were passed on to other artists.&lt;/p>
&lt;p>Understanding the art process together with technical aspects of the engine &amp;amp; rendering tech that I set up allowed us to develop some advanced optimization and rendering techniques to give our game the graphics fidelity that it has, at a very low cost to our budget and development time.&lt;/p>
&lt;p>The tools used for texturing work were Substance Painter, Photoshop, Agisoft Photoscan, and some custom tools. All of the 3D modeling has been done in Blender.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206151140.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206182539.png"/>&lt;/p>
&lt;p>I have done a large proportion of technical photography for creating references and texture sources, as well as organized other team members to do it when I was not available, helped with sound recording. Together with their help, we have taken about a hundred thousand photos, recorded thousands of hours of sound.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207154509.png"/>&lt;/p>
&lt;h4 id="subtransit-technical-art">Subtransit: Technical Art&lt;/h4>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231206152126.png"/>&lt;/p>
&lt;p>All of the technical art work on this project has been done by me. I have developed a custom approach for efficient and visually appealing rendering of the train tunnels, computer screens inside the trains, the rails and vehicles themselves and much more.&lt;/p>
&lt;p>This work included C++ engine modifications, as well as HLSL shader work combined with the Unreal Engine material editor. Most of visual aspects of this project are customized and tailored to our needs in some way, allowing us to save artist time or obtain higher fidelity at a lower cost.&lt;/p>
&lt;p>&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0004.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0004.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;img src="https://blog.opengbh.net/images/Y6DDiiB.png"/>&lt;/p>
&lt;p>The tunnel rendering required a custom Blender plugin to pre-bake special texturing data into the mesh, a special shader that mixes the different layers and elements of the texture together, resulting in a seamless high quality tunnel, rendered at a low cost. I also optimized the same rendering tech for use on the limited mobile phone/console platforms.&lt;/p>
&lt;p>All tunnel graphics are referenced to engineering drawings, and together with the track layout system that I wrote they let us recreate the accurate real world train tunnels, in the physics and visual regards.&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207163141.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207163236.png"/>&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;p>The visual assets I created for this project and as an example have then been used by other members of the team to continue producing new assets according to this workflow.&lt;/p>
&lt;p>I have also developed a crowd rendering system for Subtransit that works on the mobile platforms. It allows cheap simulation of &amp;ldquo;AI&amp;rdquo; for the individual passengers, adheres to a global traffic model that ensures the passenger flows are realistic and depend on the specific time-of-day and travel direction, and cheap passenger rendering.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207160555.png"/>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0010.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0010.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;h4 id="subtransit-sound-design">Subtransit: Sound Design&lt;/h4>
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
&lt;iframe src="https://www.youtube.com/embed/kBs3xC7MRx8" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" allowfullscreen title="YouTube Video">&lt;/iframe>
&lt;/div>
&lt;p>I developed the sound engine for Subtransit based on the FMOD Sound System. It uses multiple parallel DSP channels and clever mixing set up between them, tailored to the target scene to simulate sound propagation in realtime, without having to involve high intensity calculations.&lt;/p>
&lt;p>Many of the sounds in the game were recorded by me, before I gave this work to other team members. All sounds in Subtransit are recorded from the real conditions, edited and processed, and then attached to a physics model for maximum responsiveness. All sound cues are generated by the physics / systems simulation models and depend on the current conditions.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207160419.png"/>&lt;/p>
&lt;hr>
&lt;h2 id="project-synesthesia">Project: Synesthesia&lt;/h2>
&lt;p>Synesthesia is a side project I&amp;rsquo;ve been working. It is a highly systemic game, an open world tactical shooter with RPG elements.&lt;/p>
&lt;p>This game is a playground for testing many of the systems I have developed:&lt;/p>
&lt;ul>
&lt;li>An open world persistency system, tracking progress of player and objects they may have moved around in the world, state changes.&lt;/li>
&lt;li>An advanced custom AI system for game enemies, based on soft feedback loops rather than a state machine&lt;/li>
&lt;li>Weapon simulation models, for calculating the detailed mechanics of each weapon and creating a responsive and immersive feel for the player&lt;/li>
&lt;li>A ballistic system for the projectiles&lt;/li>
&lt;li>Assorted gameplay systems, &lt;strong>such&lt;/strong> as the unified rig and its behaviors in tight spaces&lt;/li>
&lt;li>Different special effects that integrate with the gameplay&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207160947.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207160951.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207161050.png"/>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0007.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0007.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207161829.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207161833.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207161916.png"/>
&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231207162002.png"/>&lt;/p>
&lt;hr>
&lt;h2 id="project-4d-raytracer">Project: 4D Raytracer&lt;/h2>
&lt;p>A raytracer that works in four spatial dimensions (XYZW) for 4D rendering. I built it to study the behavior of four dimensional space and as an art project. It is written in C and is fully multi-threaded.&lt;/p>
&lt;p>It supports signed distance fields, a collection of basic 4D primitives and tetrahedron-based 4D mesh rendering.&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/Pasted%20image%2020231217134336.png"/>
&lt;video class="html-video" src="https://blog.opengbh.net/videos/P0012.mp4" width="100%" preload="auto" autoplay loop controls playsinline>
Sorry, your browser doesn't support embedded videos. You can &lt;a href="P0012.mp4">download it&lt;/a> and watch it with an external player.
&lt;/video>&lt;/p>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;h4 id="heading">&lt;/h4>
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted -->
&lt;!-- raw HTML omitted --></content></item><item><title>About</title><link>https://blog.opengbh.net/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://blog.opengbh.net/about/</guid><description>My name is Phoenix&amp;hellip; &amp;hellip;and this is my personal website. Here I post articles, updates about my personal projects and anything else I want here. My interests are complex systems engineering, art, writing, programming, music, architecture, drafting and many other things.
You can contact me here: fluffy.vuuli@gmail.com</description><content>&lt;h1 id="my-name-is-phoenix">My name is Phoenix&amp;hellip;&lt;/h1>
&lt;p>&amp;hellip;and this is my personal website. Here I post articles, updates about my personal projects and anything else I want here. My interests are complex systems engineering, art, writing, programming, music, architecture, drafting and many other things.&lt;/p>
&lt;p>You can contact me here: &lt;a href="mailto:fluffy.vuuli@gmail.com">fluffy.vuuli@gmail.com&lt;/a>&lt;/p>
&lt;p>&lt;img src="https://blog.opengbh.net/images/dragon.png" alt="A cute dragon that I&amp;rsquo;ve drawn for my friend"/>&lt;/p></content></item></channel></rss>