<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Computer Graphics on jairrs</title><link>https://jairsantana.com/tags/computer-graphics/</link><description>Recent content in Computer Graphics on jairrs</description><generator>Hugo -- 0.156.0</generator><language>en-us</language><lastBuildDate>Sat, 28 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://jairsantana.com/tags/computer-graphics/index.xml" rel="self" type="application/rss+xml"/><item><title>Emulating Ghost of Tsushima's Grass in Godot</title><link>https://jairsantana.com/posts/grass/</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><guid>https://jairsantana.com/posts/grass/</guid><description>Procedural grass generation, noise-driven wind and clumping, and custom lighting in Godot.</description><content:encoded><![CDATA[<p>For this project, I tried to recreate the feel of Ghost of Tsushima&rsquo;s procedural grass in Godot, following their 2021 Advanced Graphics Summit talk, <em>Procedural Grass in &lsquo;Ghost of Tsushima&rsquo;</em>. This was my final project for MIT&rsquo;s 6.4400 Computer Graphics course.</p>
<div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;">
      <iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/MAErwFxp7_Y?autoplay=0&amp;controls=1&amp;end=0&amp;loop=0&amp;mute=0&amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"></iframe>
    </div>

<p>Here&rsquo;s a twenty-one second demo of an open field at a density of 0.4 (more on what that means later). For this scene, I used a terrain heightmap generated from simplex noise.</p>
<h2 id="introduction">Introduction</h2>
<p>The 2021 Advanced Graphics Summit talk <em>Procedural Grass in &lsquo;Ghost of Tsushima&rsquo;</em> presents an efficient and beautiful implementation of procedural grass that supports the game&rsquo;s artistic vision while running within the PS4&rsquo;s performance budget.</p>
<p>My goal was to preserve the feel and appearance of the game&rsquo;s procedural grass without the more sophisticated artist authored assets and wind system. Instead, I use a scrolling Perlin noise texture for wind strength and orientation samples, a cellular noise texture to simulate clumping of nearby blade meshes, rounding of normals in the vertex shader, and albedo interpolation based on the height of the fragment with respect to the blade mesh. To tie it all together, a custom lighting pass simulates ambient bounces and occlusion, as well as blending of blade colors in the distance.</p>
<p>The approach comes down to three main tricks:</p>
<ul>
<li>Use GPU Instancing to draw chunks of grass blades together, avoiding millions of separate draw calls.</li>
<li>Use moving noise patterns for wind and clusters of similar values to simulate grass clumping.</li>
<li>Use color and lighting to make flat blade meshes look rounded and give the field depth with ambient occlusion.</li>
</ul>
<p><details class="article-details">
    <summary>How Ghost of Tsushima&#39;s system works</summary>
    <div class="article-details-content">
        <p>The game world is tiled, with each tile containing an instance of multiple grass blade meshes via GPU instancing, using a single draw call to instantiate all instances of the blade mesh within the tile. Every blade of grass bends with respect to an artist authored bezier curve and has a unique random offset to its position, orientation, scale, and bend. Then, the wind strength and direction at the blade&rsquo;s position is sampled from the game&rsquo;s wind system to modify the blade&rsquo;s bend and orientation. They also implemented a &ldquo;clumping&rdquo; system where nearby grass blades belonging to the same &ldquo;clump&rdquo; obtain similar random properties for all of the above parameters. Each grass blade &ldquo;type&rdquo; is artist authored and includes separate vein and stem albedo, gloss, and normal textures which round the edges of the grass blade to give it a more realistic appearance. For performance, the blade meshes are assigned one of two LOD models and are view frustum culled.</p>

    </div>
</details>

<details class="article-details">
    <summary>Godot setup</summary>
    <div class="article-details-content">
        <p>I built this in Godot 4.5, using its graphics pipeline and FastNoiseLite library. The setup is pretty simple: a light source and camera attached to the root node, a script, and a material asset for the grass shader.</p>

    </div>
</details>
</p>
<h2 id="drawing-millions-of-blades">Drawing Millions of Blades</h2>
<p>The world map is divided into tiles of a specified tile size. Using a density parameter <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mo>∈</mo><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">d \in (0,1]</annotation></semantics></math></span>, we can prepare <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mrow><mi mathvariant="normal">r</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">w</mi></mrow><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">\mathrm{row}^2</annotation></semantics></math></span> evenly spaced grass blade meshes in a grid formation, where <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mi mathvariant="normal">r</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">w</mi></mrow><mo>=</mo><msub><mrow><mi mathvariant="normal">s</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">z</mi><mi mathvariant="normal">e</mi></mrow><mi>t</mi></msub><mo>×</mo><mn>10</mn><mi>d</mi></mrow><annotation encoding="application/x-tex"> \mathrm{row} = \mathrm{size}_t \times 10d </annotation></semantics></math></span>. Here, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi mathvariant="normal">s</mi><mi mathvariant="normal">i</mi><mi mathvariant="normal">z</mi><mi mathvariant="normal">e</mi></mrow><mi>t</mi></msub></mrow><annotation encoding="application/x-tex">\mathrm{size}_t</annotation></semantics></math></span> is the side length of a square tile and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">r</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">w</mi></mrow><annotation encoding="application/x-tex">\mathrm{row}</annotation></semantics></math></span> is the number of blades along each side. This gives us an easy way to modify how many grass blades we want in each tile by shifting <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span>.
Each tile is instantiated using Godot&rsquo;s MultiMesh class, which draws all its grass blade meshes together. This matters a lot for performance, as we&rsquo;d otherwise need a separate draw call for each of the millions of grass blades.</p>
<p><details class="article-details">
    <summary>What are meshes, GPU instancing, and draw calls?</summary>
    <div class="article-details-content">
        <p>A mesh is the shape of a 3D object, built from points (vertices) connected into triangles. Here, it&rsquo;s a single blade of grass. A draw call is a command telling the graphics processor to draw something. GPU instancing uses one single draw call to draw the same shape many times with different positions, sizes, and rotations.</p>

    </div>
</details>

<figure>
    <img loading="lazy" src="/images/grass/mesh-uv.png"
         alt="Grass blade mesh and its UV map in Blender"/> <figcaption>
            <p>One simple blade is repeated across the field. On the right, the UV map tells the shader where a point sits on the blade.</p>
        </figcaption>
</figure>
</p>
<p>The simple UV map lets us use the <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>u</mi><mi>v</mi></mrow><annotation encoding="application/x-tex">uv</annotation></semantics></math></span> coordinates as the relative width and height of the grass blade in the shaders later on.</p>
<h2 id="making-the-grass-move-and-clump">Making the Grass Move and Clump</h2>
<p>Using a perlin noise texture and a wind speed, we can create the illusion of unique grass blades which appear to flow and bend with the wind. We can use another noise texture to simulate grass clumping, a natural phenomenon where nearby blades of grass will exhibit similar properties. Without it, we&rsquo;d get grass of random heights all over the place which looks unnatural. I implement both of these things using a vertex and a fragment shader.</p>
<details class="article-details">
    <summary>What are vertex and fragment shaders?</summary>
    <div class="article-details-content">
        <p>A shader is a small program that runs on the graphics processor. A vertex shader moves the points that make up each blade, so it handles bending and rotation. A fragment shader decides what color the blade&rsquo;s surface should be at each visible point.</p>

    </div>
</details>

<details class="article-details">
    <summary>What is a noise texture?</summary>
    <div class="article-details-content">
        <p>A noise texture is a texture with randomized values. Each kind of noise texture has useful properties which vary depending on the generation technique. Making every pixel a random (0, 1] value is often referred to as static or white noise, for example.</p>

    </div>
</details>

<h3 id="bending-in-the-wind">Bending in the Wind</h3>
<p>To emulate wind we can &ldquo;scroll&rdquo; a wind texture across the field of grass. We effectively map the texture to the entire grid by always sampling relative to each blade&rsquo;s position in the shader. To achieve scrolling, we add a time offset to the sampling position. This essentially sweeps the same wind strength values across a direction, achieving the illusion of a continuous gust of wind.</p>
<p>I use Perlin noise for the scrolling wind texture because its values change smoothly: nearby values are similar. This means neighboring blades get similar wind strength and direction, so the grass moves together instead of each blade jittering as we scroll the wind texture. Feel free to experiment with other kinds of noise. I generated the texture with Godot&rsquo;s FastNoiseLite library. I enabled domain warping for a more interesting wind pattern and made the texture seamless to avoid strange patterns when scrolling across the edges.</p>
<p>I sample the Perlin noise texture at the same position for the wind&rsquo;s target orientation and rotation strength, then at another position for bending. The bending sample scrolls faster and uses a height-based time offset, so points farther along the blade sample a bit ahead in time, emulating turbulence.</p>
<figure class="grass-demo" data-grass-demo="wind">
    <div class="grass-demo-labels" aria-hidden="true">
    <span>One tile · shared wind samples</span>
    </div>
    <canvas class="grass-demo-scene" aria-label="3D wind scrolling demonstration" role="img"></canvas>
    
    <div class="grass-demo-noise"><canvas aria-label="Noise texture; dots mark blade sampling positions" role="img"></canvas><span>Wind texture · dots mark the same blade positions</span></div>
    
    <label class="grass-demo-control"><span>Scroll wind texture</span><input type="range" min="0" max="100" value="0" aria-label="Wind texture scroll"><output></output></label>
    
    <p class="grass-demo-hint">Move the slider: nearby blades sample similar values and bend together.</p>
    <p class="grass-demo-status" role="status"></p>
    <figcaption>A simplified tile of grass sampling a seamless Perlin wind texture. Use the slider to scroll the texture and watch the blades respond.</figcaption>
    <p class="grass-demo-disclaimer">This simulation was generated from the project source code using AI.</p>
</figure>

<details class="article-details">
    <summary>Technical details: wind, rotation, and bending</summary>
    <div class="article-details-content">
        <p>I implement all of this in the vertex shader.</p>
<p>I sample the Perlin noise texture (or &ldquo;wind texture&rdquo;) at coordinates <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>s</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">s_0</annotation></semantics></math></span>, using elapsed time <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi></mrow><annotation encoding="application/x-tex">t</annotation></semantics></math></span> in seconds, wind speed <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>s</mi></msub></mrow><annotation encoding="application/x-tex">w_s</annotation></semantics></math></span> (default <code>1.0</code>), and the blade&rsquo;s ground-plane position <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi mathvariant="normal">p</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">s</mi></mrow><mrow><mi mathvariant="normal">w</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">l</mi><mi mathvariant="normal">d</mi></mrow></msub></mrow><annotation encoding="application/x-tex">\mathrm{pos}_{\mathrm{world}}</annotation></semantics></math></span> (world-space x and z). The constant <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>c</mi><mi>d</mi></msub></mrow><annotation encoding="application/x-tex">c_d</annotation></semantics></math></span> scales the sampling coordinates:</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>s</mi><mn>0</mn></msub><mo>=</mo><msub><mi>c</mi><mi>d</mi></msub><mrow><mo fence="true">(</mo><msub><mi>w</mi><mi>s</mi></msub><mo stretchy="false">(</mo><mi>t</mi><mo separator="true">,</mo><mi>t</mi><mo stretchy="false">)</mo><mo>+</mo><mfrac><msub><mrow><mi mathvariant="normal">p</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">s</mi></mrow><mrow><mi mathvariant="normal">w</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">l</mi><mi mathvariant="normal">d</mi></mrow></msub><msub><mi>w</mi><mi>s</mi></msub></mfrac><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">
s_0 = c_d\left(w_s(t,t)+\frac{\mathrm{pos}_{\mathrm{world}}}{w_s}\right)
</annotation></semantics></math></span><p>I multiply the sampled value by <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>π</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow><annotation encoding="application/x-tex">\pi/2</annotation></semantics></math></span> to get the wind angle <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>θ</mi></msub></mrow><annotation encoding="application/x-tex">w_\theta</annotation></semantics></math></span>. Increasing <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>s</mi></msub></mrow><annotation encoding="application/x-tex">w_s</annotation></semantics></math></span> makes the wind texture scroll faster. I used <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0.005</mn></mrow><annotation encoding="application/x-tex">0.005</annotation></semantics></math></span> for <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>c</mi><mi>d</mi></msub></mrow><annotation encoding="application/x-tex">c_d</annotation></semantics></math></span>.
I compute the blade&rsquo;s initial rotation angle <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>θ</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">\theta_0</annotation></semantics></math></span> using its repeatable position-based hash <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>p</mi></msub></mrow><annotation encoding="application/x-tex">x_p</annotation></semantics></math></span> and the sampled clump value <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">x_c</annotation></semantics></math></span>, both in <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0,1]</annotation></semantics></math></span>. Here, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>τ</mi><mo>=</mo><mn>2</mn><mi>π</mi></mrow><annotation encoding="application/x-tex">\tau=2\pi</annotation></semantics></math></span> is a full turn in radians:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>θ</mi><mn>0</mn></msub><mo>=</mo><mrow><mo fence="true">(</mo><mfrac><mn>1</mn><mn>3</mn></mfrac><msub><mi>x</mi><mi>c</mi></msub><mo>+</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mo>−</mo><mn>0.15</mn><mo separator="true">,</mo><mn>0.15</mn><mo separator="true">,</mo><msub><mi>x</mi><mi>p</mi></msub><mo stretchy="false">)</mo><mo fence="true">)</mo></mrow><mi>τ</mi></mrow><annotation encoding="application/x-tex">
\theta_0 = \left(\frac{1}{3}x_c+\operatorname{mix}(-0.15,0.15,x_p)\right)\tau
</annotation></semantics></math></span><p>
I also sample a wind strength <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>f</mi></msub></mrow><annotation encoding="application/x-tex">w_f</annotation></semantics></math></span> from the wind texture at <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>s</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">s_0</annotation></semantics></math></span> and multiply it by a factor of <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>4</mn><mi mathvariant="normal">/</mi><mn>5</mn></mrow><annotation encoding="application/x-tex">4/5</annotation></semantics></math></span>. I blend these into the final rotation angle <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>θ</mi></mrow><annotation encoding="application/x-tex">\theta</annotation></semantics></math></span>, using <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>a</mi><mo separator="true">,</mo><mi>b</mi><mo separator="true">,</mo><mi>u</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>u</mi><mo stretchy="false">)</mo><mi>a</mi><mo>+</mo><mi>u</mi><mi>b</mi></mrow><annotation encoding="application/x-tex">\operatorname{mix}(a,b,u)=(1-u)a+ub</annotation></semantics></math></span>:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>θ</mi><mo>=</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><msub><mi>θ</mi><mn>0</mn></msub><mo separator="true">,</mo><msub><mi>w</mi><mi>θ</mi></msub><mo separator="true">,</mo><msub><mi>w</mi><mi>f</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">
\theta = \operatorname{mix}(\theta_0,w_\theta,w_f)
</annotation></semantics></math></span><p>
This angle is used to generate a rotation matrix about the y-axis <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>M</mi><mi>r</mi></msub></mrow><annotation encoding="application/x-tex">M_r</annotation></semantics></math></span>.</p>
<p>I use the relative height of the vertex <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>=</mo><mn>1</mn><mo>−</mo><msub><mrow><mi mathvariant="normal">U</mi><mi mathvariant="normal">V</mi></mrow><mi>y</mi></msub></mrow><annotation encoding="application/x-tex">h=1-\mathrm{UV}_y</annotation></semantics></math></span>, from <code>0</code> at the base to <code>1</code> at the tip, to compute an initial bend angle <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>β</mi><mn>0</mn></msub></mrow><annotation encoding="application/x-tex">\beta_0</annotation></semantics></math></span>. The variation value <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>c</mi><mo stretchy="false">)</mo><msub><mi>x</mi><mi>p</mi></msub><mo>+</mo><mi>c</mi><msub><mi>x</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">x=(1-c)x_p+cx_c</annotation></semantics></math></span> combines the blade hash and clump value with clumping weight <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mo>=</mo><mn>0.8</mn></mrow><annotation encoding="application/x-tex">c=0.8</annotation></semantics></math></span>:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>β</mi><mn>0</mn></msub><mo>=</mo><mi>h</mi><mi>π</mi><mtext> </mtext><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>0.10</mn><mo separator="true">,</mo><mn>0.2</mn><mi>x</mi><mo separator="true">,</mo><mi>h</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">
\beta_0 = h\pi\,\operatorname{mix}(0.10,0.2x,h)
</annotation></semantics></math></span><p>
I then sample the wind texture at a second set of coordinates <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>s</mi><mn>1</mn></msub></mrow><annotation encoding="application/x-tex">s_1</annotation></semantics></math></span>. Here, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>t</mi><mi>h</mi></msub><mo>=</mo><mi>t</mi><mo>+</mo><mn>0.25</mn><msup><mi>h</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">t_h=t+0.25h^2</annotation></semantics></math></span> offsets the time farther along the blade. The temporal sampling factor is <code>0.05</code>, while the spatial factor remains <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>c</mi><mi>d</mi></msub><mo>=</mo><mn>0.005</mn></mrow><annotation encoding="application/x-tex">c_d=0.005</annotation></semantics></math></span>:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>s</mi><mn>1</mn></msub><mo>=</mo><mn>0.05</mn><mo stretchy="false">(</mo><msub><mi>t</mi><mi>h</mi></msub><mo separator="true">,</mo><msub><mi>t</mi><mi>h</mi></msub><mo stretchy="false">)</mo><mo>+</mo><msub><mi>c</mi><mi>d</mi></msub><mfrac><msub><mrow><mi mathvariant="normal">p</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">s</mi></mrow><mrow><mi mathvariant="normal">w</mi><mi mathvariant="normal">o</mi><mi mathvariant="normal">r</mi><mi mathvariant="normal">l</mi><mi mathvariant="normal">d</mi></mrow></msub><msub><mi>w</mi><mi>s</mi></msub></mfrac></mrow><annotation encoding="application/x-tex">
s_1 = 0.05(t_h,t_h)+c_d\frac{\mathrm{pos}_{\mathrm{world}}}{w_s}
</annotation></semantics></math></span><p>
I call the value sampled here <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>b</mi></msub></mrow><annotation encoding="application/x-tex">w_b</annotation></semantics></math></span>. I remap it from <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0,1]</annotation></semantics></math></span> to <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0.25</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0.25,1]</annotation></semantics></math></span> and square it, then multiply by wind speed and a bend variation factor <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>q</mi></mrow><annotation encoding="application/x-tex">q</annotation></semantics></math></span>. That factor blends a fixed value with the blade&rsquo;s hash using the clump value:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>q</mi><mo>=</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>0.1</mn><mo separator="true">,</mo><mn>0.1</mn><mo>+</mo><mn>0.4</mn><msub><mi>x</mi><mi>p</mi></msub><mo separator="true">,</mo><msub><mi>x</mi><mi>c</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">
q = \operatorname{mix}(0.1,0.1+0.4x_p,x_c)
</annotation></semantics></math></span><p>
The remapped strength produces the final bend angle <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>β</mi></mrow><annotation encoding="application/x-tex">\beta</annotation></semantics></math></span>:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>β</mi><mo>=</mo><msub><mi>β</mi><mn>0</mn></msub><mo>+</mo><mi>π</mi><mtext> </mtext><msub><mi>w</mi><mi>s</mi></msub><mtext> </mtext><mi>q</mi><msup><mrow><mo fence="true">[</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>0.25</mn><mo separator="true">,</mo><mn>1.0</mn><mo separator="true">,</mo><msub><mi>w</mi><mi>b</mi></msub><mo stretchy="false">)</mo><mo fence="true">]</mo></mrow><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">
\beta = \beta_0+\pi\,w_s\,q\left[\operatorname{mix}(0.25,1.0,w_b)\right]^2
</annotation></semantics></math></span><p>
This matches the shader&rsquo;s <code>mix(0, PI, wind_strength_bend)</code>, since blending from zero to <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>π</mi></mrow><annotation encoding="application/x-tex">\pi</annotation></semantics></math></span> is just multiplication by <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>π</mi></mrow><annotation encoding="application/x-tex">\pi</annotation></semantics></math></span>. The angle generates a rotation matrix about the x-axis, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>M</mi><mi>b</mi></msub></mrow><annotation encoding="application/x-tex">M_b</annotation></semantics></math></span>. The height-dependent offset puts the blade tips ahead in the wind texture; the additional wind bend itself has no extra <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>h</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">h^2</annotation></semantics></math></span> multiplier.</p>
<p>Finally, I apply both rotation matrices to the vertex position <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">v</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{v}</annotation></semantics></math></span> and the normal:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold-italic">v</mi><mo>←</mo><msub><mi>M</mi><mi>r</mi></msub><msub><mi>M</mi><mi>b</mi></msub><mi mathvariant="bold-italic">v</mi></mrow><annotation encoding="application/x-tex">
\boldsymbol{v} \gets M_rM_b\boldsymbol{v}
</annotation></semantics></math></span><p>
to properly rotate and bend the blade mesh.</p>
<p>Unless otherwise mentioned, I picked the constants by experimenting and seeing what looked good.</p>

    </div>
</details>

<figure>
    <img loading="lazy" src="/images/grass/perlin-noise.png"
         alt="Grayscale Perlin noise texture used to drive wind"/> <figcaption>
            <p>Scrolling this pattern makes neighboring blades move together. Settings are in wind_noise.tres.</p>
        </figcaption>
</figure>

<h3 id="giving-each-blade-some-variation">Giving Each Blade Some Variation</h3>
<p>Individual blades are different from each other, but remain similar to nearby blades. I combine a value unique to each blade with a value from the clump texture, then use the result for width and height variation. By using a mix of the unique and clump values, we get groups of blades with similar heights and orientations (clump factor) that are still slightly different from each other.</p>
<p>I use cellular noise for the grass clump texture. Nearby values cluster together, with visible boundaries between groups of similar values. I generated this texture with FastNoiseLite too, using the default settings.</p>
<figure>
    <img loading="lazy" src="/images/grass/cellular-noise.png"
         alt="Grayscale cellular noise texture with clusters of similar values"/> <figcaption>
            <p>Clusters of similar gray values give neighboring blades similar sizes and shapes, creating clumps.</p>
        </figcaption>
</figure>

<figure class="grass-demo" data-grass-demo="clumping">
    <div class="grass-demo-labels" aria-hidden="true">
    <span>Independent random sizes</span><span>Clump-influenced sizes</span>
    </div>
    <canvas class="grass-demo-scene" aria-label="3D comparison of random and clumped grass sizes" role="img"></canvas>
    
    <div class="grass-demo-noise"><canvas aria-label="Noise texture; dots mark blade sampling positions" role="img"></canvas><span>Cellular texture · each region shares a size value</span></div>
    
    <label class="grass-demo-control"><span>Clumping weight</span><input type="range" min="0" max="100" value="80" aria-label="Clumping weight"><output></output></label>
    
    <p class="grass-demo-hint">At zero, both tiles match. Increase the weight to give neighboring blades similar sizes; their positions stay fixed.</p>
    <p class="grass-demo-status" role="status"></p>
    <figcaption>The left tile uses independent per-blade variation. The right blends those same values with a cellular texture, creating patches of similar blade sizes. Adjust the clumping weight to compare.</figcaption>
    <p class="grass-demo-disclaimer">This simulation was generated from the project source code using AI.</p>
</figure>

<details class="article-details">
    <summary>Technical details: blade appearance variation</summary>
    <div class="article-details-content">
        <p>I use the blade&rsquo;s world position to generate a hash value <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>p</mi></msub></mrow><annotation encoding="application/x-tex">x_p</annotation></semantics></math></span> in <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0,1]</annotation></semantics></math></span> and sample the cellular noise texture (or &ldquo;clump texture&rdquo;) to get a value <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>x</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">x_c</annotation></semantics></math></span> in <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">[</mo><mn>0</mn><mo separator="true">,</mo><mn>1</mn><mo stretchy="false">]</mo></mrow><annotation encoding="application/x-tex">[0,1]</annotation></semantics></math></span>. I combine these into a variation parameter <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span>, using the clumping weight <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi></mrow><annotation encoding="application/x-tex">c</annotation></semantics></math></span> (set to <code>0.8</code>). At <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">c=0</annotation></semantics></math></span>, each blade uses only its own random value; at <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>c</mi><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">c=1</annotation></semantics></math></span>, it uses only the clump texture:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>x</mi><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>c</mi><mo stretchy="false">)</mo><msub><mi>x</mi><mi>p</mi></msub><mo>+</mo><mi>c</mi><msub><mi>x</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">
x = (1-c)x_p + cx_c
</annotation></semantics></math></span><p>
that I use to scale the blade mesh&rsquo;s vertex. I scale the vertex&rsquo;s local horizontal coordinate <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>v</mi><mi>x</mi></msub></mrow><annotation encoding="application/x-tex">v_x</annotation></semantics></math></span> and vertical coordinate <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>v</mi><mi>y</mi></msub></mrow><annotation encoding="application/x-tex">v_y</annotation></semantics></math></span> with the following expressions. The function <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>a</mi><mo separator="true">,</mo><mi>b</mi><mo separator="true">,</mo><mi>u</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>u</mi><mo stretchy="false">)</mo><mi>a</mi><mo>+</mo><mi>u</mi><mi>b</mi></mrow><annotation encoding="application/x-tex">\operatorname{mix}(a,b,u)=(1-u)a+ub</annotation></semantics></math></span> blends between two values:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mtable rowspacing="0.25em" columnalign="right left" columnspacing="0em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>v</mi><mi>x</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>0.2</mn><mo separator="true">,</mo><mn>0.5</mn><mo separator="true">,</mo><mi>x</mi><mo stretchy="false">)</mo><mtext> </mtext><msub><mi>v</mi><mi>x</mi></msub></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><msub><mi>v</mi><mi>y</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow></mrow><mo>=</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mn>0.5</mn><mo separator="true">,</mo><mn>2.0</mn><mo separator="true">,</mo><mi>x</mi><mo stretchy="false">)</mo><mtext> </mtext><msub><mi>v</mi><mi>y</mi></msub></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">
\begin{aligned}v_x &amp;= \operatorname{mix}(0.2,0.5,x)\,v_x \\ v_y &amp;= \operatorname{mix}(0.5,2.0,x)\,v_y\end{aligned}
</annotation></semantics></math></span><p>
for width and height variation.</p>

    </div>
</details>

<h2 id="making-flat-blades-look-rounded">Making Flat Blades Look Rounded</h2>
<p>The grass blades are simple, flat meshes. We can artificially round them without adding extra geometry by adjusting the angle of normals close to the edge of the blade. By pointing these further outwards we get the illusion of curvature on the flat mesh.</p>
<p><figure class="grass-demo" data-grass-demo="normals">
    <div class="grass-demo-labels" aria-hidden="true">
    <span>Flat normals</span><span>Rounded normals</span>
    </div>
    <canvas class="grass-demo-scene" aria-label="3D comparison of flat and rounded normals" role="img"></canvas>
    
    <label class="grass-demo-control"><span>Normal curvature</span><input type="range" min="0" max="100" value="60" aria-label="Normal curvature"><output></output></label>
    
    <p class="grass-demo-hint">Both tiles use identical blades and lighting. Only the normals change.</p>
    <p class="grass-demo-status" role="status"></p>
    <figcaption>Identical flat blade meshes, with flat normals on the left and rounded normals on the right. Adjust the curvature to see how lighting changes without adding geometry.</figcaption>
    <p class="grass-demo-disclaimer">This simulation was generated from the project source code using AI.</p>
</figure>

<details class="article-details">
    <summary>What is a normal?</summary>
    <div class="article-details-content">
        <p>The normal vector at a point on a mesh is the vector perpendicular to the surface. Lighting uses it to work out how the surface faces the light. By varying that direction across a flat blade, we can make the lighting behave as if the blade were curved.</p>

    </div>
</details>

<details class="article-details">
    <summary>Technical details: rounded normals</summary>
    <div class="article-details-content">
        <p>In the vertex shader I compute leftward and rightward facing normals by rotating the normal about the y-axis by <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>c</mi><mi>n</mi></msub><mi>π</mi></mrow><annotation encoding="application/x-tex">c_n\pi</annotation></semantics></math></span> and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo>−</mo><msub><mi>c</mi><mi>n</mi></msub><mi>π</mi></mrow><annotation encoding="application/x-tex">-c_n\pi</annotation></semantics></math></span>, respectively. The curvature parameter <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>c</mi><mi>n</mi></msub></mrow><annotation encoding="application/x-tex">c_n</annotation></semantics></math></span> corresponds to <code>normal_curve_factor</code> in the shader, with a default of <code>0.3</code> and a range of <code>0</code> to <code>0.5</code>; multiplying by <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>π</mi></mrow><annotation encoding="application/x-tex">\pi</annotation></semantics></math></span> gives an angle in radians. I pass these to the fragment shader and interpolate between them using the fragment&rsquo;s horizontal coordinate <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mrow><mi mathvariant="normal">U</mi><mi mathvariant="normal">V</mi></mrow><mi>x</mi></msub></mrow><annotation encoding="application/x-tex">\mathrm{UV}_x</annotation></semantics></math></span>, which runs across the blade. This makes the flat blades look rounded.</p>

    </div>
</details>
</p>
<h2 id="coloring-the-grass">Coloring the Grass</h2>
<p>I interpolate between a base color and a tip color to color every blade of grass. I also color distant fragments closer to the average color to simulate blending at far distances.</p>
<p><figure>
    <img loading="lazy" src="/images/grass/base-tip-color.png"
         alt="Grass blade colors blended from base to tip"/> <figcaption>
            <p>Each blade transitions from its base color to its tip color.</p>
        </figcaption>
</figure>

<figure class="image-comparison">
    <div class="image-comparison-grid">
        <div>
            <img src="/images/grass/distance-blending-before.png" alt="Without distance blending" loading="lazy">
            <p class="image-comparison-label">Without distance blending</p>
        </div>
        <div>
            <img src="/images/grass/distance-blending-after.png" alt="With distance blending" loading="lazy">
            <p class="image-comparison-label">With distance blending</p>
        </div>
    </div>
    <figcaption>Left: individual colors stay distinct in the distance. Right: blending them makes the distant field look smoother.</figcaption>
</figure>
</p>
<details class="article-details">
    <summary>Technical details: fragment shader and color blending</summary>
    <div class="article-details-content">
        <p>The fragment shader uses the specified base color <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">b</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{b}</annotation></semantics></math></span> and tip color <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi mathvariant="bold-italic">b</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow><annotation encoding="application/x-tex">\boldsymbol{b}^{\prime}</annotation></semantics></math></span> to color every blade of grass. I also color distant fragments closer to the average color to simulate blending at far distances.</p>
<p>I set the albedo using relative height <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>=</mo><mn>1</mn><mo>−</mo><msub><mrow><mi mathvariant="normal">U</mi><mi mathvariant="normal">V</mi></mrow><mi>y</mi></msub></mrow><annotation encoding="application/x-tex">h=1-\mathrm{UV}_y</annotation></semantics></math></span>, from base (<code>0</code>) to tip (<code>1</code>). The function <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>a</mi><mo separator="true">,</mo><mi>b</mi><mo separator="true">,</mo><mi>u</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>u</mi><mo stretchy="false">)</mo><mi>a</mi><mo>+</mo><mi>u</mi><mi>b</mi></mrow><annotation encoding="application/x-tex">\operatorname{mix}(a,b,u)=(1-u)a+ub</annotation></semantics></math></span> blends between colors:
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">mix</mi><mo>⁡</mo><mrow><mo fence="true">(</mo><mfrac><mrow><mi mathvariant="bold-italic">b</mi><mo>+</mo><msup><mi mathvariant="bold-italic">b</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup></mrow><mn>4</mn></mfrac><mo separator="true">,</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi mathvariant="bold-italic">b</mi><mo separator="true">,</mo><msup><mi mathvariant="bold-italic">b</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo separator="true">,</mo><msup><mi>h</mi><mn>2</mn></msup><mo stretchy="false">)</mo><mo separator="true">,</mo><msup><mi>e</mi><mrow><mo>−</mo><mn>0.04</mn><mo stretchy="false">∥</mo><mi mathvariant="bold-italic">v</mi><mo stretchy="false">∥</mo></mrow></msup><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">
\operatorname{mix}\left(\frac{\boldsymbol{b}+\boldsymbol{b}^{\prime}}{4},\operatorname{mix}(\boldsymbol{b},\boldsymbol{b}^{\prime},h^2),e^{-0.04\lVert\boldsymbol{v}\rVert}\right)
</annotation></semantics></math></span><p>
Here, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">v</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{v}</annotation></semantics></math></span> is the fragment position in view space, so <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo stretchy="false">∥</mo><mi mathvariant="bold-italic">v</mi><mo stretchy="false">∥</mo></mrow><annotation encoding="application/x-tex">\lVert\boldsymbol{v}\rVert</annotation></semantics></math></span> is its distance from the camera. The constant <code>0.04</code> controls how quickly the distant color takes over, and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>e</mi></mrow><annotation encoding="application/x-tex">e</annotation></semantics></math></span> is the base of the natural exponential. The divisor <code>4</code> is the shader&rsquo;s chosen distant-color factor, rather than a literal average of the two colors.</p>

    </div>
</details>

<h2 id="giving-the-field-depth">Giving the Field Depth</h2>
<p>To tie it all together, a custom lighting pass simulates ambient bounces and occlusion. I darken the base of the grass based on the field&rsquo;s density, which gives the field more depth.</p>
<p><figure class="grass-demo" data-grass-demo="occlusion">
    <div class="grass-demo-labels" aria-hidden="true">
    <span>Without ambient occlusion</span><span>With ambient occlusion</span>
    </div>
    <canvas class="grass-demo-scene" aria-label="3D comparison of grass ambient occlusion" role="img"></canvas>
    
    <label class="grass-demo-control"><span>Grass density / occlusion</span><input type="range" min="0" max="100" value="60" aria-label="Ambient occlusion strength"><output></output></label>
    
    <p class="grass-demo-hint">Both tiles use identical blades and lighting. Only the base darkening changes.</p>
    <p class="grass-demo-status" role="status"></p>
    <figcaption>The same tile without ambient occlusion on the left and with density-based base darkening on the right. Move the slider to see how occlusion adds depth.</figcaption>
    <p class="grass-demo-disclaimer">This simulation was generated from the project source code using AI.</p>
</figure>

<details class="article-details">
    <summary>What is ambient occlusion?</summary>
    <div class="article-details-content">
        <p>Ambient occlusion approximates how nearby surfaces block light from reaching each other. The bases of densely packed grass get less light than the exposed tips. Here, I approximate that with the grass density and the height along the blade in the lighting function (<code>light()</code>).</p>

    </div>
</details>

<details class="article-details">
    <summary>Technical details: lighting pass</summary>
    <div class="article-details-content">
        <p>I emulate ambient occlusion using grass density <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi></mrow><annotation encoding="application/x-tex">d</annotation></semantics></math></span> and relative height <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>h</mi><mo>=</mo><mn>1</mn><mo>−</mo><msub><mrow><mi mathvariant="normal">U</mi><mi mathvariant="normal">V</mi></mrow><mi>y</mi></msub></mrow><annotation encoding="application/x-tex">h=1-\mathrm{UV}_y</annotation></semantics></math></span>, from base to tip. The occlusion factor <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>o</mi></mrow><annotation encoding="application/x-tex">o</annotation></semantics></math></span> darkens the base more as density increases. Using <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">mix</mi><mo>⁡</mo><mo stretchy="false">(</mo><mi>a</mi><mo separator="true">,</mo><mi>b</mi><mo separator="true">,</mo><mi>u</mi><mo stretchy="false">)</mo><mo>=</mo><mo stretchy="false">(</mo><mn>1</mn><mo>−</mo><mi>u</mi><mo stretchy="false">)</mo><mi>a</mi><mo>+</mo><mi>u</mi><mi>b</mi></mrow><annotation encoding="application/x-tex">\operatorname{mix}(a,b,u)=(1-u)a+ub</annotation></semantics></math></span>, I set it as
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>o</mi><mo>=</mo><mi mathvariant="normal">mix</mi><mo>⁡</mo><mrow><mo fence="true">(</mo><mn>1</mn><mo>−</mo><mfrac><mn>3</mn><mn>4</mn></mfrac><mi>d</mi><mo separator="true">,</mo><mn>1</mn><mo separator="true">,</mo><msup><mi>h</mi><mn>2</mn></msup><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">
o = \operatorname{mix}\left(1-\frac{3}{4}d,1,h^2\right)
</annotation></semantics></math></span><p>
I then set the diffuse lighting color to
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="bold-italic">α</mi><mtext> </mtext><mi>o</mi><mtext> </mtext><msub><mi mathvariant="bold-italic">l</mi><mi>c</mi></msub><mtext> </mtext><msup><mn>2</mn><mrow><mi mathvariant="bold-italic">n</mi><mo>⋅</mo><mi mathvariant="bold-italic">l</mi><mo>−</mo><mn>2</mn></mrow></msup></mrow><annotation encoding="application/x-tex">
\boldsymbol{\alpha}\,o\,\boldsymbol{l}_c\,2^{\boldsymbol{n}\cdot\boldsymbol{l}-2}
</annotation></semantics></math></span><p>
Here, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">α</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{\alpha}</annotation></semantics></math></span> is Godot&rsquo;s light attenuation (<code>ATTENUATION</code>), <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi mathvariant="bold-italic">l</mi><mi>c</mi></msub></mrow><annotation encoding="application/x-tex">\boldsymbol{l}_c</annotation></semantics></math></span> is the light color (<code>LIGHT_COLOR</code>), <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">n</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{n}</annotation></semantics></math></span> is the surface normal (<code>NORMAL</code>), and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">l</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{l}</annotation></semantics></math></span> is the direction toward the light (<code>LIGHT</code>). The dot product <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold-italic">n</mi><mo>⋅</mo><mi mathvariant="bold-italic">l</mi></mrow><annotation encoding="application/x-tex">\boldsymbol{n}\cdot\boldsymbol{l}</annotation></semantics></math></span> measures how directly the surface faces the light. This gives a result I&rsquo;m happy with.</p>

    </div>
</details>
</p>
<h2 id="results">Results</h2>
<figure class="grass-demo" data-grass-demo="result">
    <div class="grass-demo-labels" aria-hidden="true">
    <span>Wind · clumping · rounded normals · ambient occlusion</span>
    </div>
    <canvas class="grass-demo-scene" aria-label="Animated 3D grass field combining all four techniques" role="img"></canvas>
    
    <label class="grass-demo-control"><span>Wind speed</span><input type="range" min="0" max="100" value="50" aria-label="Results wind speed"><output></output></label>
    <button type="button" class="grass-demo-play">Pause animation</button>
    <p class="grass-demo-hint">A live miniature of the combined approach. Adjust wind speed or pause to inspect the blades.</p>
    <p class="grass-demo-status" role="status"></p>
    <figcaption>A simplified live WebGL scene combining scrolling wind, clumped blade sizes, rounded normals, and density-based ambient occlusion. The video at the top shows the full Godot implementation.</figcaption>
    <p class="grass-demo-disclaimer">This simulation was generated from the project source code using AI.</p>
</figure>

<p>The full Godot implementation shown in the video runs at 60-230 FPS on my test computer, depending on the density. This measurement is for the Godot scene, rather than the simplified browser demo. Individual blades are different from each other but remain similar to nearby blades, and the scrolling Perlin noise texture does a pretty good job of emulating wind.</p>
<p><figure>
    <img loading="lazy" src="/images/grass/result.png"
         alt="Final grass field over rolling terrain in Godot"/> <figcaption>
            <p>The finished field at a density of 0.4, using a terrain heightmap for the demonstration.</p>
        </figcaption>
</figure>

The ground mesh uses an albedo based on the base and tip color, specified density, and distance from the camera.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I&rsquo;m happy with how closely this captures the feel of Ghost of Tsushima&rsquo;s grass. There&rsquo;s still more I&rsquo;d like to try, including the game&rsquo;s specular lighting and shadows, and its bezier curve approach to bending individual blades.</p>
<p>You might notice that blades being viewed from their side look thin and not that great. The original Tsushima implementation rotates blades close to the camera to hide this problem. I&rsquo;d like to work on this next if I do put it in a game.</p>
<p>For that, I&rsquo;d also need to add performance optimizations like view frustum culling and LOD switching (<a href="https://github.com/2Retr0/GodotGrass">this person implemented it</a>).</p>
<details class="article-details">
    <summary>What are view frustum culling and LOD switching?</summary>
    <div class="article-details-content">
        <p>View frustum culling skips objects outside the camera&rsquo;s view. LOD (level of detail) switching uses simpler versions of objects farther away, where the extra detail is harder to see.</p>

    </div>
</details>

<h2 id="source-code-and-references">Source Code and References</h2>
<p><a href="https://github.com/rekomodo/6440_final">Source code for the project</a>.</p>
<ol>
<li><a href="https://www.youtube.com/watch?v=Ibe1JBF5i5Y">Procedural Grass in &lsquo;Ghost of Tsushima&rsquo;</a></li>
<li><a href="https://lodev.org/cgtutor/randomnoise.html">Texture Generation using Random Noise</a></li>
<li><a href="https://github.com/2Retr0/GodotGrass">Similar implementation of this emulation</a></li>
<li><a href="https://www.youtube.com/watch?v=bp7REZBV4P4">How do Major Video Games Render Grass?</a></li>
<li><a href="https://www.youtube.com/watch?v=jw00MbIJcrk">Modern Foliage Rendering</a></li>
</ol>
<h2 id="acknowledgment">Acknowledgment</h2>
<p>A lot of the implementation details came from <a href="https://github.com/2Retr0/GodotGrass">2Retr0&rsquo;s approach</a> to the same idea. This is mostly a simplified version with reduced scope. Their version is also much better looking.</p>
<p>I&rsquo;d like to thank the maintainers and contributors of the Godot game engine for providing an excellent platform on which to experiment with computer graphics.</p>
<p>I&rsquo;d also like to thank the maintainers and contributors of the Blender 3D software for providing an excellent platform on which to create models and specify UV mappings.</p>
]]></content:encoded></item><item><title>Computer Graphics: Curves, Animation, and Rendering</title><link>https://jairsantana.com/portfolio/computer-graphics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jairsantana.com/portfolio/computer-graphics/</guid><description>Spline modeling, skeletal deformation, cloth simulation, ray tracing, and shadow mapping in C++ and OpenGL.</description><content:encoded><![CDATA[<p>For MIT&rsquo;s 6.4400 Computer Graphics course, I implemented a series of assignments covering spline curves and surfaces, skeletal animation, physically-based simulation, ray tracing, and shadow mapping. Most of the work used GLOO, the course&rsquo;s lightweight C++ OpenGL framework, with GLM for vector and matrix operations.</p>
<p>Here&rsquo;s a small overview of what I learned.</p>
<h2 id="modeling-curves-and-surfaces">Modeling Curves and Surfaces</h2>
<p>In Assignment 1, I implemented cubic Bézier and B-Spline curves, conversion between their control-point representations, and bicubic tensor product patches. This gave me a better understanding of non-polygonal representations for surfaces. Since the patches are continuous and differentiable, we can easily get the normal for lighting as well.</p>
<h2 id="attaching-a-mesh-to-a-skeleton">Attaching a Mesh to a Skeleton</h2>
<p>Assignment 2 covered hierarchical transforms and skeletal subspace deformation, also called linear blend skinning. I built a node hierarchy and attached a mesh using per-vertex joint weights. Rotating a parent joint carries its descendants with it, while the weights let a vertex respond to several joints at once.</p>
<p>The approach is to first move a vertex from the character&rsquo;s bind pose into a joint&rsquo;s bind-pose coordinate frame, then move it back using that joint&rsquo;s current transform. I also passed accumulated transforms down the scene tree when collecting objects for rendering, avoiding repeated walks up the hierarchy. I believe this is very similar (if not identical) to how videogames handle object rendering trees.</p>
<details class="article-details">
    <summary>Code: one joint&#39;s contribution to a vertex</summary>
    <div class="article-details-content">
        <div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span>glm<span style="color:#ff7b72;font-weight:bold">::</span>mat4x4 <span style="color:#ff7b72;font-weight:bold">&amp;</span>bind_pose_joint_to_character_inverse_ <span style="color:#ff7b72;font-weight:bold">=</span> Bs_[j];
</span></span><span style="display:flex;"><span>glm<span style="color:#ff7b72;font-weight:bold">::</span>mat4x4 <span style="color:#ff7b72;font-weight:bold">&amp;</span>joint_to_character <span style="color:#ff7b72;font-weight:bold">=</span> joint_to_character_matrices[j];
</span></span><span style="display:flex;"><span>glm<span style="color:#ff7b72;font-weight:bold">::</span>vec4 position_contribution <span style="color:#ff7b72;font-weight:bold">=</span> (attachment_weights_[i][j] <span style="color:#ff7b72;font-weight:bold">*</span>
</span></span><span style="display:flex;"><span>joint_to_character <span style="color:#ff7b72;font-weight:bold">*</span> bind_pose_joint_to_character_inverse_ <span style="color:#ff7b72;font-weight:bold">*</span> bind_pose_position);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>new_positions<span style="color:#ff7b72;font-weight:bold">-&gt;</span>at(i) <span style="color:#ff7b72;font-weight:bold">+=</span> glm<span style="color:#ff7b72;font-weight:bold">::</span>vec3(position_contribution);
</span></span></code></pre></div><p>Summing these weighted contributions gives the deformed vertex position.</p>

    </div>
</details>

<h2 id="simulating-motion-with-particles-and-springs">Simulating Motion with Particles and Springs</h2>
<p>For Assignment 3, I implemented forward Euler, explicit trapezoidal, and fourth-order Runge–Kutta integration, then used them for circular motion, a spring-connected pendulum, and wireframe cloth. The particle system combines gravity, drag, and spring forces. The integrator then advances its positions and velocities over time.</p>
<p>The cloth is a grid with structural springs connecting neighbors, shear springs connecting diagonals, and flex springs spanning two grid edges. These resist stretching, diagonal collapse, and bending. This was more of a programming exercise, but it was still cool to see how to make a cloth simulation.</p>
<h2 id="rendering-with-rays">Rendering with Rays</h2>
<p>We were tasked with building a CPU ray tracer. I implemented sphere, plane, and triangle intersections, Phong lighting, shadow rays, and recursive reflections. Rays are transformed into object space for intersection, while normals return to world space using the inverse transpose of the object&rsquo;s transform.</p>
<details class="article-details">
    <summary>Code: tracing a reflection</summary>
    <div class="article-details-content">
        <div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-cpp" data-lang="cpp"><span style="display:flex;"><span><span style="color:#ff7b72">if</span>(bounces <span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#a5d6ff">0</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic">// ray bounces
</span></span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> glm<span style="color:#ff7b72;font-weight:bold">::</span>vec3 ray_bounce_origin    <span style="color:#ff7b72;font-weight:bold">=</span> hit_position <span style="color:#ff7b72;font-weight:bold">+</span> EPSILON <span style="color:#ff7b72;font-weight:bold">*</span> eye_reflection;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> glm<span style="color:#ff7b72;font-weight:bold">::</span>vec3 ray_bounce_direction <span style="color:#ff7b72;font-weight:bold">=</span> eye_reflection;
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">const</span> Ray       <span style="color:#d2a8ff;font-weight:bold">ray_bounce</span>(ray_bounce_origin, ray_bounce_direction);
</span></span><span style="display:flex;"><span>  HitRecord bounce_record <span style="color:#ff7b72;font-weight:bold">=</span> HitRecord();
</span></span><span style="display:flex;"><span>  color <span style="color:#ff7b72;font-weight:bold">+=</span> material_specular_color <span style="color:#ff7b72;font-weight:bold">*</span> TraceRay(ray_bounce, bounces <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>, bounce_record);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p><code>eye_reflection</code> is the reflected viewing-ray direction. The small offset keeps the new ray from immediately intersecting the surface it just left.</p>

    </div>
</details>

<h2 id="casting-shadows-in-opengl">Casting Shadows in OpenGL</h2>
<p>Assignment 5 involved texture mapping and shadows from a directional light. I rendered the scene from the light&rsquo;s viewpoint into a depth texture, then sampled that texture while shading the camera&rsquo;s view. If a fragment is farther from the light than the stored depth, another surface is blocking it.</p>
<p>This involved framebuffer objects, texture sampling, coordinate transforms, and GLSL. A small depth bias helps prevent a surface from incorrectly shadowing itself because of numerical error.</p>
<details class="article-details">
    <summary>Code: comparing against the shadow map</summary>
    <div class="article-details-content">
        <div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-glsl" data-lang="glsl"><span style="display:flex;"><span><span style="color:#ff7b72">vec3</span> proj_coords <span style="color:#ff7b72;font-weight:bold">=</span> (world_to_light_ndc_matrix <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#ff7b72">vec4</span>(world_position, <span style="color:#a5d6ff">1.0</span>)).xyz;
</span></span><span style="display:flex;"><span>proj_coords <span style="color:#ff7b72;font-weight:bold">=</span> proj_coords <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">0.5</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">0.5</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">float</span> this_depth     <span style="color:#ff7b72;font-weight:bold">=</span> proj_coords.z;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">float</span> occluder_depth <span style="color:#ff7b72;font-weight:bold">=</span> texture(shadow_texture, proj_coords.xy).r;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">float</span> bias <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.005</span>;
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span>(occluder_depth <span style="color:#ff7b72;font-weight:bold">&lt;</span> this_depth <span style="color:#ff7b72;font-weight:bold">-</span> bias) {
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#ff7b72">vec3</span>(<span style="color:#a5d6ff">0.0</span>, <span style="color:#a5d6ff">0.0</span>, <span style="color:#a5d6ff">0.0</span>);
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div>
    </div>
</details>

<p>These assignments preceded my final project, <a href="/posts/grass/">emulating Ghost of Tsushima&rsquo;s procedural grass in Godot</a>.</p>
]]></content:encoded></item><item><title>Flock Herder: Interactive Boids on FPGAs</title><link>https://jairsantana.com/portfolio/flock-herder/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://jairsantana.com/portfolio/flock-herder/</guid><description>Stereo vision, fixed-point motion, and a live camera overlay on a pair of FPGAs.</description><content:encoded><![CDATA[<p>This project entailed the construction of an interactive visual program where the user can herd flocks of virtual birds known as &ldquo;boids&rdquo; — on-screen models which behave as birds in a flock, fish in a school, or mammals in a herd — which follow a simple behavioral model based on what each individual on-screen boid can perceive around them. The intended full model includes alignment with nearby flock mates as well as movement towards and around a target in the user&rsquo;s hand.</p>
<p>Updating a singular boid&rsquo;s position requires the normalization of several vectors, which presents the need to develop a resource-efficient and low-latency module for vector normalizations. To acquire the three-dimensional position of the target, we implemented stereo vision using a pair of FPGAs — each with their own camera — which communicate with each other through the SPI protocol. Using the target&rsquo;s positional information, we computed the positions of the boids every frame in three dimensions and overlaid them on the camera footage via a double frame buffer.</p>
<p>The double frame buffer also presented a unique challenge since, due to overlaying the boids over the camera feed, there was a limited amount of additional pixels/DRAM requests that could be made every frame, amounting to roughly 25% of the screen&rsquo;s area.</p>
<p>For this class project, we committed to having multiple boids follow the target, moving every frame and being rendered at their computed positions. To demonstrate proper depth calculation, we modified every boid&rsquo;s size based on its depth. The implemented boid update module includes only the target-following force; alignment, cohesion, and separation remain future work.</p>
<figure>
    <img loading="lazy" src="/images/flock-herder/stereo-setup.png"
         alt="Physical setup showing a main FPGA and a stereo FPGA, each with a camera pointed at a shared tracking object"/> <figcaption>
            <p>The physical setup: two cameras attached to a pair of Urbana FPGAs.</p>
        </figcaption>
</figure>

<h2 id="contributions">Contributions</h2>
<p>This was a final project for MIT&rsquo;s 6.2050 Digital Systems Laboratory, built with Sreeja Thipireddy. I was responsible for the implementation of the normalizer, boid manager, and boid update modules. Sreeja was responsible for the implementation of the inter-FPGA communication protocol, depth calculation module, double frame buffer, and modifications to the traffic generator. The renderer module was implemented in collaboration.</p>
<h2 id="vector-normalization">Vector Normalization</h2>
<p>To compute the necessary vectors in the boid computations, it is necessary to normalize vectors. To normalize a vector, it&rsquo;s sufficient to multiply the vector&rsquo;s components by the reciprocal of the vector&rsquo;s magnitude.</p>
<p>For a vector <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="bold">v</mi><mo>=</mo><mo stretchy="false">(</mo><msub><mi>v</mi><mi>x</mi></msub><mo separator="true">,</mo><msub><mi>v</mi><mi>y</mi></msub><mo separator="true">,</mo><msub><mi>v</mi><mi>z</mi></msub><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\mathbf{v} = (v_x, v_y, v_z)</annotation></semantics></math></span>, we first compute its squared magnitude:</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>x</mi><mo>=</mo><msubsup><mi>v</mi><mi>x</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>y</mi><mn>2</mn></msubsup><mo>+</mo><msubsup><mi>v</mi><mi>z</mi><mn>2</mn></msubsup><mo separator="true">,</mo><mspace width="2em"/><mover accent="true"><mi mathvariant="bold">v</mi><mo>^</mo></mover><mo>=</mo><mi mathvariant="bold">v</mi><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">
x = v_x^2 + v_y^2 + v_z^2, \qquad \hat{\mathbf{v}} = \mathbf{v}\frac{1}{\sqrt{x}}.
</annotation></semantics></math></span><p>This lets us compute one reciprocal square root and reuse it for all three components.</p>
<h3 id="initial-approximation">Initial Approximation</h3>
<p>We can compute an initial approximation to the reciprocal of the square root via the position of the number&rsquo;s most significant bit.</p>
<p>Using <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>≈</mo><msup><mn>2</mn><mi>b</mi></msup></mrow><annotation encoding="application/x-tex">x \approx 2^b</annotation></semantics></math></span>, we obtain the initial approximation
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mn>1</mn><msqrt><mi>x</mi></msqrt></mfrac><mo>≈</mo><msup><mn>2</mn><mrow><mo>−</mo><mi>b</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow></msup><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">
\frac{1}{\sqrt{x}} \approx 2^{-b/2}.
</annotation></semantics></math></span><p>For a nonzero integer <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span>, the position <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>b</mi></mrow><annotation encoding="application/x-tex">b</annotation></semantics></math></span> of its most significant bit is
</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>b</mi><mo>=</mo><mrow><mi mathvariant="normal">B</mi><mi mathvariant="normal">I</mi><mi mathvariant="normal">T</mi><mi mathvariant="normal">_</mi><mi mathvariant="normal">C</mi><mi mathvariant="normal">O</mi><mi mathvariant="normal">U</mi><mi mathvariant="normal">N</mi><mi mathvariant="normal">T</mi></mrow><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>−</mo><mn>1</mn><mo>−</mo><mrow><mi mathvariant="normal">C</mi><mi mathvariant="normal">L</mi><mi mathvariant="normal">Z</mi></mrow><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">
b = \mathrm{BIT\_COUNT}(x) - 1 - \mathrm{CLZ}(x).
</annotation></semantics></math></span><p>I implemented a custom combinational module with a tree-based approach which computes the count of leading zeroes. The result for any n-bit number can be derived from the result of its left and right halves. If the upper half of the number contains a 1 bit, the result comes from that half. Otherwise, the result is the width of the upper half plus the leading-zero count of the lower half. This gives us a combinational tree for finding the CLZ. The implementation starts with 32 two-bit pairs: <code>00</code> has two leading zeroes, <code>01</code> has one, and a pair starting with <code>1</code> has none. Five merge layers combine these counts into the result for the full 64-bit input. In <code>clz_merge</code>, the highest bit indicates that a whole block is zero; the remaining bits encode its leading-zero count.
<details class="article-details">
    <summary>Code: tree-structured leading-zero counter</summary>
    <div class="article-details-content">
        <p>From <code>main/hdl/vec.sv</code>. The source references this  <a href="https://electronics.stackexchange.com/questions/196914/verilog-synthesize-high-speed-leading-zero-count">leading-zero count tree discussion</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-systemverilog" data-lang="systemverilog"><span style="display:flex;"><span><span style="color:#ff7b72">module</span> clz_merge #(
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">parameter</span> WIDTH
</span></span><span style="display:flex;"><span>) (
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">input</span> <span style="color:#ff7b72">wire</span> [WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] left,
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">input</span> <span style="color:#ff7b72">wire</span> [WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] right,
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">output</span> <span style="color:#ff7b72">logic</span> [<span style="color:#79c0ff;font-weight:bold">WIDTH:</span><span style="color:#a5d6ff">0</span>] out
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">logic</span> both_full, left_not_full;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> both_full <span style="color:#ff7b72;font-weight:bold">=</span> left[WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">&amp;</span> right[WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> left_not_full <span style="color:#ff7b72;font-weight:bold">=</span> left[WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">logic</span> [WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] right_chopped;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> right_chopped <span style="color:#ff7b72;font-weight:bold">=</span> right[WIDTH<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">logic</span> [<span style="color:#79c0ff;font-weight:bold">WIDTH:</span><span style="color:#a5d6ff">0</span>] result;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">always_comb</span> <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span>(both_full) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            result <span style="color:#ff7b72;font-weight:bold">=</span> {<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b1</span>, {WIDTH{<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>}}};
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span> <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">if</span>(left_not_full) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            result <span style="color:#ff7b72;font-weight:bold">=</span> {<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>, left};
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span> <span style="color:#ff7b72">else</span> <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            result <span style="color:#ff7b72;font-weight:bold">=</span> {<span style="color:#a5d6ff">2</span><span style="color:#a5d6ff">&#39;b01</span>, right_chopped};
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> out <span style="color:#ff7b72;font-weight:bold">=</span> result;
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">endmodule</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">module</span> clz_64 (
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">input</span> <span style="color:#ff7b72">wire</span> [<span style="color:#a5d6ff">63</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] data,
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">output</span> <span style="color:#ff7b72">logic</span> [<span style="color:#a5d6ff">6</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] out
</span></span><span style="display:flex;"><span>);
</span></span><span style="display:flex;"><span>    
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">logic</span> [<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] bit_pair [<span style="color:#a5d6ff">31</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>];
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">logic</span> [<span style="color:#a5d6ff">6</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] assembly_layer [<span style="color:#a5d6ff">5</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>] [<span style="color:#a5d6ff">31</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">genvar</span> j;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">generate</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> (j <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; j <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">32</span>; j<span style="color:#ff7b72;font-weight:bold">++</span>) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">assign</span> bit_pair[j] <span style="color:#ff7b72;font-weight:bold">=</span> data[<span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">*</span>j<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">*</span>j];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">assign</span> assembly_layer[<span style="color:#a5d6ff">0</span>][j] <span style="color:#ff7b72;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span>                (bit_pair[j] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">2</span><span style="color:#a5d6ff">&#39;b00</span>) <span style="color:#ff7b72;font-weight:bold">?</span> <span style="color:#a5d6ff">6</span><span style="color:#a5d6ff">&#39;b000010</span> <span style="color:#ff7b72;font-weight:bold">:</span>
</span></span><span style="display:flex;"><span>                (bit_pair[j] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">2</span><span style="color:#a5d6ff">&#39;b01</span>) <span style="color:#ff7b72;font-weight:bold">?</span> <span style="color:#a5d6ff">6</span><span style="color:#a5d6ff">&#39;b000001</span> <span style="color:#ff7b72;font-weight:bold">:</span>
</span></span><span style="display:flex;"><span>                                         <span style="color:#a5d6ff">6</span><span style="color:#a5d6ff">&#39;b000000</span> ;
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">endgenerate</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">genvar</span> layer, i;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">generate</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> (layer <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>; layer <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">6</span>; layer<span style="color:#ff7b72;font-weight:bold">++</span>) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>; i <span style="color:#ff7b72;font-weight:bold">&lt;</span> (<span style="color:#a5d6ff">32</span> <span style="color:#ff7b72;font-weight:bold">&gt;&gt;</span> (layer <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>)); i <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">2</span>) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>                clz_merge #(
</span></span><span style="display:flex;"><span>                    .WIDTH(layer <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>                ) m_clz_merge (
</span></span><span style="display:flex;"><span>                    .left(assembly_layer[layer <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>][i <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>][<span style="color:#79c0ff;font-weight:bold">layer:</span><span style="color:#a5d6ff">0</span>]),
</span></span><span style="display:flex;"><span>                    .right(assembly_layer[layer <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>][i][<span style="color:#79c0ff;font-weight:bold">layer:</span><span style="color:#a5d6ff">0</span>]),
</span></span><span style="display:flex;"><span>                    .out(assembly_layer[layer][i <span style="color:#ff7b72;font-weight:bold">&gt;&gt;</span> <span style="color:#a5d6ff">1</span>][layer<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>                );
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">endgenerate</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> out <span style="color:#ff7b72;font-weight:bold">=</span> assembly_layer[<span style="color:#a5d6ff">5</span>][<span style="color:#a5d6ff">0</span>];
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">endmodule</span>
</span></span></code></pre></div>
    </div>
</details>
</p>
<p>The squared components of the vector are shifted right by <code>FP_SHIFT</code> before being added, so <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span> still has <code>FP_SHIFT</code> fractional bits. If <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>F</mi><mo>=</mo><mrow><mi mathvariant="normal">F</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">_</mi><mi mathvariant="normal">S</mi><mi mathvariant="normal">H</mi><mi mathvariant="normal">I</mi><mi mathvariant="normal">F</mi><mi mathvariant="normal">T</mi></mrow></mrow><annotation encoding="application/x-tex">F = \mathrm{FP\_SHIFT}</annotation></semantics></math></span> and <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi></mrow><annotation encoding="application/x-tex">X</annotation></semantics></math></span> is the stored squared magnitude, its real value is <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>X</mi><mi mathvariant="normal">/</mi><msup><mn>2</mn><mi>F</mi></msup></mrow><annotation encoding="application/x-tex">X/2^F</annotation></semantics></math></span>. The stored reciprocal-square-root estimate therefore needs the scale factor</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mn>2</mn><mi>F</mi></msup><mfrac><mn>1</mn><msqrt><mrow><mi>X</mi><mi mathvariant="normal">/</mi><msup><mn>2</mn><mi>F</mi></msup></mrow></msqrt></mfrac><mo>≈</mo><msup><mn>2</mn><mrow><mo stretchy="false">(</mo><mn>3</mn><mi>F</mi><mo>−</mo><mi>b</mi><mo stretchy="false">)</mo><mi mathvariant="normal">/</mi><mn>2</mn></mrow></msup><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">
2^F\frac{1}{\sqrt{X/2^F}} \approx 2^{(3F-b)/2}.
</annotation></semantics></math></span><p>The code uses <code>e = (3*FP_SHIFT - b) &gt;&gt; 1</code> for this exponent. It zero-pads the squared magnitude to 64 bits before counting leading zeroes, then uses <code>b = 63 - len_sqr_clz</code> to find its most significant bit. The clocked assignment <code>y_pipe[2] &lt;= (1 &lt;&lt; e) &lt;&lt; FP_SHIFT</code> gives the estimate an extra <code>FP_SHIFT</code> bits, which are removed by <code>y_pipe_shifted</code> before it is used in the next multiplication.</p>
<details class="article-details">
    <summary>Code: fixed-point initial estimate</summary>
    <div class="article-details-content">
        <div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-systemverilog" data-lang="systemverilog"><span style="display:flex;"><span>clz_64 m_clz_64 (
</span></span><span style="display:flex;"><span>        .data({{(<span style="color:#a5d6ff">64</span><span style="color:#ff7b72;font-weight:bold">-</span>DOT_PRODUCT_WIDTH){<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>}},len_sqr_pipe[<span style="color:#a5d6ff">1</span>]}),
</span></span><span style="display:flex;"><span>        .out(len_sqr_clz)
</span></span><span style="display:flex;"><span>    ); <span style="color:#8b949e;font-style:italic">// 1
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> b <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">63</span> <span style="color:#ff7b72;font-weight:bold">-</span> len_sqr_clz; <span style="color:#8b949e;font-style:italic">// 1
</span></span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assign</span> e <span style="color:#ff7b72;font-weight:bold">=</span> (<span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">*</span>FP_SHIFT <span style="color:#ff7b72;font-weight:bold">-</span> b) <span style="color:#ff7b72;font-weight:bold">&gt;&gt;</span> <span style="color:#a5d6ff">1</span>; <span style="color:#8b949e;font-style:italic">// 1
</span></span></span></code></pre></div>
    </div>
</details>

<h3 id="newton-raphson-refinement">Newton-Raphson Refinement</h3>
<p>To improve this initial guess, I computed several iterations of a Newton-Raphson approximation scheme. Through a C simulation I found that 4 iterations was enough to achieve visually satisfactory results.</p>
<p>For an estimate <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>y</mi><mi>n</mi></msub></mrow><annotation encoding="application/x-tex">y_n</annotation></semantics></math></span> of <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><msqrt><mi>x</mi></msqrt></mrow><annotation encoding="application/x-tex">1/\sqrt{x}</annotation></semantics></math></span>, the iteration is</p>
<span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>y</mi><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>=</mo><mfrac><msub><mi>y</mi><mi>n</mi></msub><mn>2</mn></mfrac><mrow><mo fence="true">(</mo><mn>3</mn><mo>−</mo><mi>x</mi><msubsup><mi>y</mi><mi>n</mi><mn>2</mn></msubsup><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">
y_{n+1} = \frac{y_n}{2}\left(3 - xy_n^2\right).
</annotation></semantics></math></span><p>At the desired value, <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><msubsup><mi>y</mi><mi>n</mi><mn>2</mn></msubsup><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">xy_n^2 = 1</annotation></semantics></math></span>, so the correction factor is one. Each iteration employs only multiplication and substraction operations. In fixed-point arithmetic, the products also need to be shifted to account for the fractional bits. I split each iteration across three clock cycles: square the current estimate, multiply by the squared magnitude, and apply the correction. <code>THREE_HALFS</code> stores 1.5 in fixed-point format. The expression <code>half_ay2_pipe[i] &gt;&gt; (2 * FP_SHIFT + 1)</code> accounts for the product&rsquo;s extra fractional bits and divides it by two. The generated stages implement the equivalent form <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>y</mi><mrow><mi>n</mi><mo>+</mo><mn>1</mn></mrow></msub><mo>=</mo><msub><mi>y</mi><mi>n</mi></msub><mo stretchy="false">(</mo><mn>1.5</mn><mo>−</mo><mi>x</mi><msubsup><mi>y</mi><mi>n</mi><mn>2</mn></msubsup><mi mathvariant="normal">/</mi><mn>2</mn><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">y_{n+1} = y_n(1.5 - xy_n^2/2)</annotation></semantics></math></span>.
<details class="article-details">
    <summary>Code: three-cycle Newton-Raphson iteration</summary>
    <div class="article-details-content">
        <div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-systemverilog" data-lang="systemverilog"><span style="display:flex;"><span><span style="color:#ff7b72">genvar</span> pipe_idx;
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">generate</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span>(pipe_idx <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">3</span>; pipe_idx <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">3</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">*</span>ITERATIONS; pipe_idx <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">3</span>) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">always_ff</span> @( <span style="color:#ff7b72">posedge</span> clk ) <span style="color:#ff7b72">begin</span>
</span></span><span style="display:flex;"><span>                y2_pipe[pipe_idx] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y_pipe_shifted[pipe_idx<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">*</span> y_pipe_shifted[pipe_idx<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>]; <span style="color:#8b949e;font-style:italic">// 3*i
</span></span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic">// y2_pipe[pipe_idx+1] &lt;= y2_pipe[pipe_idx];
</span></span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic">// y2_pipe[pipe_idx+2] &lt;= y2_pipe[pipe_idx+1];
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic">// half_ay2_pipe[pipe_idx] &lt;= half_ay2_pipe[pipe_idx-1];
</span></span></span><span style="display:flex;"><span>                half_ay2_pipe[pipe_idx<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> (len_sqr_pipe[pipe_idx] <span style="color:#ff7b72;font-weight:bold">*</span> y2_pipe[pipe_idx]); <span style="color:#8b949e;font-style:italic">// 3*i + 1
</span></span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic">// half_ay2_pipe[pipe_idx+2] &lt;= half_ay2_pipe[pipe_idx+1];
</span></span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                y_pipe[pipe_idx] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y_pipe[pipe_idx<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>];
</span></span><span style="display:flex;"><span>                y_pipe[pipe_idx<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y_pipe[pipe_idx];
</span></span><span style="display:flex;"><span>                y_pipe[pipe_idx<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> ($signed(y_pipe_shifted[pipe_idx<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>]) <span style="color:#ff7b72;font-weight:bold">*</span> ($signed({<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>, THREE_HALFS}) <span style="color:#ff7b72;font-weight:bold">-</span> $signed({<span style="color:#a5d6ff">1</span><span style="color:#a5d6ff">&#39;b0</span>, half_ay2_pipe_shifted[pipe_idx<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>]}))); <span style="color:#8b949e;font-style:italic">// 3*i + 2
</span></span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">endgenerate</span>
</span></span></code></pre></div>
    </div>
</details>
</p>
<h3 id="precision-and-pipeline-cost">Precision and Pipeline Cost</h3>
<p>In the evaluation described in our final report, using an 11-bit integer range and an <code>FP_SHIFT</code> of 12, the computed vectors were within 10% of the desired vectors, which was enough accuracy for our application. This is an observed result for the evaluated inputs rather than a guarantee for every fixed-point input; sufficiently small vectors can have their squared components truncated to zero.</p>
<p>When synthesized for an 11-bit integer range and 12 bits of precision, the normalizer consumes 48 DSP slices. The latency of the normalizer is <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>3</mn><mo>+</mo><mn>3</mn><mi>I</mi></mrow><annotation encoding="application/x-tex">3 + 3I</annotation></semantics></math></span>, where <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>I</mi></mrow><annotation encoding="application/x-tex">I</annotation></semantics></math></span> is the number of iterations used for the inverse square root solver. While it is fully pipelined, all multiplications are assumed to take one cycle, which limits the range and precision of the input vectors due to the volume of operations required between frames. Too high a latency and we would not be able to finish computing updates for all boids.</p>
<h2 id="boid-computations">Boid Computations</h2>
<p>Every boid is associated with a position and velocity vector. Every frame, we add the velocity vector multiplied by a small <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi mathvariant="normal">Δ</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">\Delta t</annotation></semantics></math></span> to its position. The intended full behavioral model calculates the acceleration of the boid based on:</p>
<ol>
<li>Normalized offset to the target&rsquo;s position. Used in computing the target force.</li>
<li>Average direction of other boids in the perception radius. Also known as the average flock heading. Used in computing the alignment force.</li>
<li>Normalized offset to the average position of other boids in the perception radius. Also known as the flock center. Used in computing the cohesion force.</li>
<li>Average of the negative offset to the position of other boids. Also known as the separation heading. In essence, this force makes sure that boids don&rsquo;t fly over each other. Used in computing the separation force.</li>
</ol>
<p>In the full model, each of the above results is computed into a steering force by subtracting the boid&rsquo;s velocity from the influencing vector. These results are then clamped to have a maximum magnitude equal to a constant maximum steering force. These contributions to the acceleration are then summed and added to the boid&rsquo;s velocity.</p>
<p>The velocity&rsquo;s magnitude is then clamped between a constant minimum and maximum speed. This new velocity is then used next frame to update the boid&rsquo;s position.</p>
<h3 id="boid-update">Boid Update</h3>
<p>The <code>boid_update</code> module computes the boid&rsquo;s new velocity, which is passed back to the <code>boid_manager</code> module. In the submitted implementation, it only implemented the first of the four steering forces, which is responsible for leading the boid towards the target. To do this, it implements an FSM comprised of the following states:</p>
<ol>
<li><code>IDLE</code></li>
<li><code>NORMALIZE_OFFSET</code></li>
<li><code>GET_STEER</code></li>
<li><code>NORMALIZE_STEER_TARGET</code></li>
<li><code>CLAMP_STEER</code></li>
<li><code>GET_ACCELERATION</code></li>
<li><code>GET_VELOCITY</code></li>
<li><code>NORMALIZE_VELOCITY</code></li>
<li><code>CLAMP_VELOCITY</code></li>
</ol>
<p>Of relevance here is that, to clamp a vector between given minimum and maximum magnitudes, it is necessary to normalize it beforehand. Due to resource limitations, it was impossible to instantiate a separate normalizer module for each of these operations, so instead we opted for a blocking implementation during which the <code>boid_update</code> module changes the inputs to its normalizer submodule and waits for results.</p>
<p>To implement the rest of the steering forces, it would then be necessary to add stages for the computation of the necessary data as well as the necessary normalization stages before each steering force computation and subsequent clamping of the output value.</p>
<p>Our final report recorded a 51-cycle latency for <code>boid_update</code> and estimated at least 19,000 boid updates per frame on the pixel clock, including <code>boid_manager</code> overhead. In the supplied source version, the FSM takes 56 cycles after accepting an update request, before manager overhead: three 16-cycle normalization states, three two-cycle steering or clamping states, and two single-cycle states. The report&rsquo;s throughput estimate therefore needs to be recalculated for this version.</p>
<h3 id="boid-manager">Boid Manager</h3>
<p>The <code>boid_manager</code> module responsible for passing the necessary parameters to <code>boid_update</code> for every boid implements an FSM comprised of the following states:</p>
<ol>
<li><code>IDLE</code></li>
<li><code>POS_UPDATE</code></li>
<li><code>POS_UPDATE2</code></li>
<li><code>UPDATE_START</code></li>
<li><code>UPDATE_WAIT</code></li>
</ol>
<p>During the <code>IDLE</code> state, the module awaits the <code>new_frame</code> signal before saving the currently inputted target coordinates and transitioning to the <code>POS_UPDATE</code> state. In this state, the module updates all boid positions by adding their respective velocities scaled by a small positive number. Then, during <code>POS_UPDATE2</code>, all positions are boundary-checked and wrapped around the screen if necessary.</p>
<p>During <code>UPDATE_START</code>, the manager asserts the <code>data_in_valid</code> signal to the <code>boid_update</code> module and then transitions to the waiting state, where the current boid index is incremented after the <code>boid_update</code> module&rsquo;s <code>busy</code> signal goes low. This continues until all boids&rsquo; velocities have been updated, at which point the module returns to the <code>IDLE</code> state.</p>
<h2 id="design-evaluation">Design Evaluation</h2>
<h3 id="double-frame-buffer-and-rendering-bottleneck">Double Frame Buffer and Rendering Bottleneck</h3>
<p>The objective of the submitted implementation was to be able to render as many boids as possible and have them change sizes appropriately. Writing camera pixels into the frame buffer consumes most of the frame time, and the system cannot write an amount of boid pixels that surpasses the amount of cycles in the remaining frame time. This amount of pixels is roughly equal to the amount of cycles in the v-sync period.</p>
<p>This upper bounds the size and quantity of the boids by the amount of pixels for each boid when they&rsquo;re closest to the screen. In the configuration evaluated in our final report, we found that we could comfortably render 16 boids as 96 × 96 squares — or 147,456 pixels — whilst remaining under the frame time constraint. This measurement differs from the supplied renderer&rsquo;s current settings, which produce nominal square sizes of 4–49 pixels before eight-pixel width packing. This was the design&rsquo;s bottleneck and would be the first area to be improved upon in the next iteration as both the depth calculation and boid updates can handle increased throughput.</p>
<p>Additionally, we could cut off the additional pixels sent via the eight-pixel packing by modifying the strobe signal to the DDR3 controller to only write pixels included in the square region. This would remove the current rendering artifact where box widths change only in increments of eight.</p>
<h3 id="communication-protocol-and-depth-calculation">Communication Protocol and Depth Calculation</h3>
<p>Since the communication protocol only needs to communicate 22 bits for the center of mass information every frame regardless of any changes to the rest of the design, it would not need any changes or improvements. Similarly, the depth calculation module deals with known constants and is mostly a wrapper for a fixed-point divide. Even if a different camera were to be used, it would only require constant values and/or bit widths to be changed in this module.</p>
<h3 id="vector-normalization-and-boid-updates">Vector Normalization and Boid Updates</h3>
<p>The biggest source of resource allocation in the design comes from the normalizer instanced by the <code>boid_update</code> module. Forty-eight of the sixty DSP slices in the module come from the normalizer instance. Most of these come from the pipelined Newton-Raphson iterations, which instance multiplications between numbers with result bit widths of up to <span class="katex"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>4</mn><mtext> </mtext><mrow><mi mathvariant="normal">F</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">_</mi><mi mathvariant="normal">R</mi><mi mathvariant="normal">A</mi><mi mathvariant="normal">N</mi><mi mathvariant="normal">G</mi><mi mathvariant="normal">E</mi></mrow><mo>+</mo><mn>3</mn><mtext> </mtext><mrow><mi mathvariant="normal">F</mi><mi mathvariant="normal">P</mi><mi mathvariant="normal">_</mi><mi mathvariant="normal">S</mi><mi mathvariant="normal">H</mi><mi mathvariant="normal">I</mi><mi mathvariant="normal">F</mi><mi mathvariant="normal">T</mi></mrow><mo>+</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">4\,\mathrm{FP\_RANGE} + 3\,\mathrm{FP\_SHIFT} + 2</annotation></semantics></math></span>. This results in timing and resource utilization issues for higher integer ranges and precision widths.</p>
<p>This problem could be solved by splitting each multiplication into different steps — each of them fitting into a single DSP slice. This would drastically reduce resource utilization and improve throughput at the cost of increased latency. Increased latency, however, directly influences the amount of boids that can be updated every frame due to the boid manager&rsquo;s blocking implementation of updates and the choice to instance only one normalizer for <code>boid_update</code>.</p>
<p><a href="https://github.mit.edu/6205F25/fa25-6205-team28">Project source (MIT GitHub)</a></p>
]]></content:encoded></item></channel></rss>