<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en-US"><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="https://ulysseszh.github.io/feed/tags/shader.xml" rel="self" type="application/atom+xml" /><link href="https://ulysseszh.github.io/" rel="alternate" type="text/html" hreflang="en-US" /><updated>2026-04-19T16:48:01-07:00</updated><id>https://ulysseszh.github.io/feed/tags/shader.xml</id><title type="html"><![CDATA[Ulysses’ trip]]></title><subtitle>Here we are at the awesome (awful) blog written by UlyssesZhan!</subtitle><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><entry><title type="html"><![CDATA[Multi-pass Gaussian blur filter]]></title><link href="https://ulysseszh.github.io/programming/2025/07/17/blur-filter.html" rel="alternate" type="text/html" title="Multi-pass Gaussian blur filter" /><published>2025-07-17T01:06:08-07:00</published><updated>2025-07-17T01:06:08-07:00</updated><id>https://ulysseszh.github.io/programming/2025/07/17/blur-filter</id><content type="html" xml:base="https://ulysseszh.github.io/programming/2025/07/17/blur-filter.html"><![CDATA[<p>In image processing, a Gaussian blur filter is the transformation of an image that blurs it by a Gaussian function. Mathematically, a Gaussian blur filter is a linear transformation <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi></mrow><annotation encoding="application/x-tex">G</annotation></semantics></math></span></span> on a measurable function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo>:</mo><msup><mi mathvariant="double-struck">R</mi><mi>d</mi></msup><mo>→</mo><mi mathvariant="double-struck">R</mi></mrow><annotation encoding="application/x-tex">f:\bR^d\to\bR</annotation></semantics></math></span></span> that does not grow too fast at infinity (or more generally, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo>:</mo><msup><mi mathvariant="double-struck">R</mi><mi>d</mi></msup><mo>→</mo><mi>V</mi></mrow><annotation encoding="application/x-tex">f:\bR^d\to V</annotation></semantics></math></span></span>, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>V</mi></mrow><annotation encoding="application/x-tex">V</annotation></semantics></math></span></span> is a locally convex topological vector space) which results in a function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mi>f</mi><mo>:</mo><msup><mi mathvariant="double-struck">R</mi><mi>d</mi></msup><mo>→</mo><mi mathvariant="double-struck">R</mi></mrow><annotation encoding="application/x-tex">Gf:\bR^d\to\bR</annotation></semantics></math></span></span> defined by the integral <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>G</mi><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo><mi mathvariant="normal">≔</mi></mo><mo>∫</mo><mfrac><mrow><msup><mi mathvariant="normal">d</mi><mi>d</mi></msup><mi>y</mi></mrow><msqrt><mrow><msup><mrow><mo fence="true">(</mo><mn>2</mn><mi>π</mi><mo fence="true">)</mo></mrow><mi>d</mi></msup><mi>det</mi><mo>⁡</mo><mi>A</mi></mrow></msqrt></mfrac><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mo>−</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><msub><mrow><mo fence="true">(</mo><msup><mi>A</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">)</mo></mrow><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi>y</mi><mi>j</mi></msub><msub><mi>y</mi><mi>k</mi></msub><mo fence="true">)</mo></mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mi>y</mi><mo fence="true">)</mo></mrow><mo separator="true">,</mo></mrow><annotation encoding="application/x-tex">\fc{Gf}x\ceq\int\frac{\d^dy}{\sqrt{\p{2\pi}^d\det A}}\fc\exp{-\fr12\p{A^{-1}}_{jk}y_jy_k}\fc f{x+y},</annotation></semantics></math></span></span></span> where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span></span> is a symmetric positive definite matrix called the covariance matrix of the Gaussian kernel.</p>
<p>By looking at this formula, we can see that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{Gf}x</annotation></semantics></math></span></span> is exactly the expectation value of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mi>Y</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc f{x+Y}</annotation></semantics></math></span></span>, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Y</mi></mrow><annotation encoding="application/x-tex">Y</annotation></semantics></math></span></span> is a random vector distributed according to the multivariate normal distribution with mean <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>0</mn></mrow><annotation encoding="application/x-tex">0</annotation></semantics></math></span></span> and covariance matrix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span></span>. Let’s say, however, that instead of a multivariate normal distribution, we have a discrete distribution on a finite set of points <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msub><mi>a</mi><mi>i</mi></msub><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{a_i}</annotation></semantics></math></span></span>, each with probability <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">w_i</annotation></semantics></math></span></span>, satisfying
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><mo>=</mo><mn>1</mn><mo separator="true">,</mo><mspace width="1em"/><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><msub><mi>a</mi><mi>i</mi></msub><mo>=</mo><mn>0.</mn></mrow><annotation encoding="application/x-tex">\sum_i w_i=1,\quad\sum_i w_ia_i=0.</annotation></semantics></math></span></span></span> Then, for <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>Y</mi></mrow><annotation encoding="application/x-tex">Y</annotation></semantics></math></span></span> distributed according to this discrete distribution, the expectation value of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mi>Y</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc f{x+Y}</annotation></semantics></math></span></span> is
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi mathvariant="normal">E</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">[</mo><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mi>Y</mi><mo fence="true">)</mo></mrow><mo fence="true">]</mo></mrow><mo>=</mo><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><msub><mi>a</mi><mi>i</mi></msub><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">\bopc E{\fc f{x+Y}}=\sum_i w_i\fc f{x+a_i}.</annotation></semantics></math></span></span></span> This distribution has the covariance matrix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><mo>=</mo><msub><mo>∑</mo><mi>i</mi></msub><msub><mi>w</mi><mi>i</mi></msub><msub><mi>a</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><msub><mi>a</mi><mrow><mi>i</mi><mi>k</mi></mrow></msub></mrow><annotation encoding="application/x-tex">A_{jk}=\sum_i w_ia_{ij}a_{ik}</annotation></semantics></math></span></span>, but this expectation value is not the same as the result <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{Gf}x</annotation></semantics></math></span></span> of the Gaussian blur filter. In order to approach the Gaussian distribution, we need to sum <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> independent samples
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msub><mi>Y</mi><mi>α</mi></msub><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{Y_\alpha}</annotation></semantics></math></span></span>, each distributed according the same discrete distribution with covariance matrix <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">A/N</annotation></semantics></math></span></span>. Without changing the relation between <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span></span> and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msub><mi>a</mi><mi>i</mi></msub><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{a_i}</annotation></semantics></math></span></span>, we can achieve this by taking the distribution to be among the points <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msub><mi>a</mi><mi>i</mi></msub><mi mathvariant="normal">/</mi><msqrt><mi>N</mi></msqrt><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{a_i/\sqrt N}</annotation></semantics></math></span></span>, with the same probabilities <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">w_i</annotation></semantics></math></span></span>. Then, according to the central limit theorem, we have <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>G</mi><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo>=</mo><munder><mrow><mi>lim</mi><mo>⁡</mo></mrow><mrow><mi>N</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><mi mathvariant="normal">E</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">[</mo><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><munder><mo>∑</mo><mi>α</mi></munder><msub><mi>Y</mi><mi>α</mi></msub><mo fence="true">)</mo></mrow><mo fence="true">]</mo></mrow><mo>=</mo><munder><mrow><mi>lim</mi><mo>⁡</mo></mrow><mrow><mi>N</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><munder><mo>∑</mo><mrow><mo fence="true">{</mo><msub><mi>i</mi><mi>α</mi></msub><mo fence="true">}</mo></mrow></munder><mrow><mo fence="true">(</mo><munder><mo>∏</mo><mi>α</mi></munder><msub><mi>w</mi><msub><mi>i</mi><mi>α</mi></msub></msub><mo fence="true">)</mo></mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mfrac><mn>1</mn><msqrt><mi>N</mi></msqrt></mfrac><munder><mo>∑</mo><mi>α</mi></munder><msub><mi>a</mi><msub><mi>i</mi><mi>α</mi></msub></msub><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">\fc{Gf}x=\lim_{N\to\infty}\bopc E{\fc f{x+\sum_\alpha Y_\alpha}}
=\lim_{N\to\infty}\sum_{\B{i_\alpha}}\p{\prod_\alpha w_{i_\alpha}}\fc f{x+\fr{1}{\sqrt N}\sum_\alpha a_{i_\alpha}}.</annotation></semantics></math></span></span></span> By staring at this formula, one may notice that it is actually the result of the same linear transformation <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span> applied <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> times to the function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span></span>, where <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo><mi mathvariant="normal">≔</mi></mo><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mfrac><msub><mi>a</mi><mi>i</mi></msub><msqrt><mi>N</mi></msqrt></mfrac><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">\fc{P_Nf}x\ceq\sum_iw_i\fc f{x+\fr{a_i}{\sqrt N}}.</annotation></semantics></math></span></span></span> Thus, we can write <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>G</mi><mo>=</mo><munder><mrow><mi>lim</mi><mo>⁡</mo></mrow><mrow><mi>N</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><msubsup><mi>P</mi><mi>N</mi><mi>N</mi></msubsup><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">G=\lim_{N\to\infty}P_N^N.</annotation></semantics></math></span></span></span></p>
<p>We can now see why we can implement a Gaussian blur filter as a multi-pass filter: each pass of the filter is equivalent to applying the linear transformation <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span> once, and the Gaussian blur filter is the limit of applying this transformation infinitely many times. After choosing the points <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msub><mi>a</mi><mi>i</mi></msub><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{a_i}</annotation></semantics></math></span></span> and the weights <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>w</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">w_i</annotation></semantics></math></span></span>, we can easily implement the filter in a fragment shader. After implementing <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span>, one can implement the multi-pass filter by flip-flopping between two textures <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> times. Here is an example implementation of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span> for a 2D (<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mo>=</mo><mn>2</mn></mrow><annotation encoding="application/x-tex">d=2</annotation></semantics></math></span></span>) texture, with <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi>a</mi><mn>0</mn></msub><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mi>σ</mi></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo separator="true">,</mo><mspace width="1em"/><msub><mi>a</mi><mn>1</mn></msub><mo>=</mo><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em" columnalign="center" columnspacing="1em"><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mrow><mo>−</mo><mi>σ</mi></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="false"><mn>0</mn></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow><mo separator="true">,</mo><mspace width="1em"/><msub><mi>w</mi><mn>0</mn></msub><mo>=</mo><msub><mi>w</mi><mn>1</mn></msub><mo>=</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><mo separator="true">,</mo></mrow><annotation encoding="application/x-tex">a_0=\begin{pmatrix}\sgm\\0\end{pmatrix},\quad
a_1=\begin{pmatrix}-\sgm\\0\end{pmatrix},\quad
w_0=w_1=\fr12,</annotation></semantics></math></span></span></span> where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi></mrow><annotation encoding="application/x-tex">\sgm</annotation></semantics></math></span></span> would be the standard deviation of the resulting Gaussian blur filter:</p>
<table class="rouge-table">
  <tbody>
    <tr>
      <td class="highlight language-glsl">
        <pre>
          <code>
            <span class="line line-1"><span class="k">varying</span> <span class="kt">vec2</span> <span class="n">vTextureCoord</span><span class="p">;</span>
</span>
            <span class="line line-2">
</span>
            <span class="line line-3"><span class="k">uniform</span> <span class="kt">sampler2D</span> <span class="n">uTexture</span><span class="p">;</span>
</span>
            <span class="line line-4"><span class="k">uniform</span> <span class="kt">float</span> <span class="n">uStrength</span><span class="p">;</span> <span class="c1">// sigma / sqrt(N)</span>
</span>
            <span class="line line-5">
</span>
            <span class="line line-6"><span class="kt">void</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span>
            <span class="line line-7">	<span class="nb">gl_FragColor</span>  <span class="o">=</span> <span class="n">texture2D</span><span class="p">(</span><span class="n">uTexture</span><span class="p">,</span> <span class="n">vTextureCoord</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span> <span class="n">uStrength</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">))</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
</span>
            <span class="line line-8">	<span class="nb">gl_FragColor</span> <span class="o">+=</span> <span class="n">texture2D</span><span class="p">(</span><span class="n">uTexture</span><span class="p">,</span> <span class="n">vTextureCoord</span> <span class="o">+</span> <span class="kt">vec2</span><span class="p">(</span><span class="o">-</span><span class="n">uStrength</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">))</span> <span class="o">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span><span class="p">;</span>
</span>
            <span class="line line-9"><span class="p">}</span>
</span>
          </code>
        </pre>
      </td>
    </tr>
  </tbody>
</table>
<p class="no-indent">
When using the shader, the texture is input as the uniform <code>uTexture</code>, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mi mathvariant="normal">/</mi><msqrt><mi>N</mi></msqrt></mrow><annotation encoding="application/x-tex">\sgm/\sqrt N</annotation></semantics></math></span></span> is input as the uniform <code>uStrength</code>. This example is then called the 2-tap horizontal Gaussian blur filter (because to find <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc fx</annotation></semantics></math></span></span> for some <span class="katex"><span class="katex-mathml"><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></span> one needs to evaluate <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi></mrow><annotation encoding="application/x-tex">f</annotation></semantics></math></span></span> twice).
</p>
<p>To get a good Gaussian blur effect, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> should be large enough, which can quickly become computationally expensive. We can reduce the number of passes by rewriting <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>P</mi><mi>N</mi><mi>N</mi></msubsup><mo>=</mo><msup><mrow><mo fence="true">(</mo><msubsup><mi>P</mi><mi>N</mi><mi>n</mi></msubsup><mo fence="true">)</mo></mrow><mrow><mi>N</mi><mi mathvariant="normal">/</mi><mi>n</mi></mrow></msup></mrow><annotation encoding="application/x-tex">P_N^N=\p{P_N^n}^{N/n}</annotation></semantics></math></span></span> so that the number of passes is now reduced from <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span> to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi><mi mathvariant="normal">/</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">N/n</annotation></semantics></math></span></span> without changing the result, but the cost is to implement <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>P</mi><mi>N</mi><mi>n</mi></msubsup></mrow><annotation encoding="application/x-tex">P_N^n</annotation></semantics></math></span></span> instead of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span> in the shader. With the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>P</mi><mi>N</mi></msub></mrow><annotation encoding="application/x-tex">P_N</annotation></semantics></math></span></span> example above, the <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msubsup><mi>P</mi><mi>N</mi><mi>n</mi></msubsup></mrow><annotation encoding="application/x-tex">P_N^n</annotation></semantics></math></span></span> filter is then a
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo fence="true">)</mo></mrow><annotation encoding="application/x-tex">\p{n+1}</annotation></semantics></math></span></span>-tap horizontal Gaussian blur filter. The total number of times to fetch the texture is <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mrow><mo fence="true">(</mo><mi>n</mi><mo>+</mo><mn>1</mn><mo fence="true">)</mo></mrow><mi>N</mi><mi mathvariant="normal">/</mi><mi>n</mi></mrow><annotation encoding="application/x-tex">\p{n+1}N/n</annotation></semantics></math></span></span> to render the whole blurred texture, which decreases as <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>n</mi></mrow><annotation encoding="application/x-tex">n</annotation></semantics></math></span></span> increases.</p>
<p>By utilizing the linear sampling feature of the GPU, this number can be further reduced (by half) if <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mi mathvariant="normal">/</mi><msqrt><mi>N</mi></msqrt><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">\sgm/\sqrt N=1</annotation></semantics></math></span></span>. One can read further about this in <a href="https://www.rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/" target="_blank" rel="external">this article</a>.</p>
<hr/>
<p>Let us now move on to an interesting relation to the heat equation. The heat equation is a partial differential equation that reads <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi mathvariant="normal">∂</mi><mi>t</mi></msub><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo>=</mo><munder><mo>∑</mo><mi>j</mi></munder><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">\partial_t\fc{f_t}x=\sum_j\partial_j\partial_j\fc{f_t}x.</annotation></semantics></math></span></span></span> The physical meaning of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{f_t}x</annotation></semantics></math></span></span> is the temperature at position <span class="katex"><span class="katex-mathml"><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></span> at time <span class="katex"><span class="katex-mathml"><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></span>, and the solution to this equation gives the time evolution of the temperature distribution in a medium with unit thermal diffusivity. More generally, we can have some other thermal diffusivities, which may even be anisotropic, in which case the equation reads
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msub><mi mathvariant="normal">∂</mi><mi>t</mi></msub><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo>=</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><munder><mo>∑</mo><mrow><mi>j</mi><mi>k</mi></mrow></munder><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>k</mi></msub><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo separator="true">,</mo></mrow><annotation encoding="application/x-tex">\partial_t\fc{f_t}x=\fr12\sum_{jk}A_{jk}\partial_j\partial_k\fc{f_t}x,</annotation></semantics></math></span></span></span> where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span></span> is a symmetric positive definite matrix. Such an equation can always be reduced to the form with unit thermal diffusivity by a linear transformation of the coordinates <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>↦</mo><mi>L</mi><mi>x</mi></mrow><annotation encoding="application/x-tex">x\mapsto Lx</annotation></semantics></math></span></span>, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>L</mi></mrow><annotation encoding="application/x-tex">L</annotation></semantics></math></span></span> satisfies
<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi><mi mathvariant="normal">/</mi><mn>2</mn><mo>=</mo><msup><mi>L</mi><mi mathvariant="normal">T</mi></msup><mi>L</mi></mrow><annotation encoding="application/x-tex">A/2=L^\mrm TL</annotation></semantics></math></span></span> (the Cholesky decomposition of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi><mi mathvariant="normal">/</mi><mn>2</mn></mrow><annotation encoding="application/x-tex">A/2</annotation></semantics></math></span></span>). By using operator exponentiation, we can write the solution to the heat equation as <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>f</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mo>=</mo><msup><mi>G</mi><mi>t</mi></msup><msub><mi>f</mi><mn>0</mn></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{f_t}x=G^t\fc{f_0}x</annotation></semantics></math></span></span>, where <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><msup><mi>G</mi><mi>t</mi></msup><mo><mi mathvariant="normal">≔</mi></mo><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mfrac><mi>t</mi><mn>2</mn></mfrac><munder><mo>∑</mo><mrow><mi>j</mi><mi>k</mi></mrow></munder><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>k</mi></msub><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">G^t\ceq\fc\exp{\fr t2\sum_{jk}A_{jk}\partial_j\partial_k}</annotation></semantics></math></span></span></span> is called the time evolution operator (which is not generally defined for <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>&lt;</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">t&lt;0</annotation></semantics></math></span></span>, which means that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mo fence="true">{</mo><msup><mi>G</mi><mi>t</mi></msup><mo fence="true">}</mo></mrow><annotation encoding="application/x-tex">\B{G^t}</annotation></semantics></math></span></span> cannot form a group but only a monoid).</p>
<p>The reason that I denote it as <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>G</mi><mi>t</mi></msup></mrow><annotation encoding="application/x-tex">G^t</annotation></semantics></math></span></span> is that <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>G</mi><mn>1</mn></msup></mrow><annotation encoding="application/x-tex">G^1</annotation></semantics></math></span></span> gives exactly the Gaussian blur filter <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi></mrow><annotation encoding="application/x-tex">G</annotation></semantics></math></span></span> (when acting on real analytic functions). This is not immediately obvious, and I will justify its correctness by starting from the multi-pass expression of the Gaussian blur filter. For analytic functions, we can get the translation operator by exponentiating the derivative operator, so <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mfrac><msub><mi>a</mi><mi>i</mi></msub><msqrt><mi>N</mi></msqrt></mfrac><mo fence="true">)</mo></mrow><mo>=</mo><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><munder><mo>∑</mo><mi>j</mi></munder><mfrac><msub><mi>a</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><msqrt><mi>N</mi></msqrt></mfrac><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><mo fence="true">)</mo></mrow><mi>f</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">\fc f{x+\fr{a_i}{\sqrt N}}=\fc\exp{\sum_j\fr{a_{ij}}{\sqrt N}\partial_j}\fc fx.</annotation></semantics></math></span></span></span> Therefore, we can write <span class="katex-display"><span class="katex"><span class="katex-mathml"><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>P</mi><mi>N</mi></msub></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow/><mo>=</mo><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><munder><mo>∑</mo><mi>j</mi></munder><mfrac><msub><mi>a</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><msqrt><mi>N</mi></msqrt></mfrac><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><mo fence="true">)</mo></mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow/></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow/><mo>=</mo><munder><munder><mrow><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub></mrow><mo stretchy="true">⏟</mo></munder><mn>1</mn></munder><mo>+</mo><mfrac><mn>1</mn><msqrt><mi>N</mi></msqrt></mfrac><munder><mo>∑</mo><mi>j</mi></munder><munder><munder><mrow><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><msub><mi>a</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub></mrow><mo stretchy="true">⏟</mo></munder><mn>0</mn></munder><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><mo>+</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>N</mi></mrow></mfrac><munder><mo>∑</mo><mrow><mi>j</mi><mi>k</mi></mrow></munder><munder><munder><mrow><munder><mo>∑</mo><mi>i</mi></munder><msub><mi>w</mi><mi>i</mi></msub><msub><mi>a</mi><mrow><mi>i</mi><mi>j</mi></mrow></msub><msub><mi>a</mi><mrow><mi>i</mi><mi>k</mi></mrow></msub></mrow><mo stretchy="true">⏟</mo></munder><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub></munder><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>k</mi></msub><mo>+</mo><mi mathvariant="normal">O</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><msup><mi>N</mi><mrow><mo>−</mo><mn>3</mn><mi mathvariant="normal">/</mi><mn>2</mn></mrow></msup><mo fence="true">)</mo></mrow><mi mathvariant="normal">.</mi></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
P_N&amp;=\sum_iw_i\fc\exp{\sum_j\fr{a_{ij}}{\sqrt N}\partial_j}\\
&amp;=\underbrace{\sum_iw_i}_1
+\fr1{\sqrt N}\sum_j\underbrace{\sum_iw_ia_{ij}}_0\partial_j
+\fr1{2N}\sum_{jk}\underbrace{\sum_iw_ia_{ij}a_{ik}}_{A_{jk}}\partial_j\partial_k
+\order{N^{-3/2}}.
\end{align*}</annotation></semantics></math></span></span></span> Now, in the limit of infinite <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>N</mi></mrow><annotation encoding="application/x-tex">N</annotation></semantics></math></span></span>, we have <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>G</mi><mo>=</mo><munder><mrow><mi>lim</mi><mo>⁡</mo></mrow><mrow><mi>N</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><msubsup><mi>P</mi><mi>N</mi><mi>N</mi></msubsup><mo>=</mo><munder><mrow><mi>lim</mi><mo>⁡</mo></mrow><mrow><mi>N</mi><mo>→</mo><mi mathvariant="normal">∞</mi></mrow></munder><msup><mrow><mo fence="true">(</mo><mn>1</mn><mo>+</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>N</mi></mrow></mfrac><munder><mo>∑</mo><mrow><mi>j</mi><mi>k</mi></mrow></munder><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>k</mi></msub><mo fence="true">)</mo></mrow><mi>N</mi></msup><mo>=</mo><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mfrac><mn>1</mn><mn>2</mn></mfrac><munder><mo>∑</mo><mrow><mi>j</mi><mi>k</mi></mrow></munder><msub><mi>A</mi><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi mathvariant="normal">∂</mi><mi>j</mi></msub><msub><mi mathvariant="normal">∂</mi><mi>k</mi></msub><mo fence="true">)</mo></mrow><mo>=</mo><msup><mi>G</mi><mn>1</mn></msup><mi mathvariant="normal">.</mi></mrow><annotation encoding="application/x-tex">G=\lim_{N\to\infty}P_N^N
=\lim_{N\to\infty}\p{1+\fr1{2N}\sum_{jk}A_{jk}\partial_j\partial_k}^N
=\fc\exp{\fr12\sum_{jk}A_{jk}\partial_j\partial_k}=G^1.</annotation></semantics></math></span></span></span> This means that applying the Gaussian blur filter is equivalent to evolving one unit of time according to the heat equation. Therefore, the equation in the beginning of this article, the definition of the Gaussian blur filter, can then be used to express the unit time evolution under the heat equation in the form of a integral transformation. To get the general <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>G</mi><mi>t</mi></msup></mrow><annotation encoding="application/x-tex">G^t</annotation></semantics></math></span></span>, we can just replace <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>A</mi></mrow><annotation encoding="application/x-tex">A</annotation></semantics></math></span></span> with <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mi>A</mi></mrow><annotation encoding="application/x-tex">tA</annotation></semantics></math></span></span>.</p>
<p>As a byproduct, we can then show that the heat kernel is a Gaussian kernel. The heat kernel <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>K</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo separator="true">,</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{K_t}{x,x'}</annotation></semantics></math></span></span> is defined as the solution to the heat equation with initial condition <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>K</mi><mn>0</mn></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo separator="true">,</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow><mo>=</mo><mi>δ</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>−</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc{K_0}{x,x'}=\fc\dlt{x-x'}</annotation></semantics></math></span></span>. Directly applying <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mi>G</mi><mi>t</mi></msup></mrow><annotation encoding="application/x-tex">G^t</annotation></semantics></math></span></span> to <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>δ</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>−</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow><annotation encoding="application/x-tex">\fc\dlt{x-x'}</annotation></semantics></math></span></span> gives
<span class="katex-display"><span class="katex"><span class="katex-mathml"><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"><mrow><msub><mi>K</mi><mi>t</mi></msub><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo separator="true">,</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow/><mo>=</mo><msup><mi>G</mi><mi>t</mi></msup><mi>δ</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>−</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow/></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow/><mo>=</mo><mo>∫</mo><mfrac><mrow><msup><mi mathvariant="normal">d</mi><mi>d</mi></msup><mi>y</mi></mrow><msqrt><mrow><msup><mrow><mo fence="true">(</mo><mn>2</mn><mi>π</mi><mi>t</mi><mo fence="true">)</mo></mrow><mi>d</mi></msup><mi>det</mi><mo>⁡</mo><mi>A</mi></mrow></msqrt></mfrac><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mo>−</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>t</mi></mrow></mfrac><msub><mrow><mo fence="true">(</mo><msup><mi>A</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">)</mo></mrow><mrow><mi>j</mi><mi>k</mi></mrow></msub><msub><mi>y</mi><mi>j</mi></msub><msub><mi>y</mi><mi>k</mi></msub><mo fence="true">)</mo></mrow><mi>δ</mi><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mi>x</mi><mo>+</mo><mi>y</mi><mo>−</mo><msup><mi>x</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msup><mo fence="true">)</mo></mrow></mrow></mstyle></mtd></mtr><mtr><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow/></mstyle></mtd><mtd><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow/><mo>=</mo><mfrac><mn>1</mn><msqrt><mrow><msup><mrow><mo fence="true">(</mo><mn>2</mn><mi>π</mi><mi>t</mi><mo fence="true">)</mo></mrow><mi>d</mi></msup><mi>det</mi><mo>⁡</mo><mi>A</mi></mrow></msqrt></mfrac><mi>exp</mi><mo>⁡</mo><mtext> ⁣</mtext><mrow><mo fence="true">(</mo><mo>−</mo><mfrac><mn>1</mn><mrow><mn>2</mn><mi>t</mi></mrow></mfrac><msub><mrow><mo fence="true">(</mo><msup><mi>A</mi><mrow><mo>−</mo><mn>1</mn></mrow></msup><mo fence="true">)</mo></mrow><mrow><mi>j</mi><mi>k</mi></mrow></msub><mrow><mo fence="true">(</mo><msub><mi>x</mi><mi>j</mi></msub><mo>−</mo><msubsup><mi>x</mi><mi>j</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msubsup><mo fence="true">)</mo></mrow><mrow><mo fence="true">(</mo><msub><mi>x</mi><mi>k</mi></msub><mo>−</mo><msubsup><mi>x</mi><mi>k</mi><mo mathvariant="normal" lspace="0em" rspace="0em">′</mo></msubsup><mo fence="true">)</mo></mrow><mo fence="true">)</mo></mrow><mo separator="true">,</mo></mrow></mstyle></mtd></mtr></mtable><annotation encoding="application/x-tex">\begin{align*}
\fc{K_t}{x,x'}&amp;=G^t\fc\dlt{x-x'}\\
&amp;=\int\fr{\d^dy}{\sqrt{\p{2\pi t}^d\det A}}\fc\exp{-\fr1{2t}\p{A^{-1}}_{jk}y_jy_k}\fc\dlt{x+y-x'}\\
&amp;=\fr{1}{\sqrt{\p{2\pi t}^d\det A}}\fc\exp{-\fr1{2t}\p{A^{-1}}_{jk}\p{x_j-x'_j}\p{x_k-x'_k}},
\end{align*}</annotation></semantics></math></span></span></span> which is the form that you would find in textbooks.</p>
<hr/>
<p>The reason that I decided to study the Gaussian blur filter is that I spotted a flaw in the implementation of the blur filter in <a href="https://pixijs.com" target="_blank" rel="external">PixiJS</a>, where the uniform <code>uStrength</code> in the example shader above is scaled by <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><mi>N</mi></mrow><annotation encoding="application/x-tex">1/N</annotation></semantics></math></span></span> instead of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mn>1</mn><mi mathvariant="normal">/</mi><msqrt><mi>N</mi></msqrt></mrow><annotation encoding="application/x-tex">1/\sqrt N</annotation></semantics></math></span></span>. I was very happy to find the bug because this is the first time that I found a bug without actually producing an unexpected phenomenon first but by just staring at the source codes and deducing the mathematical formulas by hand. I opened an <a href="https://github.com/pixijs/pixijs/issues/11554" target="_blank" rel="external">issue</a> for my findings.</p>]]></content><author><name>UlyssesZhan</name><email>ulysseszhan@gmail.com</email></author><category term="programming" /><category term="algorithm" /><category term="shader" /><category term="probability" /><category term="pde" /><summary type="html"><![CDATA[According to the central limit theorem, the sum of some i.i.d. samples is normally distributed in the limit of large sample size. This fact can be used to implement a multi-pass Gaussian blur filter, where the total number of passes is equal to the number of samples used in the averaging. Through this, we can also see a nice relation to the heat equation, which is not surprising since the heat kernel is a Gaussian function.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://ulysseszh.github.io/assets/images/covers/2025-07-17-blur-filter.png" /><media:content medium="image" url="https://ulysseszh.github.io/assets/images/covers/2025-07-17-blur-filter.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>