<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Archive on Transdimensional Phoenix</title><link>https://blog.opengbh.net/posts/</link><description>Recent content in Archive on Transdimensional Phoenix</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Sat, 01 Jan 2000 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.opengbh.net/posts/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/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></channel></rss>