<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Gaurav De</title>
    <description>The latest articles on DEV Community by Gaurav De (@gaurav_de).</description>
    <link>https://dev.to/gaurav_de</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F699977%2F36dd9f96-d7f7-470d-a016-c3c3b65037ac.jpg</url>
      <title>DEV Community: Gaurav De</title>
      <link>https://dev.to/gaurav_de</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/gaurav_de"/>
    <language>en</language>
    <item>
      <title>Creating an HD-2D Rendering Pipeline on DX12</title>
      <dc:creator>Gaurav De</dc:creator>
      <pubDate>Fri, 16 Jan 2026 10:12:05 +0000</pubDate>
      <link>https://dev.to/gaurav_de/creating-an-hd-2d-rendering-pipeline-on-dx12-205k</link>
      <guid>https://dev.to/gaurav_de/creating-an-hd-2d-rendering-pipeline-on-dx12-205k</guid>
      <description>&lt;h3&gt;
  
  
  Introduction:
&lt;/h3&gt;

&lt;p&gt;In recent years, the HD-2D art style, made popular by games like Octopath Traveler and Triangle Strategy has brought new life to pixel art. This style places 2D pixel sprites into realistic 3D worlds with modern lighting, creating a look that feels like a living diorama.&lt;br&gt;
For my university graphics project, I wanted to understand how this style works and recreate it myself. I built a custom renderer designed to produce the same visual effect.&lt;br&gt;
This project was not just about making things look good—it was also a technical challenge. Traditional rendering pipelines are built for 3D models, not flat 2D sprites with transparency. A big part of the work was figuring out how to make these two ideas work together.&lt;br&gt;
In this post, I explain the research I did, the design choices I made (such as choosing between Deferred and Forward rendering), and how I implemented key features like shadows and cinematic depth of field.&lt;/p&gt;
&lt;h3&gt;
  
  
  Pipeline Architecture: Why I Chose Deferred Rendering
&lt;/h3&gt;

&lt;p&gt;The first big technical choice was deciding how to render the scene. While researching similar games, I found that studios take two different approaches.&lt;/p&gt;

&lt;p&gt;The Octopath approach:&lt;br&gt;
Square Enix uses Unreal Engine 4, which uses Deferred Rendering by default. This makes it easy to support many dynamic lights, such as torches, street lamps, and spell effects. These lights are very important for the dramatic, high-contrast look of HD-2D games.&lt;/p&gt;

&lt;p&gt;The modern indie approach:&lt;br&gt;
Some newer indie games, like Sea of Stars, use custom rendering pipelines. These are often based on Forward+ rendering, which works well for 2D lighting and avoids some of the transparency problems that Deferred rendering has.&lt;/p&gt;

&lt;p&gt;I decided to use a Deferred Rendering pipeline.&lt;br&gt;
Deferred rendering makes transparency harder to handle, so sprites often need extra forward passes or special techniques. However, it has a big advantage: lighting cost is separated from geometry complexity. This was important for my project.&lt;br&gt;
Using Deferred rendering allowed me to treat 2D sprites like real 3D objects. I stored their color, normals, and depth in the G-Buffer, so they could react to lighting in the same way as 3D models.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 1: The Unified G-Buffer Layout:
&lt;/h4&gt;

&lt;p&gt;To make this work, I standardized the output of all my geometry shaders. Whether drawing a 3D mesh or a 2D Sprite, they both target this exact stucture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// This is the G_Buffer pixel shader&lt;/span&gt;
&lt;span class="c1"&gt;// Common Output Structure (Used by both 3D PBR and 2D Sprite Shaders)&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;PixelShaderOutput&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Target 0: Albedo (R8G8B8A8)&lt;/span&gt;
    &lt;span class="c1"&gt;// Stores Base Color. For Sprites, this is the Texture Color.&lt;/span&gt;
    &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;Albedo&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SV_Target0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

    &lt;span class="c1"&gt;// Target 1: World Normal (R16G16B16A16_FLOAT)&lt;/span&gt;
    &lt;span class="c1"&gt;// High precision is critical for smooth lighting on "flat" surfaces.&lt;/span&gt;
    &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;Normal&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SV_Target1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

    &lt;span class="c1"&gt;// Target 2: Material Data (R8G8B8A8)&lt;/span&gt;
    &lt;span class="c1"&gt;// Packed: Roughness (R), Metalness (G), Ambient Occlusion (B)&lt;/span&gt;
    &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;Material&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SV_Target2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Making Sprites React to Light&lt;/em&gt;&lt;br&gt;
One problem I discovered was that normal sprite rendering looks flat when lit. To fix this, I added normal maps to the sprites.&lt;/p&gt;

&lt;p&gt;For creation of Normal Maps for my Sprites I used a famous tool available online called "Laigter". This tool allows us to create Normal Maps for any sprites or textures simply by uploading it to the software.&lt;/p&gt;
&lt;h4&gt;
  
  
  Step 2: Solving "Flat" Sprites with Normal Mapping
&lt;/h4&gt;

&lt;p&gt;A naive implementation of 2D sprites in a 3D engine looks "paper-thin" because the geometric normal always points straight back at the camera (0,0,-1).&lt;/p&gt;

&lt;p&gt;To give sprites volume, I implemented Tangent-Space Normal Mapping within the sprite shader. This cheats the physics by transforming the 2D texture normals (bumps on the sprite) into the 3D World Space.&lt;/p&gt;

&lt;p&gt;I encountered a specific issue where the Green channel (Y) was flipped compared to my engine's coordinate system, making light come from the wrong angle. I fixed this by manually constructing the TBN (Tangent-Bitangent-Normal) matrix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from shaders/SpriteShader_ps.hlsl&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Sample the Normal Map&lt;/span&gt;
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;normalTangent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g_NormalTexture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_Sampler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;// Unpack from [0,1] to [-1, 1] range&lt;/span&gt;
&lt;span class="n"&gt;normalTangent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalTangent&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Fix Coordinate Mismatch&lt;/span&gt;
&lt;span class="c1"&gt;// My engine uses a coordinate system where Y is opposite to the texture generator&lt;/span&gt;
&lt;span class="n"&gt;normalTangent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;normalTangent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 

&lt;span class="c1"&gt;// 3. Construct TBN Matrix on the fly&lt;/span&gt;
&lt;span class="c1"&gt;// We use the Vertex Tangent (Right) and Geometric Normal (Back) to find Up&lt;/span&gt;
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tangent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyz&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; 
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;IN&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tangent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;float3x3&lt;/span&gt; &lt;span class="n"&gt;TBN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float3x3&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 4. Transform 2D Bump to 3D World Normal&lt;/span&gt;
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;finalNormal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalTangent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TBN&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

&lt;span class="c1"&gt;// Now the flat sprite writes a "3D" normal to the G-Buffer!&lt;/span&gt;
&lt;span class="n"&gt;OUT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Normal&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;finalNormal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h4&gt;
  
  
  Step 3: The Lighting Pass
&lt;/h4&gt;

&lt;p&gt;Once the G-Buffer is packed, I perform a Deferred Lighting Pass. This is a full-screen operation that calculates lighting for every pixel.&lt;/p&gt;

&lt;p&gt;Optimization: The Full-Screen Triangle Instead of rendering a generic Quad mesh, I use a Vertex Shader trick to generate a single triangle that covers the entire screen. This is slightly faster than a Quad because it avoids the diagonal edge down the middle of the screen (quads are 2 triangles), preventing helper-pixel waste along that edge.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from shaders/LightingShader_vs.hlsl&lt;/span&gt;
&lt;span class="n"&gt;VS_Output&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uint&lt;/span&gt; &lt;span class="n"&gt;VertexID&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SV_VertexID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Algorithmically generate a triangle that covers UV space [0,0] to [1,1]&lt;/span&gt;
    &lt;span class="c1"&gt;// ID 0 -&amp;gt; (-1,  1)&lt;/span&gt;
    &lt;span class="c1"&gt;// ID 1 -&amp;gt; ( 3,  1)&lt;/span&gt;
    &lt;span class="c1"&gt;// ID 2 -&amp;gt; (-1, -3)&lt;/span&gt;
    &lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;texCoord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float2&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;VertexID&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;VertexID&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;texCoord&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;float2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;float2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;texCoord&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Lighting Logic: Reconstructing World Position&lt;/em&gt; Since I am using a Deferred renderer, I don't have the "World Position" of the geometry anymore—I only have a 2D image. To calculate lighting (which depends on distance), I implemented a mathematical reconstruction using the Hardware Depth Buffer.&lt;/p&gt;

&lt;p&gt;This saves massive amounts of memory bandwidth because I don't need to write a &lt;code&gt;XYZ_FLOAT&lt;/code&gt; texture to the G-Buffer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from shaders/LightingShader_ps.hlsl&lt;/span&gt;

&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="nf"&gt;ReconstructWorldPos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Convert Screen UV [0,1] back to NDC [-1, 1]&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Create a vector in Clip Space&lt;/span&gt;
    &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Multiply by Inverse View-Projection Matrix&lt;/span&gt;
    &lt;span class="c1"&gt;// This effectively "un-projects" the pixel back into the 3D world&lt;/span&gt;
    &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;worldPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;InverseViewProj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;worldPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xyz&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;worldPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By combining these techniques, I achieved a pipeline where:  Sprites and Meshes are indistinguishable to the lighting engine, Expensive lighting is only calculated once per pixel, not per object and Flat pixel art gains "volume" and reacts correctly to rotating lights.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Shadow Problem: Making 2D Sprites Feel Grounded
&lt;/h3&gt;

&lt;p&gt;One of the hardest problems was getting shadows to look right.&lt;br&gt;
2D sprites are basically flat images with no thickness. When light hits them from certain angles (like sunlight from above), they don’t cast proper shadows because a flat plane has no real volume.&lt;/p&gt;

&lt;p&gt;What I Learned from Research&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Shadow proxies:&lt;/em&gt;&lt;br&gt;
By studying games like Octopath Traveler and Live A Live, it appears they use invisible 3D shapes to cast shadows. These hidden models give characters proper, solid shadows even though the visible character is just a flat sprite.&lt;br&gt;
However, for this engine, I wanted a more precise solution that respects the pixel art's silhouette.&lt;/p&gt;
&lt;h4&gt;
  
  
  Solution A: Alpha-Tested Shadow Mapping
&lt;/h4&gt;

&lt;p&gt;I implemented a specialized "Shadow Pass" that runs before the main rendering. This pass renders the scene from the perspective of the sun (LightViewProj).&lt;/p&gt;

&lt;p&gt;For standard 3D meshes, this is a simple Z-buffer write. But for 2D sprites, I wrote a custom Pixel Shader that performs an Alpha Test.&lt;/p&gt;

&lt;p&gt;The shader reads the sprite's texture. If a pixel is transparent (alpha &amp;lt; 0.9), it discards the pixel. This prevents the empty parts of the rectangular quad from casting a shadow, ensuring the shadow perfectly matches the character's pixel art shape.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// from shaders/ShadowShader_ps.hlsl&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PS_Input&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Sample the sprite texture alpha&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g_Texture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_Sampler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UV&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Discard invisible pixels&lt;/span&gt;
    &lt;span class="c1"&gt;// By discarding here, we prevent the Depth Buffer from recording a hit.&lt;/span&gt;
    &lt;span class="c1"&gt;// This allows light to pass through the transparent details of the sprite.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;alpha&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;discard&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// If we survive the discard, the hardware automatically writes the depth.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;em&gt;Contact shadow issues:&lt;/em&gt;&lt;br&gt;
Standard shadow maps often have a problem called “Peter Panning,” where the shadow appears slightly separated from the character’s feet. This makes sprites look like they’re floating above the ground.&lt;/p&gt;

&lt;p&gt;I used Shadow Mapping in my Deferred Rendering pipeline, using the scene’s depth data. However, shadow maps alone didn’t fully solve the floating effect.&lt;/p&gt;
&lt;h4&gt;
  
  
  Solution B: Screen Space Contact Shadows (SSCS)
&lt;/h4&gt;

&lt;p&gt;Even with correct shadow shapes, sprites often look like they are floating. This is caused by "Shadow Bias" a small offset required to prevent visual glitches (shadow acne) on 3D surfaces. This bias detaches the shadow from the sprite's feet.&lt;/p&gt;

&lt;p&gt;To fix this, I implemented Screen Space Contact Shadows.&lt;/p&gt;

&lt;p&gt;This technique works in the Lighting Shader. It fires a short ray from every pixel towards the light source. It checks the Depth Buffer at each step to see if something nearby is blocking the light. Because it works in Screen Space, it has perfect precision and doesn't need a bias closer to the camera.&lt;/p&gt;

&lt;p&gt;Here is the core raymarching loop I implemented:&lt;br&gt;
This technique makes the sprites feel firmly attached to the ground. It adds visual weight and realism that regular shadow maps alone could not provide.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// LightingShader_ps.hlsl&lt;/span&gt;

&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="nf"&gt;ScreenSpaceContactShadows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;worldPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;lightDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;uv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Raymarch Setup&lt;/span&gt;
    &lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;rayDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lightDir&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;stepSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RayLength&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;MaxSteps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// ... Dithering logic to hide banding ...&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MaxSteps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;rayPos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;rayDir&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;stepSize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. Project Ray to Screen UVs&lt;/span&gt;
        &lt;span class="n"&gt;float4&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ViewProj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rayPos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
        &lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;rayUV&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;xy&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="c1"&gt;// ... transform to [0,1] ...&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Check the Depth Buffer&lt;/span&gt;
        &lt;span class="c1"&gt;// If the ray is BEHIND something in the Depth Buffer, it's occluded!&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;bufferDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;LinearizeDepth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_Depth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SampleLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_Sampler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rayUV&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;rayDepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;clipPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rayDepth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bufferDepth&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rayDepth&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;bufferDepth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;Thickness&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; 
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// HIT! We are in shadow.&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// No hit, fully lit.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By combining these two techniques—Shadow Mapping for large global shadows and SSCS for tiny grounding details—the 2D sprites feel physically present in the 3D world.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Cinematic Lens: Depth of Field
&lt;/h3&gt;

&lt;p&gt;The miniature or tilt-shift look is one of the most important parts of the HD-2D style. It makes the scene feel like a small toy world, not a life-size environment.&lt;/p&gt;

&lt;p&gt;Research and Design Choices:&lt;/p&gt;

&lt;p&gt;I explored different Depth of Field (DoF) techniques that work in real time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Gaussian blur was not suitable because it looks soft and fake.&lt;/li&gt;
&lt;li&gt;Instead, I focused on Bokeh Depth of Field, using a technique called Scatter-as-Gather.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This approach allows bright lights in the background to blur into the shape of the camera aperture (such as circles or hexagons). This effect is essential for achieving the HD-2D cinematic look.&lt;/p&gt;

&lt;p&gt;I added a post-processing step that calculates how blurry each pixel should be. This is done using the Circle of Confusion (CoC), which is based on the depth stored in the G-Buffer.&lt;/p&gt;

&lt;p&gt;The formula for the Circle of Confusion (CoC) is given by: &lt;br&gt;


&lt;span class="katex-element"&gt;
  &lt;span class="katex"&gt;&lt;span class="katex-mathml"&gt;CoC=A⋅f⋅∣P−z∣z⋅(P−f)CoC = A \cdot \frac{f \cdot |P - z|}{z \cdot (P - f)} &lt;/span&gt;&lt;span class="katex-html"&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;C&lt;/span&gt;&lt;span class="mord mathnormal"&gt;o&lt;/span&gt;&lt;span class="mord mathnormal"&gt;C&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mrel"&gt;=&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord mathnormal"&gt;A&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;span class="mbin"&gt;⋅&lt;/span&gt;&lt;span class="mspace"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="base"&gt;&lt;span class="strut"&gt;&lt;/span&gt;&lt;span class="mord"&gt;&lt;span class="mopen nulldelimiter"&gt;&lt;/span&gt;&lt;span class="mfrac"&gt;&lt;span class="vlist-t vlist-t2"&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;z&lt;/span&gt;&lt;span class="mbin mtight"&gt;⋅&lt;/span&gt;&lt;span class="mopen mtight"&gt;(&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;P&lt;/span&gt;&lt;span class="mbin mtight"&gt;−&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;f&lt;/span&gt;&lt;span class="mclose mtight"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="frac-line"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span class="pstrut"&gt;&lt;/span&gt;&lt;span class="sizing reset-size6 size3 mtight"&gt;&lt;span class="mord mtight"&gt;&lt;span class="mord mathnormal mtight"&gt;f&lt;/span&gt;&lt;span class="mbin mtight"&gt;⋅&lt;/span&gt;&lt;span class="mord mtight"&gt;∣&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;P&lt;/span&gt;&lt;span class="mbin mtight"&gt;−&lt;/span&gt;&lt;span class="mord mathnormal mtight"&gt;z&lt;/span&gt;&lt;span class="mord mtight"&gt;∣&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-s"&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span class="vlist-r"&gt;&lt;span class="vlist"&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="mclose nulldelimiter"&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;/span&gt;
&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;z is the pixel depth&lt;/li&gt;
&lt;li&gt;P is the focus plane&lt;/li&gt;
&lt;li&gt;f is the focal length&lt;/li&gt;
&lt;li&gt;A controls the aperture size&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Implementation Detail of the "Scatter-as-Gather" Depth Of Field:
&lt;/h4&gt;

&lt;p&gt;As I said, my research indicated that a simple Gaussian Blur looks too "foggy". For the cinematic look, we need Bokeh, where bright points of light expand into visible shapes (circles/hexagons).&lt;/p&gt;

&lt;p&gt;I implemented a Golden Angle Spiral kernel. Instead of grid sampling (which looks blocky), I generate sample points in a spiral pattern on the fly. This creates a natural, organic blur.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Solving the "Ghosting" Problem:&lt;/em&gt; With only 64 samples, a static spiral pattern creates visible bands ("ghosting"). To fix this, I implemented Interleaved Gradient Noise to rotate the spiral randomly for every pixel. This trades banding for high-frequency noise, which looks like film grain—much more acceptable for a cinematic aesthetic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;//DoF_ps.hlsl&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Generate Noise based on Screen Position&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InterleavedGradientNoise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UV&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ScreenRes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Create Random Rotation Matrix&lt;/span&gt;
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;theta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;noise&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;28&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;theta&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;float2x2&lt;/span&gt; &lt;span class="n"&gt;rotation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float2x2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// 3. Golden Angle Spiral Loop (64 Samples)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Generate Spiral Offset&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;phi&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;39996&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Golden Angle&lt;/span&gt;
    &lt;span class="n"&gt;float2&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;float2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;cos&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;sin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;phi&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="c1"&gt;// Rotate the kernel randomly per-pixel to hide artifacts&lt;/span&gt;
    &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mul&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Sample Scene Color&lt;/span&gt;
    &lt;span class="n"&gt;accumColor&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;g_SceneColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;g_Sampler&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UV&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Handling Foreground and Background Issues: &lt;br&gt;
One common problem with DoF is color bleeding, where blurry background colors leak onto sharp foreground objects.&lt;br&gt;
To fix this, I used a depth-weighted sampling kernel. This ensures that background blur does not affect nearby focused sprites, keeping characters sharp while the background stays soft and cinematic.&lt;/p&gt;

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

&lt;p&gt;Since I am using PBR lighting, my light values can easily exceed 1.0 (HDR). If I just output this to the screen, bright colors get clamped to ugly flat white.&lt;/p&gt;

&lt;p&gt;To fix this, I implemented the ACES Filmic Tone Mapping curve. This mathematically compresses the High Dynamic Range (HDR) values into the visible monitor range (LDR), preserving detail in bright highlights (like fire and magic effects) and contrasting shadows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="c1"&gt;// final shader pass&lt;/span&gt;

&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="nf"&gt;ACESFilm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Standard ACES fitted curve&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;51&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mo"&gt;03&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;float&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;clamp&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ... In Main Shader ...&lt;/span&gt;
&lt;span class="n"&gt;float3&lt;/span&gt; &lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;finalHDRColor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rgb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Exposure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;mapped&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ACESFilm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;float4&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mapped&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By combining the Golden Angle Bokeh with ACES Tone Mapping, the engine transforms the raw, jagged render into a soft, cohesive image that mimics real photography.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Reflections and Future Work
&lt;/h4&gt;

&lt;p&gt;While I successfully implemented the core pillars of the HD-2D look (Deferred Lighting, Normal-Mapped Sprites, Shadows, and DoF), the scope of a block project meant some advanced features remains as future work:&lt;/p&gt;

&lt;p&gt;Volumetric Fog: A key component of Octopath's atmosphere is volumetric god-rays. Currently, my engine lacks a volumetric rendering pass.&lt;/p&gt;

&lt;p&gt;Translucency Sorting: My Deferred pipeline relies on alpha testing (cutout). Implementing true translucency (for glass or water) would require a separate Forward pass or "Separate Translucency" buffer, which was out of scope for this block.&lt;/p&gt;

&lt;h3&gt;
  
  
  References &amp;amp; Further Reading
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;On HD-2D Architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  [1] &lt;strong&gt;Square Enix &amp;amp; Epic Games.&lt;/strong&gt; &lt;em&gt;"The Fusion of Nostalgia and Novelty in the Development of Octopath Traveler."&lt;/em&gt; Unreal Fest Europe 2019. &lt;a href="https://www.unrealengine.com/en-US/events/unreal-fest-europe-2019/the-fusion-of-nostalgia-and-novelty-in-the-development-of-octopath-traveler" rel="noopener noreferrer"&gt;Watch Presentation&lt;/a&gt; (Primary inspiration for the Deferred Rendering choice).&lt;/li&gt;
&lt;li&gt;  [2] &lt;strong&gt;/ 3DGEP.&lt;/strong&gt; &lt;em&gt;"Forward+ Rendering."&lt;/em&gt; &lt;a href="https://www.3dgep.com/forward-plus/" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt; (Used for the architectural trade-off analysis).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On Shadows &amp;amp; Integration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  [3] &lt;strong&gt;Epic Games.&lt;/strong&gt; &lt;em&gt;"Proxy Geometry Shadows in Unreal Engine."&lt;/em&gt; Unreal Engine Documentation. &lt;a href="https://dev.epicgames.com/documentation/en-us/unreal-engine/proxy-geometry-shadows-in-unreal-engine" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt; (Research on shadow proxies).&lt;/li&gt;
&lt;li&gt;  [4] &lt;strong&gt;Panos Karabelas.&lt;/strong&gt; &lt;em&gt;"Screen Space Shadows."&lt;/em&gt; &lt;a href="https://panoskarabelas.com/posts/screen_space_shadows/" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt; (The mathematical basis for the Contact Shadows implementation).&lt;/li&gt;
&lt;li&gt;  [5] &lt;strong&gt;Unity Technologies.&lt;/strong&gt; &lt;em&gt;"Forward and Deferred Rendering in HDRP."&lt;/em&gt; &lt;a href="https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@14.0/manual/Forward-And-Deferred-Rendering.html" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;On Post-Processing (DoF &amp;amp; Tone Mapping):&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  [6] &lt;strong&gt;Earl Hammon, Jr.&lt;/strong&gt; &lt;em&gt;"Practical Post-Process Depth of Field."&lt;/em&gt; GPU Gems 3, Chapter 28. &lt;a href="https://developer.nvidia.com/gpugems/gpugems3/part-iv-image-effects/chapter-28-practical-post-process-depth-field" rel="noopener noreferrer"&gt;Read Paper&lt;/a&gt; (The foundational algorithm for "Scatter-as-Gather" Bokeh).&lt;/li&gt;
&lt;li&gt;  [7] &lt;strong&gt;The Realm of MJP.&lt;/strong&gt; &lt;em&gt;"Bokeh."&lt;/em&gt; &lt;a href="https://therealmjp.github.io/posts/bokeh/" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt; (In-depth analysis of physical lens simulation).&lt;/li&gt;
&lt;li&gt;  [8] &lt;strong&gt;Krzysztof Narkowicz.&lt;/strong&gt; &lt;em&gt;"ACES Filmic Tone Mapping Curve."&lt;/em&gt; &lt;a href="https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/" rel="noopener noreferrer"&gt;Read Article&lt;/a&gt; (Source for the tone-mapping math used in the shader).&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>d3d12</category>
      <category>hd2d</category>
      <category>graphics</category>
    </item>
    <item>
      <title>Do Colleges teach Programming properly?</title>
      <dc:creator>Gaurav De</dc:creator>
      <pubDate>Fri, 10 Sep 2021 11:09:18 +0000</pubDate>
      <link>https://dev.to/gaurav_de/do-colleges-teach-programming-properly-44al</link>
      <guid>https://dev.to/gaurav_de/do-colleges-teach-programming-properly-44al</guid>
      <description>&lt;p&gt;Whether colleges truly teach programming properly or not is a subjective matter. I would like to have my take on this. Several key factors play a role, to shape up a student in his/ her programming journey. These factors can include stuff like how much rating the college has, what sort of professors do the college has, and lastly, the type of curriculum &amp;amp; teaching methods the college undertakes. &lt;/p&gt;

&lt;p&gt;Talking from my own personal experience, I had to face several hurdles and invest a lot of time to learn programming on my own from the Internet as opposed to relying on the teachings from college. Our college follows a very classic teaching style in India which emphasizes more on mugging up and theories as opposed to practical classes. Even during practical classes we would be sitting on our chairs and writing 'notes' on our 'exercise books' by looking at the board as opposed to actually typing some code on the computer. Although, I must admit that it is true that we were taught everything from the very basics; starting from the syntax to basic programming language like C to concepts on data structures, but despite learning so much, I truly didn't feel that I  learned something productive which I could actually use in real life to build something.&lt;/p&gt;

&lt;p&gt;The thing is, most of these Colleges don't really teach us 'Computer Science'. A person who truly understands Computer Science would be able to make productive usage of the Software available to him, and 'build' something which could benefit the society. In order to build that piece of software, knowing some syntaxes of a programming language and some data structures is not enough. What really needs to be taught are code 'frameworks' instead of the language itself. If you learn the framework then you will automatically be able to use the language.&lt;/p&gt;

&lt;p&gt;After investing some time in the past few weeks, to learn JavaScript and React to make actual 'projects'; it came to my realization that how important project based learning truly is. To add to that, learning to use a powerful tool like GitHub for the first time made me sit and think if the college education is even needed at all or not. After learning to use Git , I can't even fathom the fact that the Colleges don't teach something so basic yet so important to the Students which in turn would inevidently help them in future as it is used everywhere in the industry.&lt;/p&gt;

&lt;p&gt;So to conclude my short blog, I would like to say it again that yes , colleges indeed don't teach programming properly (for the most part). To change this, there needs to be a change in the curriculum and the way it is taught. There is absolutely no reason to have theory classes or at least 80% of it should be non-theory. Students should learn to code by 'typing' it themselves; and instead of focusing on 'learning C' or 'learning Java' , they should focus on learning frameworks, and build projects. They will understand things on their own.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>How I started my coding journey</title>
      <dc:creator>Gaurav De</dc:creator>
      <pubDate>Mon, 06 Sep 2021 01:15:04 +0000</pubDate>
      <link>https://dev.to/gaurav_de/how-i-started-my-coding-journey-25g8</link>
      <guid>https://dev.to/gaurav_de/how-i-started-my-coding-journey-25g8</guid>
      <description>&lt;p&gt;Ever since I was very young, I was always into computers. I had always wondered how these things worked, it seemed as though if it were magic. Back in my school days, I was always very excited for the days when we would be having computer classes. Especially the practical ones, because that was the only time when I could actually use a computer since we had none at home back in the day. We were taught most of the basics about computers at school, and with each passing day my passion to know more about them grew.&lt;/p&gt;

&lt;p&gt;When I had graduated from high school, I had made up my mind to enroll into a college course where I could learn about programming So I ended up enrolling into a program on Computer Applications. Throughout my college life, I learned about a lot of things starting from the basics of programming where we were introduced about the C language to stuff related to data structures, operating systems, databases , and so on.&lt;/p&gt;

&lt;p&gt;Although I learned about so many things, I couldn't still fixate on my mind about what I really wanted to do. Because, computer science is an absolutely vast field. You can do thousands of things starting from developing web apps, to mobile apps, and thousands of other things. &lt;/p&gt;

&lt;p&gt;I had always been a fanatic about games. I always enjoyed playing computer games. So it came up to my mind that I could be developing these games myself that I enjoy playing, so I decided to give it a try. After a bit of research I came to know about game engines that already come with an entirely pre-made framework for us so that we can focus on programming the actual game. So I ended up learning C# and the Unity Game Engine. I did end up making a few basic games which you can play here:&lt;/p&gt;

&lt;p&gt;[Link] &lt;a href="https://sharemygame.com/@Manoyal/aero-blast" rel="noopener noreferrer"&gt;https://sharemygame.com/@Manoyal/aero-blast&lt;/a&gt;&lt;br&gt;
[Link] &lt;a href="https://sharemygame.com/@Manoyal/blockanoid3x" rel="noopener noreferrer"&gt;https://sharemygame.com/@Manoyal/blockanoid3x&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now, if I jump back to the present, then it came to my mind that I should have a portfolio to showcase all of my projects. So I decided to properly learn about web development and truly fell in love with it. Previously I had always relied on things like WordPress but when I actually dove into the realm of Html, CSS and JavaScript I realized how fun it was.&lt;/p&gt;

&lt;p&gt;So I did end up making a simple portfolio website for myself which you can check out here:&lt;br&gt;
[Link] &lt;a href="https://gaurav-de.netlify.app/" rel="noopener noreferrer"&gt;https://gaurav-de.netlify.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For now, I feel I will explore web development a bit more, and put game development to the side as a 'hobby'. I am looking forward to learning more along this journey!&lt;/p&gt;

&lt;p&gt;-Thanks for reading till the end of my first blog.&lt;/p&gt;

</description>
      <category>codenewbie</category>
      <category>programming</category>
      <category>webdev</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
