<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

 <title>Xiang Feng | Blog</title>
 <link href="https://blog.sigfeng.com/atom.xml" rel="self"/>
 <link href="https://blog.sigfeng.com/"/>
 <updated>2026-10-22T05:11:11+02:00</updated>
 <id>https://blog.sigfeng.com</id>
 <author>
   <name>gesko</name>
   <email>your-email@email.com</email>
 </author>

 
 <entry>
   <title>SAP Memo</title>
   <link href="https://blog.sigfeng.com/2026/10/15/sap-memo"/>
   <updated>2026-10-15T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2026/10/15/SAP-Memo</id>
   <content type="html">&lt;h2 id=&quot;按你的四步框架把修正和两个问题的答案都揉进去&quot;&gt;按你的四步框架，把修正和两个问题的答案都揉进去：&lt;/h2&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;1-法向规则&quot;&gt;1. 法向规则&lt;/h3&gt;

&lt;p&gt;我们定义了一套法向力的规则，法向力跟穿透和靠近速度有关（Kelvin–Voigt：弹簧加阻尼，隐式地用步末的穿透），本质上是一根弹簧。整理之后是：&lt;/p&gt;

\[\gamma_n = \frac{\hat v_n - v_n}{R_n}\]

&lt;p&gt;$\hat v_n = -\phi_0/(h+\tau)$ 是法向力为 0 时的零点，它是一个门槛，不是一个状态：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;已经穿透时，$\hat v_n &amp;gt; 0$：物体必须以这个速度往外退，力才为 0；&lt;/li&gt;
  &lt;li&gt;还有间隙时，$\hat v_n &amp;lt; 0$：物体只要靠近得不比这个速度快，就没有力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$v_n &amp;lt; \hat v_n$ 时有推力；$v_n \ge \hat v_n$ 时没有力。SAP 里的”分离”指的是后者，也就是”这一步没有法向力”，不一定是物体在远离。&lt;/p&gt;

&lt;h3 id=&quot;2-理想的切向规则&quot;&gt;2. 理想的切向规则&lt;/h3&gt;

&lt;p&gt;理想情况下，摩擦力有三种情况：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;分离：没有法向力，也就没有摩擦力；&lt;/li&gt;
  &lt;li&gt;滑动：摩擦力 $= \mu F_n$，方向与滑移相反；&lt;/li&gt;
  &lt;li&gt;粘滞：$v_t = 0$，摩擦力 $\le \mu F_n$。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以理想情况下，摩擦力本身就像一个 projection 操作：$F_n$ 决定上界，摩擦力被限制在这个上界以内。&lt;/p&gt;

&lt;p&gt;难点在于粘滞：这时摩擦力是一个约束力，”需要多少就给多少”，由整个系统决定，&lt;strong&gt;不是速度的函数&lt;/strong&gt;。一个速度（$v_t = 0$）对应无数个可能的力，没法写成规则，也就没法优化。&lt;/p&gt;

&lt;h3 id=&quot;3-sap-的切向规则用一点点滑动换来静摩擦&quot;&gt;3. SAP 的切向规则：用一点点滑动换来静摩擦&lt;/h3&gt;

&lt;p&gt;SAP 的做法是：$\gamma_t = -v_t/R_t$，$v_t$ 是相对滑动速度。通过允许一点点相对滑动，让静摩擦力”需要多少就给多少”变成由速度唯一确定。这和法向用一点点穿透换来弹簧规则是同一个套路。$R_t = \sigma w$，滑动量大约是”没有摩擦时会滑的速度”的 $\sigma$ 倍。&lt;/p&gt;

&lt;p&gt;这样做还有一个好处：切向规则的形式和法向一样（切向取 $\hat v_t = 0$），可以统一写成&lt;/p&gt;

\[y = R^{-1}(\hat v - v_c)\]

&lt;p&gt;$y$ 就是投影前的冲量。然后用 $R$ 范数把 $y$ 投影到摩擦锥上，得到真正的冲量 $\gamma$。在锥里就是粘滞，被截到锥面上就是滑动，被截到锥尖就是分离。&lt;/p&gt;

&lt;h3 id=&quot;4-为了能优化要对称所以联合投影&quot;&gt;4. 为了能优化：要对称，所以联合投影&lt;/h3&gt;

&lt;p&gt;如果法向和切向分开投影（先算 $F_n$，再把摩擦截到 $\mu F_n$），会发现 Jacobian $\partial\gamma/\partial v$ 不对称：滑动时摩擦上限依赖 $v_n$，但法向不依赖 $v_t$。不对称就写不成”某个凸势能的梯度”，也就没法优化。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;所以 SAP 把 $y$ 整个一起在 $R$ 范数下投影到三维锥上。这样一来，法向&lt;strong&gt;自动&lt;/strong&gt;获得了对切向速度的耦合：滑动时，法向冲量相当于把 $v_n$ 换成了 $v_n - \mu&lt;/td&gt;
      &lt;td&gt;v_t&lt;/td&gt;
      &lt;td&gt;$，而且强度正好和正向耦合相等。用 $R$ 范数，就是为了让两边配平。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这种耦合只在滑动时出现，因为只有滑动时摩擦被上界卡住，才依赖法向。粘滞时摩擦没达到上界，和法向无关，正向耦合为 0，反向耦合也必须为 0。分离时两者都是 0。所以耦合不是三种情况都要加，而是&lt;strong&gt;哪里有正向耦合，哪里才需要反向耦合&lt;/strong&gt;。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;代价是 gliding 伪影：滑动时物体会被多推出约 $\mu&lt;/td&gt;
      &lt;td&gt;v_t&lt;/td&gt;
      &lt;td&gt;(h+\tau)$，悬在表面上方滑行。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;5-补一句icf--lagged&quot;&gt;5. 补一句：ICF / Lagged&lt;/h3&gt;

&lt;p&gt;“能优化”的精确条件是：冲量场 $\gamma(v_c)$ 无旋（Jacobian 对称）并且单调（势能是凸的）。SAP 是通过”&lt;strong&gt;两边都耦合&lt;/strong&gt;“来满足对称性。Lagged 则是”&lt;strong&gt;两边都不耦合&lt;/strong&gt;“：把摩擦上界冻结成步初算好的 $\mu n_0$，法向和切向完全分开，因此不需要联合投影，也没有 gliding 伪影。代价是摩擦上界滞后一步。&lt;/p&gt;

&lt;h2 id=&quot;gliding-artifacts到底是允许一点点相对滑动引入的还是这个耦合引入的&quot;&gt;gliding artifacts，到底是允许一点点相对滑动引入的，还是这个耦合引入的&lt;/h2&gt;

&lt;p&gt;是&lt;strong&gt;耦合&lt;/strong&gt;引入的，和”允许一点点相对滑动”无关。有三个证据。&lt;/p&gt;

&lt;h3 id=&quot;证据-1伪影的大小和-r_t-无关&quot;&gt;证据 1：伪影的大小和 $R_t$ 无关&lt;/h3&gt;

&lt;p&gt;gliding 那一项是：&lt;/p&gt;

\[\hat\mu\,y_r = \mu\frac{R_t}{R_n}\cdot\frac{\|v_t\|}{R_t} = \frac{\mu\|v_t\|}{R_n}\]

&lt;p&gt;$R_t$（也就是 $\sigma$）被约掉了。把 $\sigma$ 调大或调小，蠕滑会变，但 gliding 不变。即使让 $\sigma \to 0$（完全不允许滑动），它仍然存在。没有正则化的刚体 Anitescu 凸近似，同样有这个伪影。&lt;/p&gt;

&lt;h3 id=&quot;证据-2它只在滑动时出现而滑动时正则化根本不起作用&quot;&gt;证据 2：它只在滑动时出现，而滑动时正则化根本不起作用&lt;/h3&gt;

&lt;p&gt;滑动时 $\gamma_t = -\mu\gamma_n\hat v_t$，和 $R_t$ 无关，正则化在这里已经”失效”了。”允许一点点滑动”只在&lt;strong&gt;粘滞&lt;/strong&gt;时起作用，而粘滞时 $\gamma = y$，没有任何耦合，也就没有 gliding。&lt;/p&gt;

&lt;h3 id=&quot;证据-3lagged-有同样的正则化却没有-gliding&quot;&gt;证据 3：Lagged 有同样的正则化，却没有 gliding&lt;/h3&gt;

&lt;p&gt;Lagged 的摩擦在原点处的刚度也是 $1/(\sigma w)$，”允许一点点滑动”的程度和 SAP 完全相同。但它把摩擦上界冻结成了 $\mu n_0$，没有联合投影，也就没有反向耦合，因此没有 gliding。&lt;/p&gt;

&lt;h3 id=&quot;两个伪影各管各的&quot;&gt;两个伪影各管各的&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;来源&lt;/th&gt;
      &lt;th&gt;什么时候出现&lt;/th&gt;
      &lt;th&gt;大小&lt;/th&gt;
      &lt;th&gt;SAP&lt;/th&gt;
      &lt;th&gt;Lagged&lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt; &lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;蠕滑&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;切向正则化（允许一点点滑动）&lt;/td&gt;
      &lt;td&gt;粘滞时&lt;/td&gt;
      &lt;td&gt;约为”没有摩擦时会滑的速度”的 $\sigma$ 倍&lt;/td&gt;
      &lt;td&gt;有&lt;/td&gt;
      &lt;td&gt;有&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;gliding&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;联合投影带来的反向耦合&lt;/td&gt;
      &lt;td&gt;滑动时&lt;/td&gt;
      &lt;td&gt;多推出约 $\mu&lt;/td&gt;
      &lt;td&gt;v_t&lt;/td&gt;
      &lt;td&gt;(h+\tau)$&lt;/td&gt;
      &lt;td&gt;有&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;需要澄清的一点是：之前说”正则化让切向和法向形式统一，所以可以用 $R$ 范数联合投影”。这说的是正则化让联合投影&lt;strong&gt;写得出来&lt;/strong&gt;，但 gliding 不是正则化造成的。真正造成 gliding 的，是”为了对称而让法向依赖 $v_t$”这个选择本身。&lt;/p&gt;

&lt;h2 id=&quot;最核心最核心的一定要让摩擦力和切向速度联系在一起的原因是啥&quot;&gt;最核心最核心的一定要让摩擦力和切向速度联系在一起的原因是啥&lt;/h2&gt;

&lt;p&gt;最核心的原因只有一个：&lt;strong&gt;SAP 选择了只把速度当作未知量。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;一句话&quot;&gt;一句话&lt;/h3&gt;

&lt;p&gt;SAP 把一个时间步写成：&lt;/p&gt;

\[\min_v\ \tfrac12\|v - v^*\|_M^2 + \sum_i \ell_i(J_i v)\]

&lt;p&gt;&lt;strong&gt;未知量只有 $v$&lt;/strong&gt;。力不是未知量，而是 $v$ 的函数 $\gamma(v_c)$。既然力必须由速度算出来，摩擦力也就必须是切向速度的函数。&lt;/p&gt;

&lt;h3 id=&quot;为什么理想的静摩擦做不到这一点&quot;&gt;为什么理想的静摩擦做不到这一点&lt;/h3&gt;

&lt;p&gt;理想静摩擦是：$v_t = 0$，摩擦力可以是 $[-\mu F_n, \mu F_n]$ 中的任意值。&lt;/p&gt;

&lt;p&gt;在 $v_t = 0$ 这一点上，一个速度对应无数个力。&lt;strong&gt;它不是函数&lt;/strong&gt;，所以：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;给定速度 $v$，算不出唯一的 $\gamma$，Newton 法的每一步都没法计算；&lt;/li&gt;
  &lt;li&gt;也写不出光滑的势能 $\ell$，因为 $\ell$ 在那里有尖角，没有 Hessian。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;用 $\gamma_t = -v_t/R_t$ 代替它，把”一个点对应一整段力”换成”一条很陡的直线”，就变成了函数。&lt;/p&gt;

&lt;h3 id=&quot;不这样做的话替代方案是什么&quot;&gt;不这样做的话，替代方案是什么&lt;/h3&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;替代方案是&lt;strong&gt;把力也当成未知量&lt;/strong&gt;，把静摩擦写成约束：$v_t = 0$，并且 $&lt;/td&gt;
      &lt;td&gt;\gamma_t&lt;/td&gt;
      &lt;td&gt;\le \mu\gamma_n$。传统的 LCP、NCP、PGS，以及各种锥规划方法都是这么做的。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;代价是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;变成带约束的问题，要么不光滑，要么需要 active set、内点法这类更复杂的方法；&lt;/li&gt;
  &lt;li&gt;刚性静摩擦的解可能不唯一（比如四条腿的桌子，每条腿分担多少摩擦是不确定的）。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;SAP 的取舍是：&lt;strong&gt;接受一点点蠕滑，换来一个只关于速度的、无约束、光滑、严格凸的问题&lt;/strong&gt;，可以直接用 Newton 法求解，而且解唯一。&lt;/p&gt;

&lt;p&gt;所以”摩擦力必须和切向速度联系起来”不是物理上的要求，而是”&lt;strong&gt;只解速度&lt;/strong&gt;“这个求解方式上的要求。法向也是一样：用弹簧代替刚性的”不穿透”约束，同样是为了让法向力成为速度的函数。&lt;/p&gt;

&lt;h2 id=&quot;primal和dual优化指的是什么&quot;&gt;primal和dual优化指的是什么？&lt;/h2&gt;

&lt;p&gt;这两个词说的是&lt;strong&gt;选哪个量作为未知量&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Primal（原问题）&lt;/strong&gt;：未知量是&lt;strong&gt;速度&lt;/strong&gt; $v$；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Dual（对偶问题）&lt;/strong&gt;：未知量是&lt;strong&gt;冲量&lt;/strong&gt; $\gamma$。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;两者描述的是同一个物理问题，最优解对应同一个状态，只是从两个方向去解。&lt;/p&gt;

&lt;h3 id=&quot;primal解速度sap-的做法&quot;&gt;Primal：解速度（SAP 的做法）&lt;/h3&gt;

\[\min_v\ \tfrac12(v - v^*)^\top M(v - v^*) + \sum_i \ell_i(J_i v)\]

&lt;ul&gt;
  &lt;li&gt;未知量的个数 = 自由度数；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;没有约束&lt;/strong&gt;。摩擦锥藏在 $\gamma(v_c)$ 这个函数里，通过投影来处理；&lt;/li&gt;
  &lt;li&gt;冲量是速度的函数，所以静摩擦必须正则化成速度的函数（上一个问题的答案）；&lt;/li&gt;
  &lt;li&gt;Newton 法的矩阵是 $M + \sum_i J_i^\top G_i J_i$。它的稀疏结构和 $M$ 一样，沿运动学树分布。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;dual解冲量&quot;&gt;Dual：解冲量&lt;/h3&gt;

&lt;p&gt;从动量方程把速度消掉：$v = v^* + M^{-1}J^\top\gamma$，所以接触速度是：&lt;/p&gt;

\[v_c = \underbrace{Jv^*}_{v_c^*} + \underbrace{JM^{-1}J^\top}_{W}\,\gamma\]

&lt;p&gt;Delassus 算子 $W$ 在这里登场了。代入后，问题变成：&lt;/p&gt;

\[\min_{\gamma\in\mathcal F}\ \tfrac12\gamma^\top(W + R)\,\gamma + \gamma^\top(v_c^* - \hat v)\]

&lt;ul&gt;
  &lt;li&gt;未知量的个数 = 3 × 接触数；&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;有约束&lt;/strong&gt;：$\gamma$ 必须在摩擦锥里；&lt;/li&gt;
  &lt;li&gt;冲量本身就是未知量，静摩擦可以直接当约束处理，原则上不需要正则化。但加上 $R$ 可以保证 $W + R$ 正定，从而解唯一。否则接触有冗余时（比如四条腿的桌子），$W$ 是奇异的，冲量不唯一；&lt;/li&gt;
  &lt;li&gt;矩阵 $W + R$ 需要 $M^{-1}$，而且只要两个接触在同一个物体或同一条链上，它们之间就有耦合，所以通常比较稠密。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;可以验证：对这个 dual 问题写出最优性条件，得到的正是”$\gamma$ 是 $y = R^{-1}(\hat v - v_c)$ 在 $R$ 范数下到 $\mathcal F$ 的投影”，也就是 SAP 的投影规则。两个问题互为 Fenchel 对偶，正则化 $R$ 在两边是同一个东西。&lt;/p&gt;

&lt;h3 id=&quot;对比&quot;&gt;对比&lt;/h3&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;Primal（SAP）&lt;/th&gt;
      &lt;th&gt;Dual&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;未知量&lt;/td&gt;
      &lt;td&gt;速度 $v$&lt;/td&gt;
      &lt;td&gt;冲量 $\gamma$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;规模&lt;/td&gt;
      &lt;td&gt;自由度数&lt;/td&gt;
      &lt;td&gt;3 × 接触数&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;约束&lt;/td&gt;
      &lt;td&gt;无&lt;/td&gt;
      &lt;td&gt;$\gamma \in \mathcal F$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;矩阵&lt;/td&gt;
      &lt;td&gt;$M + J^\top GJ$，稀疏，跟树结构一致&lt;/td&gt;
      &lt;td&gt;$W + R = JM^{-1}J^\top + R$，往往稠密&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;典型解法&lt;/td&gt;
      &lt;td&gt;Newton + line search，收敛快&lt;/td&gt;
      &lt;td&gt;PGS（逐个接触投影）、锥规划、内点法&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;优点&lt;/td&gt;
      &lt;td&gt;无约束，收敛快，有全局收敛保证&lt;/td&gt;
      &lt;td&gt;接触少、自由度多时规模小；PGS 实现简单&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;缺点&lt;/td&gt;
      &lt;td&gt;规模随自由度增长；需要正则化静摩擦&lt;/td&gt;
      &lt;td&gt;有约束；PGS 在质量比大时收敛很慢&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h3 id=&quot;和你在对齐的-mujoco-的关系&quot;&gt;和你在对齐的 MuJoCo 的关系&lt;/h3&gt;

&lt;p&gt;MuJoCo 两种都有：Newton 和 CG 求解器在 primal 空间（加速度）里工作，PGS 在 dual 空间（约束力）里工作。它也有正则化 $R$，作用和 SAP 的 $R$ 一样。SAP 和 MuJoCo 默认的 Newton 求解器都是 primal 的，所以它们的结构很像。这也是我们能把刚体场景对齐到 MuJoCo Warp 的原因之一。&lt;/p&gt;

&lt;h2 id=&quot;法向力模型&quot;&gt;法向力模型&lt;/h2&gt;

&lt;p&gt;法向力用线性 Kelvin–Voigt 模型，并且取步末的穿透量：&lt;/p&gt;

\[\phi(v_n) \approx \phi_0 + h\,v_n,\qquad f_n = -k\,(\phi + \tau v_n) = -k\big(\phi_0 + (h+\tau)v_n\big)\]

&lt;blockquote&gt;
  &lt;p&gt;&lt;u&gt;我现在有点迷糊，我们一点点来，线性 Kelvin–Voigt 模型是啥&lt;/u&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;kelvinvoigt-模型是什么&quot;&gt;Kelvin–Voigt 模型是什么&lt;/h3&gt;

&lt;p&gt;它就是&lt;strong&gt;一根弹簧和一个阻尼器并联&lt;/strong&gt;：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;      ┌──/\/\/\/──┐   弹簧 k
  ────┤           ├────
      └───[ | ]───┘   阻尼 c
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;两个元件的伸缩量相同，所以力直接相加：&lt;/p&gt;

\[f = k\,\delta + c\,\dot\delta\]

&lt;p&gt;其中 $\delta$ 是压缩量。弹簧项和压缩量成正比，阻尼项和压缩速度成正比。”线性”指的是两项都是一次的。Drake 里还有非线性的 Hunt–Crossley 模型，那是另一回事。&lt;/p&gt;

&lt;p&gt;习惯上会把阻尼写成 $c = k\tau$：&lt;/p&gt;

\[f = k\,(\delta + \tau\,\dot\delta)\]

&lt;p&gt;$\tau = c/k$ 的单位是秒，就是图里的 &lt;strong&gt;dissipation time scale&lt;/strong&gt;。它表示阻尼和弹簧的相对强弱：$\tau = 0$ 是纯弹簧，碰撞后会完全弹回；$\tau$ 越大，能量耗散越多。&lt;/p&gt;

&lt;h2 id=&quot;法向冲量形式&quot;&gt;法向冲量形式&lt;/h2&gt;

&lt;p&gt;好，接着做下一步：把力变成冲量，再整理成 $R_n^{-1}(\hat v_n - v_n)$ 的形式。&lt;/p&gt;

&lt;h3 id=&quot;第一步力乘以-h得到冲量&quot;&gt;第一步：力乘以 $h$，得到冲量&lt;/h3&gt;

&lt;p&gt;求解器解的是速度，动量方程里用的是&lt;strong&gt;冲量&lt;/strong&gt;（力 × 时间），所以：&lt;/p&gt;

\[\gamma_n = h\,f_n = -h k\big(\phi_0 + (h+\tau)\,v_n\big)\]

&lt;h3 id=&quot;第二步把-htau-提出来&quot;&gt;第二步：把 $(h+\tau)$ 提出来&lt;/h3&gt;

\[\gamma_n = -h k (h+\tau)\Big(\frac{\phi_0}{h+\tau} + v_n\Big)
= \underbrace{h k (h+\tau)}_{R_n^{-1}}\Big(\underbrace{-\frac{\phi_0}{h+\tau}}_{\hat v_n} - v_n\Big)\]

&lt;p&gt;这就是：&lt;/p&gt;

\[\gamma_n = R_n^{-1}\,(\hat v_n - v_n),\qquad R_n = \frac{1}{h k (h+\tau)},\qquad \hat v_n = -\frac{\phi_0}{h+\tau}\]

&lt;h3 id=&quot;为什么是除htautau这个不是当时法向力里面和速度耦合的吗&quot;&gt;为什么是除h+tau，tau这个不是当时法向力里面和速度耦合的吗&lt;/h3&gt;

&lt;p&gt;回到这个式子：&lt;/p&gt;

\[f_n = -k\big(\phi_0 + h\,v_n + \tau\,v_n\big)\]

&lt;p&gt;括号里有&lt;strong&gt;两项&lt;/strong&gt;都乘着 $v_n$：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$h\,v_n$ 来自隐式离散化：这一步走完，位置变了 $h v_n$；&lt;/li&gt;
  &lt;li&gt;$\tau\,v_n$ 来自阻尼器：速度本身产生的阻尼力。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;合起来，$v_n$ 的系数就是 $(h+\tau)$。$\hat v_n$ 的定义是”力为零时的速度”，也就是解方程：&lt;/p&gt;

\[\phi_0 + (h+\tau)\,v_n = 0 \;\Rightarrow\; v_n = -\frac{\phi_0}{h+\tau}\]

&lt;p&gt;所以除以 $(h+\tau)$ 并没有引入新的物理，只是&lt;strong&gt;求这个线性函数的零点&lt;/strong&gt;。系数是什么就除以什么。$\tau$ 之所以在分母里，恰恰是因为它和 $v_n$ 耦合。&lt;/p&gt;

&lt;h2 id=&quot;推出法向力和法向冲量之后怎么体现摩擦问题优化的&quot;&gt;推出法向力和法向冲量之后，怎么体现摩擦问题优化的？&lt;/h2&gt;

&lt;h3 id=&quot;第一步只有法向的一维例子&quot;&gt;第一步：只有法向的一维例子&lt;/h3&gt;

&lt;p&gt;一个质量为 $m$ 的方块落向地面。没有接触力时，它这一步的速度会是 $v^*$。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;动量方程&lt;/strong&gt;：&lt;/p&gt;

\[m(v - v^*) = \gamma_n(v)\]

&lt;p&gt;&lt;strong&gt;Kelvin–Voigt 推出来的法向冲量&lt;/strong&gt;（只推不拉，所以有个 $\max$）：&lt;/p&gt;

\[\gamma_n(v) = \max\big(0,\ R_n^{-1}(\hat v_n - v)\big)\]

&lt;p&gt;这两条联立，就能解出 $v$。现在换个角度，定义一个势能：&lt;/p&gt;

\[\ell(v) = \tfrac12 R_n^{-1}\big(\max(0,\ \hat v_n - v)\big)^2 = \tfrac12 R_n\gamma_n^2\]

&lt;p&gt;对它求导，正好是 $\ell’(v) = -\gamma_n(v)$。于是考虑这个优化问题：&lt;/p&gt;

\[\min_v\ \underbrace{\tfrac12 m(v - v^*)^2}_{\text{动能项}} + \underbrace{\ell(v)}_{\text{接触势能}}\]

&lt;p&gt;令导数为零：&lt;/p&gt;

\[m(v - v^*) - \gamma_n(v) = 0\]

&lt;p&gt;&lt;strong&gt;这就是动量方程。&lt;/strong&gt; 所以”解动量方程”和”求这个函数的最小值”是同一件事。而且这个函数是凸的（两项都是凸的），所以最小值唯一，可以用 Newton 法求。&lt;/p&gt;

&lt;h3 id=&quot;第二步加上摩擦max-变成了锥投影&quot;&gt;第二步：加上摩擦，$\max$ 变成了锥投影&lt;/h3&gt;

&lt;p&gt;一维时，”只推不拉”就是一个 $\max(0,\cdot)$。它其实就是&lt;strong&gt;把 $y_n$ 投影到集合 ${\gamma_n \ge 0}$ 上&lt;/strong&gt;。&lt;/p&gt;

&lt;table&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;加上摩擦后，冲量变成三维的，允许的集合从半直线 ${\gamma_n \ge 0}$ 变成了摩擦锥 ${&lt;/td&gt;
      &lt;td&gt;\gamma_t&lt;/td&gt;
      &lt;td&gt;\le \mu\gamma_n}$。&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;一维（只有法向）&lt;/th&gt;
      &lt;th&gt;三维（法向 + 摩擦）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;试探冲量&lt;/td&gt;
      &lt;td&gt;$y_n = R_n^{-1}(\hat v_n - v_n)$&lt;/td&gt;
      &lt;td&gt;$y = R^{-1}(\hat v - v_c)$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;限制&lt;/td&gt;
      &lt;td&gt;$\gamma_n \ge 0$&lt;/td&gt;
      &lt;td&gt;$\gamma \in \mathcal F$（摩擦锥）&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;怎么得到冲量&lt;/td&gt;
      &lt;td&gt;$\max(0, y_n)$&lt;/td&gt;
      &lt;td&gt;在 $R$ 范数下投影到 $\mathcal F$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;势能&lt;/td&gt;
      &lt;td&gt;$\tfrac12 R_n\gamma_n^2$&lt;/td&gt;
      &lt;td&gt;$\tfrac12\gamma^\top R\gamma$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;关键性质&lt;/td&gt;
      &lt;td&gt;$\ell’ = -\gamma_n$&lt;/td&gt;
      &lt;td&gt;$\nabla_{v_c}\ell = -\gamma$&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;&lt;strong&gt;锥投影就是 $\max(0,\cdot)$ 的三维版本。&lt;/strong&gt; 摩擦就是通过这一步进入问题的：它决定了 $\gamma_t$ 怎样被截断，同时也决定了 $\ell$ 的形状。&lt;strong&gt;而选 $R$ 范数，就是为了让最后一行的梯度关系在三维时依然成立。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;第三步整个系统&quot;&gt;第三步：整个系统&lt;/h3&gt;

&lt;p&gt;多个接触、多个自由度时，接触速度是 $v_{c,i} = J_i v$，问题变成：&lt;/p&gt;

\[\min_v\ \tfrac12(v - v^*)^\top M(v - v^*) + \sum_i \ell_i(J_i v)\]

&lt;p&gt;用链式法则求梯度，令其为零：&lt;/p&gt;

\[M(v - v^*) - \sum_i J_i^\top\gamma_i = 0\]

&lt;p&gt;这还是动量方程：广义动量的变化等于所有接触冲量（通过 $J^\top$ 搬回广义坐标）之和。&lt;/p&gt;

&lt;h3 id=&quot;所以有两层优化&quot;&gt;所以有两层”优化”&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;内层&lt;/strong&gt;（每个接触各自做，有闭式解）：给定 $v_c$，在 $R$ 范数下把 $y$ 投影到锥上，得到 $\gamma$。这一步编码了摩擦的物理：粘滞、滑动、分离三种情况。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;外层&lt;/strong&gt;（全局，用 Newton 法）：对整体速度 $v$ 最小化上面的总代价。每次 Newton 迭代都要调用内层来得到 $\gamma_i$ 和它的导数 $G_i = -\partial\gamma_i/\partial v_c$，然后解：&lt;/li&gt;
&lt;/ol&gt;

\[\Big(M + \sum_i J_i^\top G_i J_i\Big)\Delta v = -\text{梯度}\]

&lt;p&gt;所以，Kelvin–Voigt 给出的是&lt;strong&gt;法向的物理&lt;/strong&gt;，正则化给出的是&lt;strong&gt;切向的物理&lt;/strong&gt;，锥投影把两者拼在一起并加上 Coulomb 限制。最后，”$\nabla\ell = -\gamma$”这个性质把它们变成了一个可以用 Newton 法求解的凸优化问题。&lt;/p&gt;

&lt;h2 id=&quot;到底什么是在-r-范数下投影到-mathcal-f&quot;&gt;到底什么是“在 $R$ 范数下投影到 $\mathcal F$”&lt;/h2&gt;

&lt;p&gt;我们一个词一个词来，最后用一组具体数字算一遍。&lt;/p&gt;

&lt;h3 id=&quot;1-投影在集合里找离某个点最近的点&quot;&gt;1. “投影”：在集合里找离某个点最近的点&lt;/h3&gt;

&lt;p&gt;给定一个点 $y$ 和一个集合 $\mathcal F$：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;如果 $y$ 在 $\mathcal F$ 里，投影就是 $y$ 自己；&lt;/li&gt;
  &lt;li&gt;如果不在，就在 $\mathcal F$ 里找一个&lt;strong&gt;离 $y$ 最近&lt;/strong&gt;的点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;最简单的例子是投影到半直线 ${\gamma_n \ge 0}$ 上：$y_n = 3$ 就还是 3，$y_n = -2$ 最近的点是 0。结果就是 $\max(0, y_n)$。&lt;/p&gt;

&lt;h3 id=&quot;2-mathcal-f摩擦锥&quot;&gt;2. “$\mathcal F$”：摩擦锥&lt;/h3&gt;

&lt;p&gt;$\mathcal F = {\gamma : |\gamma_t| \le \mu\gamma_n}$，就是所有物理上允许的冲量：法向只能推，摩擦不能超过 $\mu$ 倍法向。为了画图，只看一个切向方向，它就是一个 V 字形区域：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;       γ_n
        ↑
   ╲    │    ╱
    ╲ 𝓕 │   ╱      边界：|γ_t| = μ γ_n
     ╲  │  ╱
      ╲ │ ╱
───────╲│╱────────→ γ_t
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;3-在-r-范数下用什么尺子量最近&quot;&gt;3. “在 $R$ 范数下”：用什么尺子量”最近”&lt;/h3&gt;

&lt;p&gt;“最近”取决于怎么量距离。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;欧氏距离&lt;/strong&gt;：$d^2 = \Delta\gamma_t^2 + \Delta\gamma_n^2$。到 $y$ 等距离的点组成&lt;strong&gt;圆&lt;/strong&gt;。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$R$ 范数&lt;/strong&gt;：$d^2 = R_t\,\Delta\gamma_t^2 + R_n\,\Delta\gamma_n^2$。到 $y$ 等距离的点组成&lt;strong&gt;椭圆&lt;/strong&gt;。$R_t$ 很小，所以椭圆沿 $\gamma_t$ 方向&lt;strong&gt;拉得很长&lt;/strong&gt;：在切向走很远，距离才增加一点点；在法向稍微动一下，距离就增加很多。&lt;/p&gt;

&lt;p&gt;投影的几何做法：以 &lt;em&gt;y&lt;/em&gt; 为中心画等距离曲线，慢慢放大，&lt;strong&gt;第一次碰到锥的那个点&lt;/strong&gt;就是投影结果 &lt;em&gt;γ&lt;/em&gt;。&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  欧氏：圆                        R 范数：扁椭圆
       γ_n                             γ_n
        ↑     ╱                         ↑     ╱
        │    ╱  ●γ                      │    ╱
        │   ╱ ╱  ╲                      │   ╱
        │  ╱ (  ×y )                  ──│●─(─────×y─────)
        │ ╱                             │╱γ
────────┼──────────→ γ_t       ─────────┼──────────→ γ_t
   圆往斜上方碰到锥边                 椭圆横着拉长，几乎水平地碰到锥边
   → γ_n 被抬高很多                    → γ_n 几乎不变，只砍了 γ_t
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;4-算一组数字&quot;&gt;4. 算一组数字&lt;/h3&gt;

&lt;p&gt;取 $\mu = 0.5$，$R_t = 0.01$，$R_n = 1$，试探冲量 $y = (y_t, y_n) = (4,\ 1)$。检查：$\lvert y_t\rvert = 4 &amp;gt; \mu y_n = 0.5$，在锥外面，需要投影。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;欧氏投影&lt;/strong&gt;：&lt;/p&gt;

\[\gamma_n = \frac{y_n + \mu|y_t|}{1+\mu^2} = \frac{1 + 2}{1.25} = 2.4,\qquad \gamma_t = 1.2\]

&lt;p&gt;法向冲量从 1 &lt;strong&gt;变成了 2.4&lt;/strong&gt;。只因为物体在滑，法向力就翻了一倍多，物理上说不通。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;$R$ 范数投影&lt;/strong&gt;（$\tilde\mu = \mu R_t/R_n = 0.005$）：&lt;/p&gt;

\[\gamma_n = \frac{y_n + \tilde\mu|y_t|}{1+\mu\tilde\mu} = \frac{1 + 0.02}{1.0025} \approx 1.017,\qquad \gamma_t = \mu\gamma_n \approx 0.509\]

&lt;p&gt;法向几乎没变，摩擦从 4 被截到了 0.509，正好落在锥边上，就是 Coulomb 滑动摩擦。&lt;/p&gt;

&lt;h3 id=&quot;5-为什么-r-范数这把尺子是对的&quot;&gt;5. 为什么 $R$ 范数这把尺子是对的&lt;/h3&gt;

&lt;p&gt;$y_t = 4$ 之所以这么大，是因为 $y_t = -v_t/R_t$，而 $R_t$ 被&lt;strong&gt;人为地&lt;/strong&gt;设得很小（极硬的粘性摩擦）。所以这个大数字只是正则化”放大”出来的，并不是一个真实的物理需求。&lt;/p&gt;

&lt;p&gt;$R$ 把冲量换算回速度：切向改动 $\Delta\gamma_t$，对应的速度只变 $R_t\Delta\gamma_t$，很小；法向改动 $\Delta\gamma_n$，对应的速度变 $R_n\Delta\gamma_n$，很大。$R$ 范数 $\Delta\gamma^\top R\,\Delta\gamma$ 的单位是”冲量 × 速度”，也就是能量。所以：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;&lt;strong&gt;在 $R$ 范数下投影 = 找一个物理上允许的冲量，使它和试探冲量之间的”能量差”最小。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;这个能量差会按每个方向各自的刚度来衡量，而不是把不同刚度放大出来的数字直接放在一起比较。&lt;/p&gt;

&lt;p&gt;写成公式就是图里 (26) 式的左半边：&lt;/p&gt;

\[\gamma = \arg\min_{\gamma\in\mathcal F}\ (\gamma - y)^\top R\,(\gamma - y)\]

&lt;p&gt;此外还有上一节说的数学上的好处：用这把尺子，才能保证 $\nabla_{v_c}\ell = -\gamma$，从而把整个时间步变成一个凸优化问题。&lt;/p&gt;

&lt;h3 id=&quot;y是不是就是投影前的gamma&quot;&gt;y是不是就是投影前的gamma&lt;/h3&gt;

&lt;p&gt;对，可以这么理解。$y$ 就是&lt;strong&gt;投影前的冲量&lt;/strong&gt;：&lt;/p&gt;

\[y = R^{-1}(\hat v - v_c)\]

&lt;p&gt;它是在&lt;strong&gt;不管摩擦锥限制&lt;/strong&gt;的情况下，只按弹簧阻尼（法向）和粘性摩擦（切向）算出来的冲量。投影之后才是真正用的冲量：&lt;/p&gt;

\[\gamma = P_{\mathcal F}(y)\]

&lt;ul&gt;
  &lt;li&gt;粘滞时，$y$ 本来就在锥里，$\gamma = y$，两者相同；&lt;/li&gt;
  &lt;li&gt;滑动时，$\gamma$ 是 $y$ 被截到锥面上的结果；&lt;/li&gt;
  &lt;li&gt;分离时，$\gamma = 0$。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;有一点要注意：$y$ 和 $\gamma$ 都&lt;strong&gt;依赖于当前的接触速度 $v_c$&lt;/strong&gt;，不是固定不变的数。Newton 法每迭代一次，$v$ 就会变，$v_c = Jv$ 跟着变，$y$ 要重新算，然后再投影得到新的 $\gamma$。代码里也是这样：&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_eval_contact_point&lt;/code&gt; 每次调用都先由 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vc&lt;/code&gt; 算出 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;y&lt;/code&gt;，再投影得到 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;gamma&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;所以准确地说，$y$ 是”&lt;strong&gt;在当前速度猜测下，投影前的冲量&lt;/strong&gt;“。&lt;/p&gt;

&lt;h2 id=&quot;还是没懂&quot;&gt;还是没懂&lt;/h2&gt;

&lt;p&gt;还是没懂，首先我在优化一个系统，一个包含frictional contact考量的系统，会有个未知的速度变量让我求解。然后我定义了法向冲量的规则，我能通过这个速度变量得到法向冲量。同时，我是不是也应该有一套规则，直接从速度变量算出切向冲量。但这个时候，我需要去检查，法向冲量和切向冲量耦合在一起是不是在摩擦锥里。&lt;/p&gt;

&lt;p&gt;对，你的理解完全正确。我按你的思路把它整理成一个流程，再补上最后一块。&lt;/p&gt;

&lt;h3 id=&quot;你说的流程&quot;&gt;你说的流程&lt;/h3&gt;

&lt;p&gt;给定速度变量的当前值 $v$，先算接触速度 $v_c = Jv$，然后：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 法向规则（Kelvin–Voigt）&lt;/strong&gt;：从速度直接算出法向冲量&lt;/p&gt;

\[y_n = R_n^{-1}(\hat v_n - v_n)\]

&lt;p&gt;&lt;strong&gt;2. 切向规则（粘性摩擦）&lt;/strong&gt;：从速度直接算出切向冲量&lt;/p&gt;

\[y_t = -v_t / R_t\]

&lt;p&gt;&lt;strong&gt;3. 检查&lt;/strong&gt;：两者合在一起，$(y_t, y_n)$ 在不在摩擦锥里？&lt;/p&gt;

&lt;p&gt;这三步就是你说的。&lt;strong&gt;$y$ 就是这两条规则直接算出来的结果，还没经过检查。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;你缺的是第-4-步检查不通过怎么办&quot;&gt;你缺的是第 4 步：检查不通过怎么办&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;4. 修正&lt;/strong&gt;：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;检查通过（$&lt;/td&gt;
          &lt;td&gt;y_t&lt;/td&gt;
          &lt;td&gt;\le \mu y_n$）：两条规则的结果都合法，直接用，$\gamma = y$。这时切向冲量确实就是粘性规则给出的，而这个粘性规则极硬，所以物体几乎不滑。&lt;strong&gt;这种情况起了个名字叫”粘滞”&lt;/strong&gt;。&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
  &lt;li&gt;检查不通过：要把它修正成锥里的某个点。&lt;strong&gt;投影就是”怎么修正”的规则&lt;/strong&gt;：找锥里离 $y$ 最近（$R$ 范数）的点。结果是摩擦被截到 $\mu\gamma_n$（滑动），或者全部为零（分离）。法向也可能被顺带微调一点。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;所以，1 到 4 合起来，就是一个完整的函数：&lt;/p&gt;

\[v \;\to\; v_c = Jv \;\to\; y \;\to\; \gamma = P_{\mathcal F}(y)\]

&lt;p&gt;&lt;strong&gt;输入速度，输出一个保证在锥里的冲量。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;容易混淆的一点检查不是优化问题的约束&quot;&gt;容易混淆的一点：检查不是优化问题的约束&lt;/h3&gt;

&lt;p&gt;你可能会以为”摩擦锥”是加在优化问题上的一个约束，即优化器要在满足锥约束的前提下去找 $v$。&lt;strong&gt;不是这样的。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;检查和修正都发生在&lt;strong&gt;冲量函数的内部&lt;/strong&gt;。不管优化器试的是什么 $v$，这个函数吐出来的 $\gamma$ 永远在锥里，因为它已经被投影过了。所以优化器本身没有任何约束：&lt;/p&gt;

\[\min_v\ \tfrac12\|v - v^*\|_M^2 + \sum_i \ell_i(J_i v)\]

&lt;p&gt;它只管找一个 $v$，使得动量方程 $M(v - v^*) = \sum_i J_i^\top\gamma_i(J_i v)$ 成立。这就是 SAP 名字里 “&lt;strong&gt;Unconstrained&lt;/strong&gt;” 的意思：锥的限制被藏进了 $\gamma(v_c)$ 这个函数，而不是作为优化问题的约束出现。&lt;/p&gt;

&lt;h2 id=&quot;切向规则是怎么定义出来的&quot;&gt;切向规则是怎么定义出来的&lt;/h2&gt;

&lt;p&gt;切向规则&lt;strong&gt;不是从物理推出来的&lt;/strong&gt;，而是&lt;strong&gt;人为选的&lt;/strong&gt;。法向规则有物理来源（弹簧阻尼），切向没有。下面说明它是怎么被”选”出来的。&lt;/p&gt;

&lt;h3 id=&quot;1-想要的东西写不成函数&quot;&gt;1. 想要的东西写不成函数&lt;/h3&gt;

&lt;p&gt;我们需要一条”从速度算出切向冲量”的规则，也就是一个函数 $\gamma_t = g(v_t)$。&lt;/p&gt;

&lt;p&gt;真实的静摩擦是：$v_t = 0$，冲量可以是 $[-\mu\gamma_n, \mu\gamma_n]$ 里的任意值。在 $v_t = 0$ 这一点上，一个速度对应无数个冲量，&lt;strong&gt;它不是函数&lt;/strong&gt;。所以没法直接用。&lt;/p&gt;

&lt;h3 id=&quot;2-那就选一个最简单的近似它的函数&quot;&gt;2. 那就选一个最简单的、近似它的函数&lt;/h3&gt;

&lt;p&gt;最简单的选择是一条过原点、很陡的直线：&lt;/p&gt;

\[\gamma_t = -\frac{v_t}{R_t}\]

&lt;p&gt;这相当于在切向加了一个阻尼器，阻尼系数 $c_t$ 很大：$f_t = -c_t v_t$，乘以 $h$ 得到冲量，于是 $R_t = 1/(h c_t)$。只要 $R_t$ 足够小，不滑就需要很大的冲量，所以 $v_t$ 会非常接近 0，这样就近似了 $v_t = 0$。&lt;/p&gt;

&lt;h3 id=&quot;3-为什么正好是这个形式&quot;&gt;3. 为什么正好是这个形式&lt;/h3&gt;

&lt;p&gt;关键是要&lt;strong&gt;和法向规则长得一样&lt;/strong&gt;。法向是：&lt;/p&gt;

\[\gamma_n = R_n^{-1}(\hat v_n - v_n)\]

&lt;p&gt;切向写成同样的形式，取 $\hat v_t = 0$（切向没有弹簧，没有”想回去的位置”，目标就是不滑）：&lt;/p&gt;

\[\gamma_t = R_t^{-1}(\hat v_t - v_t) = R_t^{-1}(0 - v_t)\]

&lt;p&gt;于是三个方向可以统一写成：&lt;/p&gt;

\[y = R^{-1}(\hat v - v_c),\qquad R = \text{diag}(R_t, R_t, R_n)\]

&lt;p&gt;这样统一的好处是：后面的投影（在 $R$ 范数下）、代价 $\ell = \tfrac12\gamma^\top R\gamma$，以及 $\nabla\ell = -\gamma$ 这个性质，都可以对三个方向一起成立。如果切向选了别的形状（比如 Lagged 那种光滑曲线），就要换一套构造方法。&lt;/p&gt;

&lt;h3 id=&quot;4-最后定-r_t-的大小&quot;&gt;4. 最后定 $R_t$ 的大小&lt;/h3&gt;

&lt;p&gt;这是唯一需要权衡的地方：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;太大：蠕滑明显，抓不住东西；&lt;/li&gt;
  &lt;li&gt;太小：条件数差，Newton 和 PCG 难收敛。&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;选 $R_t = \sigma w$，让蠕滑比例固定为约 $\sigma$，条件数固定为 $1/\sigma$，与物体轻重无关。这就是前面讲过的内容。&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;&lt;strong&gt;总结&lt;/strong&gt;：切向规则 = “用一个极硬的线性阻尼器近似静摩擦”。选线性，是为了和法向统一成 $R^{-1}(\hat v - v_c)$；选 $\hat v_t = 0$，是因为目标就是不滑；选 $R_t = \sigma w$，是为了在蠕滑和条件数之间取得一个与尺度无关的折中。&lt;/p&gt;

&lt;p&gt;MuJoCo 其实也是同样的思路：它在每个约束方向（包括摩擦方向）上都加了一个正则化项 $R$。SAP 的这套构造和它是一脉相承的。&lt;/p&gt;

&lt;h2 id=&quot;这两个速度是怎么回到摩擦锥的&quot;&gt;这两个速度是怎么回到摩擦锥的&lt;/h2&gt;

&lt;p&gt;先澄清一点：&lt;strong&gt;摩擦锥在冲量空间里，不在速度空间里&lt;/strong&gt;。速度本身不需要”回到锥里”，需要在锥里的是冲量。&lt;/p&gt;

&lt;h3 id=&quot;速度是怎么变成锥里的冲量的&quot;&gt;速度是怎么变成锥里的冲量的&lt;/h3&gt;

&lt;p&gt;分两步：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;第一步：两条规则把速度换成冲量&lt;/strong&gt;（这一步还不管锥）&lt;/p&gt;

\[v_c = (v_t, v_n) \;\longrightarrow\; y = \Big(-\frac{v_t}{R_t},\ \frac{\hat v_n - v_n}{R_n}\Big)\]

&lt;p&gt;&lt;strong&gt;第二步：在冲量空间里检查、投影&lt;/strong&gt;&lt;/p&gt;

\[y \;\longrightarrow\; \gamma = P_{\mathcal F}(y) \in \mathcal F\]

&lt;p&gt;所以，”回到锥里”的是 $y$（投影前的冲量），而不是速度。速度只是输入。&lt;/p&gt;

&lt;h3 id=&quot;反过来看三种情况在速度空间里长什么样&quot;&gt;反过来看：三种情况在速度空间里长什么样&lt;/h3&gt;

&lt;p&gt;把 $y$ 的定义代进三个区域的判断条件，就能直接用速度来写。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;粘滞&lt;/strong&gt;：$\lvert y_t \rvert \le \mu y_n$
\(\frac{\lvert v_t\rvert}{R_t} \le \mu\frac{\hat v_n - v_n}{R_n}
\quad\Longrightarrow\quad
\|v_t\| \le \hat\mu\,(\hat v_n - v_n),\qquad \hat\mu = \mu\frac{R_t}{R_n}\)
$\hat\mu$ 很小，所以&lt;strong&gt;粘滞时滑移速度必须非常小&lt;/strong&gt;，和法向压得多紧成正比。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;分离&lt;/strong&gt;：$y_n + \hat\mu,\lvert y_t\rvert \le 0$
\(\frac{\hat v_n - v_n}{R_n} + \mu\frac{R_t}{R_n}\cdot\frac{\|v_t\|}{R_t} \le 0
\quad\Longrightarrow\quad
v_n - \hat v_n \ge \mu\,\|v_t\|\)
也就是法向速度超过平衡点足够多，物体在分开。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;滑动&lt;/strong&gt;：介于两者之间。&lt;/p&gt;

&lt;p&gt;画在速度空间里（横轴是 $\lvert v_t\rvert$，纵轴是 $v_n - \hat v_n$）：&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  v_n − v̂_n
     ↑        ╱
     │ 分离  ╱   斜率 1/μ：v_n − v̂_n = μ‖v_t‖
     │     ╱
     │   ╱
     │ ╱       滑动
─────┼────────────────→ ‖v_t‖
     │╲
     │ 粘滞（一条极窄的楔形，斜率 1/μ̂，几乎贴着纵轴）
     ↓
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;两个空间对照：&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;冲量空间（$y$）&lt;/th&gt;
      &lt;th&gt;速度空间（$v_c$）&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;粘滞&lt;/td&gt;
      &lt;td&gt;摩擦锥内部 $\lvert y_t\rvert \le \mu y_n$&lt;/td&gt;
      &lt;td&gt;贴着纵轴的极窄楔形 $\lvert v_t\rvert \le \hat\mu(\hat v_n - v_n)$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;分离&lt;/td&gt;
      &lt;td&gt;锥尖下方的区域&lt;/td&gt;
      &lt;td&gt;锥 $v_n - \hat v_n \ge \mu\lvert v_t\rvert$&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;滑动&lt;/td&gt;
      &lt;td&gt;其他&lt;/td&gt;
      &lt;td&gt;其他&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;这两个图是对偶的：冲量空间里的摩擦锥，对应速度空间里的”分离锥”。&lt;/p&gt;

&lt;p&gt;分离条件 $v_n - \hat v_n \ge \mu\lvert v_t\rvert$ 还再次暴露了前面说的 gliding 伪影：滑得越快，物体需要更大的法向速度才算”分离”。这正是法向和切向在投影里耦合的结果。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>APIC (Beginner Guide)</title>
   <link href="https://blog.sigfeng.com/2025/11/04/mpm2"/>
   <updated>2025-11-04T00:00:00+01:00</updated>
   <id>https://blog.sigfeng.com/2025/11/04/MPM2</id>
   <content type="html">&lt;p&gt;&lt;em&gt;It’s been almost two years since I last wrote a blog. The reason I’m starting again is that I realized sometimes when I forget things, looking back at my own blog helps me recap very quickly. So I’m opening another recap log — this way, I can recap my recaps later on. And this time I will use LLM to polish my writing :)&lt;/em&gt;&lt;/p&gt;

&lt;h4 id=&quot;shape-functions&quot;&gt;Shape Functions&lt;/h4&gt;

&lt;p&gt;The shape function $w_{ip}^n$ is associated with grid node $i$, and the particle position at time step $n$ is denoted by $\boldsymbol{x}_p^n$. We choose a shape function that satisfies the following conditions:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Partition of unity&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

\[\sum_i w_{ip}^n = 1\]

&lt;blockquote&gt;
  &lt;p&gt;$\sum_i w_{ip}^n = 1$, but generally $\sum_p w_{ip}^n \neq 1$.&lt;br /&gt;
For example, if all particles are concentrated within a single cell, the corresponding nodes of that cell will accumulate large total weights.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Linear consistency&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

\[\sum_i w_{ip}^n \boldsymbol{x}_i^n = \boldsymbol{x}_p^n\]

&lt;p&gt;Typical shape functions that meet these requirements include &lt;strong&gt;linear interpolation functions&lt;/strong&gt;, &lt;strong&gt;barycentric coordinates&lt;/strong&gt;, and &lt;strong&gt;B-splines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Based on above properties, we can also derive that&lt;/p&gt;

\[\sum_i w_{ip}^n (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)=\boldsymbol{0}\]

&lt;h4 id=&quot;momentums&quot;&gt;Momentums&lt;/h4&gt;

&lt;p&gt;The total linear momentum on particles (before P2G)&lt;/p&gt;

\[\boldsymbol{p}_{tot}^{P,n}=\sum_p m_p\boldsymbol{v}_p^n\]

&lt;p&gt;The total linear momentum on the grid (after P2G)&lt;/p&gt;

\[\boldsymbol{p}_{tot}^{G,n}=\sum_i m_i^n\boldsymbol{v}_i^n\]

&lt;p&gt;The total angular momentum on the grid (after P2G)&lt;/p&gt;

\[\boldsymbol{L}_{tot}^{G,n}=\sum_i\boldsymbol{x}_i^n\times m_i^n\boldsymbol{v}_i^n\]

&lt;p&gt;The total linear momentum on the grid (after the grid dynamics, before G2P)&lt;/p&gt;

\[\boldsymbol{p}_{tot}^{G,n+1}=\sum_i m_i^n\boldsymbol{\tilde{v}}_i^{n+1}\]

&lt;p&gt;The total angular momentum on the grid (after the grid dynamics, before G2P)&lt;/p&gt;

\[\boldsymbol{L}_{tot}^{G,n+1}=\sum_i \boldsymbol{\tilde{x}}_i^{n+1}\times m_i^n\boldsymbol{\tilde{v}}_i^{n+1}\]

&lt;blockquote&gt;
  &lt;p&gt;When we consider conservation of angular momentum when transferring from grid to particles at the end of a time step, we need to consider angular momentum to be defined over moved grid nodes and we use the notation $\boldsymbol{\tilde{x}}_i^{n+1}=\boldsymbol{x}_i+\Delta t\boldsymbol{\tilde{v}}_i^{n+1}$ to indicate this.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One may notice that Eq. (5) gives&lt;/p&gt;

\[\boldsymbol{p}_{\mathrm{tot}}^{G,n+1} = \sum_i m_i^{n+1}\boldsymbol{v}_i^{n+1}, \notag\]

&lt;p&gt;while Eq. (7) defines it as&lt;/p&gt;

\[\boldsymbol{p}_{\mathrm{tot}}^{G,n+1} = \sum_i m_i^n\boldsymbol{\tilde{v}}_i^{n+1}. \notag\]

&lt;p&gt;A similar discrepancy can be observed between Eq. (6) and Eq. (8). Don’t worry! We will demonstrate the equivalence of these expressions in the following discussion.&lt;/p&gt;

&lt;h4 id=&quot;rpic-rigid-particle-in-cell&quot;&gt;RPIC (Rigid Particle-in-Cell)&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/media/2025-11-04-MPM2/image-20251104214114144.png&quot; alt=&quot;image-20251104214114144&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The transfer from particles to the grid are given by&lt;/p&gt;

\[\begin{align}
m_i^n&amp;amp;=\sum_p w_{ip}^nm_p \notag\\
\boldsymbol{K}_p^n&amp;amp;=\sum_j	 w_{jp}^n m_p (\boldsymbol{x}_j^n-\boldsymbol{x}_p^n)^\star(\boldsymbol{x}_j^n-\boldsymbol{x}_p^n)^{\star \top}\notag\\
m_i^n\boldsymbol{v}_i^n&amp;amp;=\sum_p w_{ip}^n m_p (\boldsymbol{v}_p^n + ((\boldsymbol{K}_p^n)^{-1}\boldsymbol{L}_p^n)\times(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n))\notag
\end{align}\]

&lt;p&gt;One may imagine this transfer as distributing the masses $w_{ip}^n m_p$ from a rigid body to the grid node $i$. $\boldsymbol{K}_p^n$ is the inertia tensor associated with the local rigid body represented by particle $p$.&lt;/p&gt;

&lt;p&gt;For any vectors $\boldsymbol{u}$ and $\boldsymbol{v}$, $\boldsymbol{u}^\star$ is defined to be the cross product matrix of $\boldsymbol{u}$, so that $\boldsymbol{u}^\star \boldsymbol{v}=\boldsymbol{u}\times\boldsymbol{v}$ and $(\boldsymbol{u}^\star)^\top\boldsymbol{v}=\boldsymbol{v}\times\boldsymbol{u}$.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;To be specific, the cross product matrix of a vector $\boldsymbol{u}$ is defined as&lt;/p&gt;

\[\boldsymbol{u}^\star =
\begin{bmatrix}
0 &amp;amp; -u_z &amp;amp; u_y \\
u_z &amp;amp; 0 &amp;amp; -u_x \\
-u_y &amp;amp; u_x &amp;amp; 0
\end{bmatrix}.\notag\]

  &lt;p&gt;Then,&lt;/p&gt;

\[\boldsymbol{u}^\star\boldsymbol{v} =
\begin{bmatrix}
0 &amp;amp; -u_z &amp;amp; u_y \\
u_z &amp;amp; 0 &amp;amp; -u_x \\
-u_y &amp;amp; u_x &amp;amp; 0
\end{bmatrix}
\begin{bmatrix}
v_x \\[2pt]
v_y \\[2pt]
v_z
\end{bmatrix}
=
\begin{bmatrix}
u_y v_z - u_z v_y \\
u_z v_x - u_x v_z \\
u_x v_y - u_y v_x
\end{bmatrix}
= \boldsymbol{u} \times \boldsymbol{v}.\notag\]

  &lt;p&gt;For Eq. (9), we have&lt;/p&gt;

\[\boldsymbol{r}^\star\boldsymbol{r}^{\star \top} =
\begin{bmatrix}
0 &amp;amp; -r_z &amp;amp; r_y \\
r_z &amp;amp; 0 &amp;amp; -r_x \\
-r_y &amp;amp; r_x &amp;amp; 0
\end{bmatrix}
\begin{bmatrix}
0 &amp;amp; r_z &amp;amp; -r_y \\
-r_z &amp;amp; 0 &amp;amp; r_x \\
r_y &amp;amp; -r_x &amp;amp; 0
\end{bmatrix}
=
\begin{bmatrix}
r_y^2 + r_z^2 &amp;amp; -r_x r_y &amp;amp; -r_x r_z \\
-r_x r_y &amp;amp; r_x^2 + r_z^2 &amp;amp; -r_y r_z \\
-r_x r_z &amp;amp; -r_y r_z &amp;amp; r_x^2 + r_y^2
\end{bmatrix}.\notag\]

  &lt;p&gt;Therefore,&lt;/p&gt;

\[\begin{align}
\boldsymbol{K}_p^n
&amp;amp;= \sum_i w_{ip}^n m_i^n
\begin{bmatrix}
r_y^2 + r_z^2 &amp;amp; -r_x r_y &amp;amp; -r_x r_z \\
-r_x r_y &amp;amp; r_x^2 + r_z^2 &amp;amp; -r_y r_z \\
-r_x r_z &amp;amp; -r_y r_z &amp;amp; r_x^2 + r_y^2
\end{bmatrix} \notag\\
&amp;amp;\approx
\begin{bmatrix}
\int_m (r_y^2 + r_z^2)\, dm &amp;amp; \int_m (-r_x r_y)\, dm &amp;amp; \int_m (-r_x r_z)\, dm \\
\int_m (-r_x r_y)\, dm &amp;amp; \int_m (r_x^2 + r_z^2)\, dm &amp;amp; \int_m (-r_y r_z)\, dm \\
\int_m (-r_x r_z)\, dm &amp;amp; \int_m (-r_y r_z)\, dm &amp;amp; \int_m (r_x^2 + r_y^2)\, dm
\end{bmatrix}= 
\begin{bmatrix}
I_{xx} &amp;amp; -I_{xy} &amp;amp; -I_{xz} \\
-I_{yx} &amp;amp; I_{yy} &amp;amp; -I_{yz} \\
-I_{zx} &amp;amp; -I_{zy} &amp;amp; I_{zz}
\end{bmatrix}
.\notag
\end{align}\]
&lt;/blockquote&gt;

&lt;p&gt;The transfer from the grid to particles are given by&lt;/p&gt;

\[\begin{align}
v_p^{n+1}&amp;amp;=\sum_i w_{ip}^n\boldsymbol{\tilde{v}}_i^{n+1} \notag\\
\boldsymbol{L}_p^{n+1}&amp;amp;=\sum_i	 w_{ip}^n(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\times m_p\boldsymbol{\tilde{v}}_i^{n+1}\notag
\end{align}\]

&lt;p&gt;The proofs for the preservation (or conservation) of the rigid-motion velocity field, linear momentum, and angular momentum are quite lengthy, so we will omit them here. For detailed derivations, please refer to the &lt;a href=&quot;https://alexey.stomakhin.com/research/siggraph2015_tech_report.pdf&quot;&gt;technical document&lt;/a&gt; of APIC. But basically, here are some take-away notes:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Preservation of the rigid-motion field&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;If the velocities before the transfer represent a rigid motion,&lt;/p&gt;

\[\boldsymbol{\tilde{v}}_i^{n+1} = \boldsymbol{v} + \boldsymbol{\omega} \times \boldsymbol{x}_i^n,\notag\]

    &lt;p&gt;where $ \boldsymbol{v} $ and $ \boldsymbol{\omega} $ are constant vectors, then after G2P we have&lt;/p&gt;

\[\boldsymbol{v}_p^{n+1} = \boldsymbol{v} + \boldsymbol{\omega} \times \boldsymbol{x}_p^n.\notag\]

    &lt;p&gt;The P2G operator also preserves this field.&lt;/p&gt;

    &lt;p&gt;Note that during G2P or P2G, since we typically use a B-spline kernel, each grid node or particle only exchanges physical quantities with its local $ 3\times3\times3 $ neighborhood. This means that in different $ 3\times3\times3 $ regions, the values of $ \boldsymbol{v} $ and $ \boldsymbol{\omega} $ can vary, as long as each local region represents a rigid-motion field.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Quadrature points with self-spin&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;Note that the total angular momentum on particles (before the particle-to-grid transfer at time $n$) represented by RPIC is&lt;/p&gt;

\[\boldsymbol{L}_{tot}^{P,G} = \sum_p (\boldsymbol{x}_p^n \times m_p \boldsymbol{v}_p^n + \boldsymbol{L}_p^n)\]

    &lt;p&gt;At first glance, one might wonder why there is an additional term $ \boldsymbol{L}_p^n $ given that $ \boldsymbol{x}_p^n \times m_p \boldsymbol{v}_p^n $ already appears in the expression. The reason is that this is not a point particle—it is a &lt;strong&gt;quadrature point&lt;/strong&gt; that represents a finite area (or volume). Therefore, the first term only accounts for the angular momentum of the center of mass, while the second term represents &lt;strong&gt;self-spin&lt;/strong&gt;, capturing the rotational motion relative to the center of mass.&lt;/p&gt;

    &lt;p&gt;This also explains how we distribute the linear momentum from particles to the grid&lt;/p&gt;

\[m_i^n \boldsymbol{v}_i^n = \sum_p w_{ip}^n m_p \left( \boldsymbol{v}_p^n + ((\boldsymbol{K}_p^n)^{-1} \boldsymbol{L}_p^n) \times (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n) \right) \notag\]

    &lt;p&gt;The term $ ((\boldsymbol{K}_p^n)^{-1} \boldsymbol{L}_p^n) \times (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n) $ represents the &lt;strong&gt;self-spinning velocity&lt;/strong&gt; relative to the quadrature point’s center, which should be incorporated to correct the actual linear velocity during the transfer.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;apic-affine-particle-in-cell&quot;&gt;APIC (Affine Particle-in-Cell)&lt;/h4&gt;

&lt;p&gt;&lt;img src=&quot;/media/2025-11-04-MPM2/image-20251105154107641.png&quot; alt=&quot;image-20251105154107641&quot; /&gt;&lt;/p&gt;

&lt;p&gt;APIC is quite similar to RPIC. However, instead of recovering a rigid motion field, it takes one step further — we now recover an &lt;strong&gt;affine motion field&lt;/strong&gt;&lt;/p&gt;

\[\boldsymbol{\tilde{v}}_i^{n+1} = \boldsymbol{v} + \boldsymbol{C} \boldsymbol{x}_i^n, \notag\]

&lt;p&gt;Here, $ \boldsymbol{C} $ is a matrix. The transfer from particles to the grid is given by&lt;/p&gt;

\[\begin{align}
m_i^n &amp;amp;= \sum_p w_{ip}^n m_p \notag \\
\boldsymbol{D}_p^n &amp;amp;= \sum_i w_{ip}^n (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)(\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)^\top \notag \\
m_i^n \boldsymbol{v}_i^n &amp;amp;= \sum_p w_{ip}^n m_p \left( \boldsymbol{v}_p^n + ((\boldsymbol{B}_p^n)^{-1} \boldsymbol{D}_p^n)(\boldsymbol{x}_i^n - \boldsymbol{x}_p^n) \right) \notag
\end{align}\]

&lt;p&gt;The transfer back to particles is given by&lt;/p&gt;

\[\begin{align}
\boldsymbol{v}_p^{n+1} &amp;amp;= \sum_i w_{ip}^n \boldsymbol{\tilde{v}}_i^{n+1} \notag \\
\boldsymbol{B}_p^{n+1} &amp;amp;= \sum_i w_{ip}^n \boldsymbol{\tilde{v}}_i^{n+1} (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)^\top \notag
\end{align}\]

&lt;p&gt;Analogous to how the angular velocity $ \boldsymbol{\omega} $ can be reconstructed via $ \boldsymbol{\omega} = \boldsymbol{K}^{-1} \boldsymbol{L} $ — where $ \boldsymbol{K} $ is the inertia tensor and $ \boldsymbol{L} $ is the angular momentum — we may ask how $ \boldsymbol{C} $ can be reconstructed.&lt;/p&gt;

&lt;p&gt;Is there an invariant quantity that characterizes $ \boldsymbol{C} $, similar to how the inertia tensor describes angular motion? Indeed, there is — it is captured by $ \boldsymbol{B} $ and $ \boldsymbol{D} $.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Minimal least squares for $ \boldsymbol{B} $ and $ \boldsymbol{D} $&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;One might wonder where $ \boldsymbol{B} $ and $ \boldsymbol{D} $ come from. We will derive them through a specific example and show that the resulting formula preserves both overall momentum and angular momentum.&lt;/p&gt;

    &lt;p&gt;Suppose we have only one particle (or equivalently, one quadrature point). Its underlying quadrature is represented by a grid ${\boldsymbol{x}_i}$. The particle has a position $\boldsymbol{x}_p$. Given that the affine motion is described by&lt;/p&gt;

\[\boldsymbol{v}(\boldsymbol{x}) = \boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x} - \boldsymbol{x}_p), \quad \forall \boldsymbol{x} \in \text{quadrature},\]

    &lt;p&gt;which also holds for each grid node $i$. Assume the velocity associated with node $i$ is $\boldsymbol{v}_i$. We want the momentum stored in the affine motion field to best approximate the momentum on the grid. That is, we aim to solve for $ \boldsymbol{C}_p $ and $ \boldsymbol{v}_p $ from&lt;/p&gt;

\[\mathop{\rm argmin}_{\boldsymbol{C}_p, \boldsymbol{v}_p} \sum_i \left\| w_{ip} m_p [\boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x}_i - \boldsymbol{x}_p)] - \sum_i m_i \boldsymbol{v}_i \right\|^2\]

    &lt;p&gt;We already know that the mass on the quadrature represented by grid node $i$ is $ w_{ip} m_p $ (some confusion might arise here; the key takeaway is that we can imagine a quadrature point as an equivalent grid element describing an area or volume. Even though we only have one particle, we store certain physical quantities on it, such as $ \boldsymbol{C} $, and these quantities are later transferred to the grid during the P2G step).&lt;/p&gt;

    &lt;p&gt;Therefore, Eq. (11) becomes&lt;/p&gt;

\[\mathop{\rm argmin}_{\boldsymbol{C}_p, \boldsymbol{v}_p} \sum_i \left\| w_{ip} m_p [\boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x}_i - \boldsymbol{x}_p)] - \sum_i w_{ip} m_p \boldsymbol{v}_i \right\|^2 \notag\]

    &lt;p&gt;Since $ m_p $ is a constant, we can drop it and denote the error as $ E $:&lt;/p&gt;

\[\begin{align}
\mathop{\rm argmin}_{\boldsymbol{C}_p, \boldsymbol{v}_p} E(\boldsymbol{C}_p, \boldsymbol{v}_p)
&amp;amp;= \mathop{\rm argmin}_{\boldsymbol{C}_p, \boldsymbol{v}_p} \sum_i \left\| w_{ip} [\boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x}_i - \boldsymbol{x}_p)] - \sum_i w_{ip} \boldsymbol{v}_i \right\|^2 \notag \\
&amp;amp;= \mathop{\rm argmin}_{\boldsymbol{C}_p, \boldsymbol{v}_p} [\boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x}_i - \boldsymbol{x}_p)]^\top [\boldsymbol{v}_p + \boldsymbol{C}_p (\boldsymbol{x}_i - \boldsymbol{x}_p)]
\end{align}\]

    &lt;p&gt;To find the minimum, we take derivatives of $ E $ with respect to $ \boldsymbol{C}_p $ and $ \boldsymbol{v}_p $, and set them to zero:&lt;/p&gt;

\[\frac{\partial E}{\partial \boldsymbol{C}_p} = 2 \sum_i w_{ip} (\boldsymbol{v}_p + \boldsymbol{C}_p \boldsymbol{r}_i - \boldsymbol{v}_i) \boldsymbol{r}_i^\top = 0\]

\[\frac{\partial E}{\partial \boldsymbol{v}_p} = 2 \sum_i w_{ip} (\boldsymbol{v}_p + \boldsymbol{C}_p \boldsymbol{r}_i - \boldsymbol{v}_i) = 0\]

    &lt;p&gt;Since we have
\(\sum_i w_{ip} = 1\notag\)
 and
\(\sum_i w_{ip} (\boldsymbol{x}_i - \boldsymbol{x}_p) = \boldsymbol{0},\notag\)
 from Eq. (14) we obtain&lt;/p&gt;

\[\boldsymbol{v}_p = \sum_i w_{ip} \boldsymbol{v}_i.\]

    &lt;p&gt;From Eq. (13), we get&lt;/p&gt;

\[\sum_i w_{ip} (\boldsymbol{v}_i - \boldsymbol{v}_p) \boldsymbol{r}_i^\top = \boldsymbol{C}_p \sum_i w_{ip} \boldsymbol{r}_i \boldsymbol{r}_i^\top.\]

    &lt;p&gt;Denoting&lt;/p&gt;

\[\boldsymbol{D}_p = \sum_i w_{ip} \boldsymbol{r}_i \boldsymbol{r}_i^\top\]

    &lt;p&gt;and&lt;/p&gt;

\[\boldsymbol{B}_p = \sum_i w_{ip} (\boldsymbol{v}_i - \boldsymbol{v}_p) \boldsymbol{r}_i^\top,\]

    &lt;p&gt;we finally have&lt;/p&gt;

\[\boldsymbol{C}_p = \boldsymbol{B}_p (\boldsymbol{D}_p)^{-1}.\notag\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Grid averaging effect&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;The derivation above applies to a single particle. When we have many particles, even if each particle is initially assigned a distinct affine motion $ \boldsymbol{C}_p $, after the P2G transfer, each grid node is influenced by multiple particles. As a result, the grid effectively represents an &lt;em&gt;average&lt;/em&gt; affine motion field across those particles. Consequently, during the subsequent G2P transfer, the affine motion field reconstructed for each quadrature point &lt;strong&gt;will no longer&lt;/strong&gt; match its previous state exactly.&lt;/p&gt;

    &lt;p&gt;However, if we begin from the grid — where the affine motion field is already stored — and perform G2P, each resulting particle will still represent an affine motion consistent with the others. After performing P2G again, the grid recovers the same affine motion field.&lt;/p&gt;

    &lt;p&gt;The key takeaway is that different particles should not carry significantly different affine motions. Otherwise, these variations will be averaged out on the grid during P2G and cannot be maintained across transfers.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Conservation of momentum&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;The good news is that, despite this averaging effect, the total linear and angular momentum of the system remain conserved. For detailed proof, please refer to the &lt;a href=&quot;https://alexey.stomakhin.com/research/siggraph2015_tech_report.pdf&quot;&gt;technical document&lt;/a&gt; of APIC.&lt;/p&gt;
    &lt;blockquote&gt;
      &lt;p&gt;The total angular momentum on particles represented by APIC is given by&lt;/p&gt;

\[\boldsymbol{L}_{tot}^{P,n} = \sum_p \boldsymbol{x}_p \times (m_p \boldsymbol{v}_p) + \sum_p m_p (\boldsymbol{B}_p^n)^\top : \boldsymbol{\epsilon}\]

      &lt;p&gt;Here, we use the permutation tensor $ \boldsymbol{\epsilon} $ to convert a cross product into a tensor contraction. Specifically,&lt;/p&gt;

\[\boldsymbol{u} \times \boldsymbol{v} = (\boldsymbol{v} \boldsymbol{u}^\top)^\top : \boldsymbol{\epsilon}\]

      &lt;p&gt;For a matrix $ \boldsymbol{A} = { A_{\alpha\beta} } $, we have&lt;/p&gt;

\[(\boldsymbol{A} : \boldsymbol{\epsilon})_\gamma = \sum_{\alpha, \beta} A_{\alpha\beta} \epsilon_{\alpha\beta\gamma}\]

      &lt;p&gt;For a $ 3 \times 3 $ matrix, this expands to&lt;/p&gt;

\[\begin{align}
(\boldsymbol{A} : \boldsymbol{\epsilon})_1 &amp;amp;= A_{23} - A_{32} \notag\\
(\boldsymbol{A} : \boldsymbol{\epsilon})_2 &amp;amp;= A_{31} - A_{13} \notag\\
(\boldsymbol{A} : \boldsymbol{\epsilon})_3 &amp;amp;= A_{12} - A_{21} \notag
\end{align}\]

      &lt;p&gt;Therefore,&lt;/p&gt;

\[(\boldsymbol{v} \boldsymbol{u}^\top)^\top : \boldsymbol{\epsilon}
=
\begin{bmatrix}
u_1 v_1 &amp;amp; u_1 v_2 &amp;amp; u_1 v_3 \notag\\
u_2 v_1 &amp;amp; u_2 v_2 &amp;amp; u_2 v_3 \notag\\
u_3 v_1 &amp;amp; u_3 v_2 &amp;amp; u_3 v_3 \notag
\end{bmatrix}
: \boldsymbol{\epsilon}
=
\begin{pmatrix}
u_2 v_3 - u_3 v_2 \\
u_3 v_1 - u_1 v_3 \\
u_1 v_2 - u_2 v_1
\end{pmatrix}
= \boldsymbol{u} \times \boldsymbol{v}\]

      &lt;p&gt;Since&lt;/p&gt;

\[\boldsymbol{B}_p = \sum_i w_{ip} (\boldsymbol{v}_i - \boldsymbol{v}_p) \boldsymbol{r}_i^\top,\notag\]

      &lt;p&gt;we have&lt;/p&gt;

\[\begin{align}
\sum_p m_p (\boldsymbol{B}_p^n)^\top : \boldsymbol{\epsilon}
&amp;amp;= \sum_p m_p \left[ \sum_i (\boldsymbol{v}_i - \boldsymbol{v}_p) \boldsymbol{r}_i^\top \right]^\top : \boldsymbol{\epsilon} \notag\\
&amp;amp;= \sum_{i,p} m_p [(\boldsymbol{v}_i - \boldsymbol{v}_p) \boldsymbol{r}_i^\top]^\top : \boldsymbol{\epsilon} \notag\\
&amp;amp;= \sum_{i,p} m_p \boldsymbol{r}_i \times (\boldsymbol{v}_i - \boldsymbol{v}_p) \notag
\end{align}\]

      &lt;p&gt;This explains the presence of the second term in the total angular momentum expression.&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Constant $ \boldsymbol{D} $&lt;/strong&gt;&lt;/p&gt;

    &lt;p&gt;There is another good piece of news. The formula&lt;/p&gt;

\[\boldsymbol{D}_p^n = \sum_i w_{ip}^n (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)(\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)^\top \notag\]

    &lt;p&gt;can be simplified. For the quadratic B-spline kernel,&lt;/p&gt;

\[w(r) =
\begin{cases}
\dfrac{3}{4} - r^2, &amp;amp; |r| &amp;lt; \dfrac{1}{2}, \\[6pt]
\dfrac{1}{2}\left(\dfrac{3}{2} - |r|\right)^2, &amp;amp; \dfrac{1}{2} \le |r| &amp;lt; \dfrac{3}{2}, \\[6pt]
0, &amp;amp; |r| \ge \dfrac{3}{2}.
\end{cases}\notag\]

\[\begin{align}
\boldsymbol{D}_p^n &amp;amp;= \sum_i w_{ip}^n 
\begin{pmatrix}
\boldsymbol{r}_1 \Delta x \\
\boldsymbol{r}_2 \Delta x
\end{pmatrix}
\begin{pmatrix}
\boldsymbol{r}_1 \Delta x &amp;amp; \boldsymbol{r}_2 \Delta x
\end{pmatrix} \notag\\
&amp;amp;= \Delta x^2 \sum_i w_{ip}^n 
\begin{pmatrix}
\boldsymbol{r}_1 \\
\boldsymbol{r}_2
\end{pmatrix}
\begin{pmatrix}
\boldsymbol{r}_1 &amp;amp; \boldsymbol{r}_2
\end{pmatrix} \notag
\end{align}\]

    &lt;p&gt;This quantity is constant and invariant to geometry or kernel. Instead of deriving it analytically, we can confirm this numerically with the following code:&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kn&quot;&gt;import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numpy&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;as&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;quadratic_b_spline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.75&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
  
&lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cubic_b_spline&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;):&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;elif&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;return &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;abs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;r&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;**&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.0&lt;/span&gt;
  
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;linspace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;meshgrid&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;stack&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;reshape&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;kernel&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cubic_b_spline&lt;/span&gt;  &lt;span class=&quot;c1&quot;&gt;# or quadratic_b_spline
&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;xp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;random&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;uniform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Dp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;zeros&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;((&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xi&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xp&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;kernel&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;dx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_&lt;/span&gt; &lt;span class=&quot;ow&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;range&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)])&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;wip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;np&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prod&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;wip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Dp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;wip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[:,&lt;/span&gt; &lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;@&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;bp&quot;&gt;None&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:])&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Dp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;For the cubic kernel&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;3.33333333e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.08149167e-17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;8.94466792e-19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.08149167e-17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;3.33333333e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.13841228e-17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;8.94466792e-19&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.13841228e-17&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;3.33333333e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;which is $\frac{1}{3}\Delta x^2$. For the quadratic kernel&lt;/p&gt;

    &lt;div class=&quot;language-python highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;array&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;([[&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;2.50000000e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.94767466e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.60208521e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.94767466e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;2.50000000e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;9.74426703e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;2.60208521e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;9.74426703e-18&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;  &lt;span class=&quot;mf&quot;&gt;2.50000000e-01&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;

    &lt;p&gt;which is $\frac{1}{4}\Delta x^2$. So we have&lt;/p&gt;

\[\begin{align}
\boldsymbol{C}_p^{n+1} &amp;amp;= \boldsymbol{B}_p^{n+1} (\boldsymbol{D}_p^{n+1})^{-1}.\notag\\
&amp;amp;=\frac{4}{\Delta x^2}\sum_i w_{ip}^n \boldsymbol{\tilde{v}}_i^{n+1} (\boldsymbol{x}_i^n - \boldsymbol{x}_p^n)^\top
\end{align}\]

    &lt;p&gt;for quadratic kernel.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;mls-mpm&quot;&gt;MLS-MPM&lt;/h4&gt;

&lt;p&gt;For &lt;strong&gt;weak form&lt;/strong&gt;, &lt;strong&gt;evolve of internal force&lt;/strong&gt;,&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Projective Dynamics</title>
   <link href="https://blog.sigfeng.com/2023/10/13/projective-dynamics"/>
   <updated>2023-10-13T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2023/10/13/Projective-Dynamics</id>
   <content type="html">&lt;h2 id=&quot;mass-spring-system-and-implicit-method&quot;&gt;Mass-spring System And Implicit Method&lt;/h2&gt;

&lt;p&gt;For node $i$ with mass $m_i$ experiencing force $\boldsymbol{f_i}$&lt;/p&gt;

\[\begin{align}
&amp;amp;m_i\boldsymbol{v_i^{t+1}}=m_i\boldsymbol{v_i^{t}}+\Delta t\boldsymbol{f_i^{t+1}}\\
&amp;amp;\boldsymbol{x_i^{t+1}}=\boldsymbol{x_i^{t}}+\Delta t\boldsymbol{v_i^{t+1}}\\
\end{align}\]

&lt;p&gt;which goes to&lt;/p&gt;

\[\boldsymbol{x_i^{t+1}}=\boldsymbol{x_i^{t}}+\Delta t\boldsymbol{v_i^{t}}+\frac{1}{m_i}\Delta t^2\boldsymbol{f_i^{t+1}}\]

&lt;p&gt;Gathering all nodes we have&lt;/p&gt;

\[\boldsymbol{x^{t+1}}=\boldsymbol{x^{t}}+\Delta t\boldsymbol{v^{t}}+M^{-1}\Delta t^2\boldsymbol{f^{t+1}}\]

&lt;p&gt;where&lt;/p&gt;

\[\boldsymbol{x^{t+1}}=\begin{pmatrix}
\boldsymbol{x_0^{t+1}}\\
\boldsymbol{x_1^{t+1}}\\
...
\end{pmatrix},\boldsymbol{v^{t+1}}=\begin{pmatrix}
\boldsymbol{v_0^{t+1}}\\
\boldsymbol{v_1^{t+1}}\\
...
\end{pmatrix},\boldsymbol{f^{t+1}}=\begin{pmatrix}
\boldsymbol{f_0^{t+1}}\\
\boldsymbol{f_1^{t+1}}\\
...
\end{pmatrix}\]

&lt;p&gt;and&lt;/p&gt;

\[M=\begin{pmatrix}
m_0\\
&amp;amp;m_0\\
&amp;amp;&amp;amp;m_0\\
&amp;amp;&amp;amp;&amp;amp;m_1\\
&amp;amp;&amp;amp;&amp;amp;&amp;amp;m_1\\
&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;m_1\\
&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;&amp;amp;...
\end{pmatrix}\]

&lt;p&gt;Considering $\boldsymbol{x^{t+1}}$ as unkown variable to be found, we have&lt;/p&gt;

\[\begin{align}
&amp;amp;\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}}-M^{-1}\Delta t^2\boldsymbol{f(x)}=\boldsymbol{0}\\
&amp;amp;\frac{1}{\Delta t^2}M(\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}})-\boldsymbol{f(x)}=\boldsymbol{0}
\end{align}\]

&lt;p&gt;Construct a function&lt;/p&gt;

\[F(\boldsymbol{x})=\frac{1}{2\Delta t^2}||\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}}||^2_{M}+E(\boldsymbol{x}),\ ||a||_M^2=a^TMa\]

&lt;p&gt;where $\partial E/\partial \boldsymbol{x}=-\boldsymbol{f}$, we will see that&lt;/p&gt;

\[\begin{align}
\frac{\partial F(\boldsymbol{x})}{\partial \boldsymbol{x}}&amp;amp;=\frac{1}{2\Delta t^2}(M+M^T)(\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}})-\boldsymbol{f(x)}\\
&amp;amp;=\frac{1}{\Delta t^2}M(\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}})-\boldsymbol{f(x)}
\end{align}\]

&lt;p&gt;Or in some senarios we can also write it with respect to $\boldsymbol{v}$&lt;/p&gt;

\[\begin{align}
\frac{\partial F}{\partial \boldsymbol{v}}&amp;amp;=\frac{\partial F}{\partial \boldsymbol{x}}\frac{\partial \boldsymbol{x}}{\partial \boldsymbol{v}}=\frac{\partial F}{\partial \boldsymbol{x}}\frac{\partial (\boldsymbol{x^t}+\Delta t \boldsymbol{v})}{\partial \boldsymbol{v}}=\Delta t\frac{\partial F}{\partial \boldsymbol{x}}\\
&amp;amp;=\frac{1}{\Delta t}M(\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}})-\Delta t\boldsymbol{f(x)}\\
&amp;amp;=M(\boldsymbol{v}-\boldsymbol{v^t})-\Delta t\boldsymbol{f(x)}
\end{align}\]

&lt;p&gt;so we are finding $\boldsymbol{x}$ such that $\frac{\partial F(\boldsymbol{x})}{\partial \boldsymbol{x}}=0$, which is also a necessary condition for local minimum of $F$. That is, if we find a $\boldsymbol{x}$ at which $F$ is locally minimal, we find a $\boldsymbol{x}$ such that $\frac{\partial F(\boldsymbol{x})}{\partial \boldsymbol{x}}=0$. Finding local minimum can be done by Newton-Raphson’s method.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231007132955916.png&quot; alt=&quot;image-20231007132955916&quot; /&gt;&lt;/p&gt;

&lt;p&gt;We will need to calculate the hessian of $F$.&lt;/p&gt;

\[\frac{\partial^2 F}{\partial \boldsymbol{x}^2}=\frac{1}{\Delta t^2}M+\partial \boldsymbol{f}/\partial \boldsymbol{x}=\frac{1}{\Delta t^2}M+H\]

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231007133226058.png&quot; alt=&quot;image-20231007133226058&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;position-based-dynamics&quot;&gt;Position Based Dynamics&lt;/h2&gt;

&lt;p&gt;High stiffness can cause problem when updating position using newton’s law (time and force). If we have another rule to directly manuplate position while conserving neccesary physical quantity (e.g. momentum), the problem can be solved. That is PBD. PBD is non-physical.&lt;/p&gt;

&lt;p&gt;We will define the constraint as&lt;/p&gt;

\[C_{*}(\boldsymbol{x}) = ||\boldsymbol{x_i}-\boldsymbol{x_j}||-d\]

&lt;p&gt;where $d$ is the rest length between node $i$ and $j$. In general, we want all nodes satisfying constraints&lt;/p&gt;

\[\boldsymbol{C(x)}=\begin{pmatrix}
C_0(\boldsymbol{x})\\
C_1(\boldsymbol{x})\\
...
\end{pmatrix}\]

&lt;p&gt;But sadly in practice, we usually cannot satisfy all constraints at once, and must iterate over all constraints. Take one constraint $C_i$ for example. If the system has experienced an external force and moved to $\boldsymbol{x^{new}}$, the constraint is still expected to be satisfied&lt;/p&gt;

\[\boldsymbol{C_i(x^{new})}=\boldsymbol{C_i(x+\Delta x)}\approx\boldsymbol{C_i(x)}+\frac{\partial \boldsymbol{C_i}}{\partial \boldsymbol{x}}\Delta\boldsymbol{x}=\boldsymbol{C_i(x)}+\nabla\boldsymbol{C_i}\Delta\boldsymbol{x}=\boldsymbol{0}\]

&lt;p&gt;Here PBD makes an assumption: the direction of $\Delta\boldsymbol{x}$ is same with gradient of $\boldsymbol{C}_i$&lt;/p&gt;

\[\Delta\boldsymbol{x}=\lambda(\nabla\boldsymbol{C_i})^T\]

&lt;p&gt;Substitute it into previous equation and we will get&lt;/p&gt;

\[\boldsymbol{\lambda}=-\frac{\boldsymbol{C_i}}{||\nabla \boldsymbol{C_i}||^2}\]

&lt;p&gt;and&lt;/p&gt;

\[\Delta\boldsymbol{x}=\lambda(\nabla\boldsymbol{C_i})^T=-\frac{\boldsymbol{C_i}}{||\nabla \boldsymbol{C_i}||^2}(\nabla\boldsymbol{C_i})^T\]

&lt;p&gt;For the correction of an individual point $\boldsymbol{x_{k}}$, we have&lt;/p&gt;

\[\Delta\boldsymbol{x}_k=-\frac{\boldsymbol{C_i}}{||\nabla \boldsymbol{C_i}||^2}(\nabla_{\boldsymbol{x}_k}\boldsymbol{C_i})^T=s_i(\nabla_{\boldsymbol{x}_k}\boldsymbol{C_i})^T\]

&lt;p&gt;where the scaling factor&lt;/p&gt;

\[s_i = -\boldsymbol{C_i} / ||\nabla \boldsymbol{C_i}||^2\]

&lt;p&gt;If the points have individual masses, we weight the corrections $\Delta\boldsymbol{x_k}$ by the inverse masses $w_k=1/m_k$&lt;/p&gt;

\[\Delta\boldsymbol{x}_k=\lambda w_k(\nabla_{\boldsymbol{x}_k}\boldsymbol{C_i})^T\]

&lt;p&gt;Finally, we have&lt;/p&gt;

\[s_i=\frac{\boldsymbol{C_i}}{\sum_kw_k||\nabla_{\boldsymbol{x}_k} \boldsymbol{C_i}||^2}\]

&lt;p&gt;and&lt;/p&gt;

\[\Delta\boldsymbol{x}_k=s_i w_k(\nabla_{\boldsymbol{x}_k}\boldsymbol{C_i})^T\]

&lt;p&gt;Following is a concrete example in one-spring case.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231007165514488.png&quot; alt=&quot;image-20231007165514488&quot; /&gt;&lt;/p&gt;

&lt;p&gt;What about multiple springs? The Gauss-Seidel approach projects each spring sequentially in a certain order. We cannot ensure the satisfaction of every constraint. But the more iterations we use, the better those constraints are satisfied.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231007165725897.png&quot; alt=&quot;image-20231007165725897&quot; /&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Instead of representing all the elastic potential as a penalty-like term, PBD treats all the constraints as infinitely hard constraints.  For instance, in a mass-spring system simulation,  PBD also treats all springs as inextensible springs.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;projective-dynamics&quot;&gt;Projective Dynamics&lt;/h2&gt;

&lt;p&gt;For simplification, we will denote $\boldsymbol{y}:=\boldsymbol{x^{t}}+\Delta t\boldsymbol{v^{t}}$, so&lt;/p&gt;

\[\begin{align}
F(\boldsymbol{x})&amp;amp;=\frac{1}{2\Delta t^2}||\boldsymbol{x}-\boldsymbol{x^{t}}-\Delta t\boldsymbol{v^{t}}||^2_{M}+E(\boldsymbol{x})\\
&amp;amp;=\frac{1}{2\Delta t^2}(\boldsymbol{x-y})^TM(\boldsymbol{x-y})+E(\boldsymbol{x})
\end{align}\]

&lt;blockquote&gt;
  &lt;p&gt;The optimization problem offers an interesting insight into Position Based Dynamics. If we define an energy potential EP BD as the sum of squares of the PBD constraints, we notice that the PBD constraint projection solver attempts to minimize F using a Gauss-Seidel-like method. Unfortunately, the PBD solver does not take the inertial term explicitly into account, and further the projection of each individual constraint is not guaranteed to decrease energy because the effect of the remaining constraints is ignored.  Nevertheless, PBD is typically quite effective at reducing F  and can be therefore understood as a heuristic variant of the implicit Euler method.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3 id=&quot;mass-spring-system&quot;&gt;Mass-spring System&lt;/h3&gt;

&lt;p&gt;We wll start introducing PD from simple mass-spring system containing $n$ vertices and $m$ springs.&lt;/p&gt;

&lt;p&gt;In the first step, we wll reformulate elastic energy&lt;/p&gt;

\[E=\frac{k}{2}(||\boldsymbol{x_1-x_2}-r||)^2\]

&lt;p&gt;where $\boldsymbol{x_1,x_2}$ are spring endpoints, $r$ is the rest length, and $k$ is the spring stiffness. The key to reformulation is the following fact showing that the spring potential is a solution to a specially designed constrained minimization problem.&lt;/p&gt;

\[{\rm min}\ (||\boldsymbol{x}||-r)^2=\mathop{\rm min}_{||\boldsymbol{d}||=r}\ ||\boldsymbol{x}-\boldsymbol{d}||^2\]

&lt;p&gt;That is, from&lt;/p&gt;

\[{\rm min}\ F(\boldsymbol{x})={\rm min}\ \frac{1}{2\Delta t^2}(\boldsymbol{x-y})^TM(\boldsymbol{x-y})+\sum_{i=1}^m\frac{k_i}{2}(||\boldsymbol{x_{i_1}-x_{i_2}}||-r_i)^2\]

&lt;p&gt;we goes to&lt;/p&gt;

\[{\rm min}\ F(\boldsymbol{x})=\mathop{\rm min}_{||\boldsymbol{d_i}||=r_i}\ \frac{1}{2\Delta t^2}(\boldsymbol{x-y})^TM(\boldsymbol{x-y})+\sum_{i=1}^m\frac{k_i}{2}||\boldsymbol{x_{i_1}-x_{i_2}-d_i}||^2\]

&lt;p&gt;where $i$ indicates the i-th spring and $i_{1},i_{2}$ indicates two end points of the i-th spring.&lt;/p&gt;

&lt;p&gt;It might sound like a bad idea to reformulate our original unconstrained minimization problem into a constrained optimization problem with an extra auxilary variable $\boldsymbol{d_i}$ which almost doubles the degrees of the freedom of the system. However, the new problem can be really easily solved by alternating optimization (also known as local-global optimization). In the local step, we fix $\boldsymbol{x}$ and find $\boldsymbol{d}$. In the global step, we fix $\boldsymbol{d}$ and find $\boldsymbol{x}$ by solving a large convex quadratic optimization problem iteratively.&lt;/p&gt;

\[\boldsymbol{x}=(\frac{M}{\Delta t^2}+L)^{-1}(\frac{M}{\Delta t^2}\boldsymbol{y}+J\boldsymbol{d})\]

&lt;p&gt;where&lt;/p&gt;

\[\begin{align}
E(\boldsymbol{x})&amp;amp;=\sum_{i=1}^m\frac{k_i}{2}||\boldsymbol{x_{i_1}-x_{i_2}-d_i}||^2\\
&amp;amp;=\sum_{i=1}^m\frac{k_i}{2}(\boldsymbol{x_{i_1}-x_{i_2}})^T(\boldsymbol{x_{i_1}-x_{i_2}})-k_i(\boldsymbol{x_{i_1}-x_{i_2}})^T\boldsymbol{d_i}+C\\
&amp;amp;=\frac{1}{2}\boldsymbol{x}^TL\boldsymbol{x}-\boldsymbol{x}^TJ\boldsymbol{d}+C\\
L=&amp;amp;\sum_{i=1}^mk_iG_iG_i^T,\ J=\sum_{i=1}^mk_iG_iS_i^T
\end{align}\]

&lt;p&gt;where $G_i\in R^{n\times1}$ is the incidence vector of the i-th spring, i.e. $G_i(i_1)=1$ and $G_i(i_2)=-1$ and zero otherwise. Similarly, $S_i\in R^{m\times1}$ is a indicator vector for the i-th spring, $S_i=\delta_i$. Based on this, we have&lt;/p&gt;

\[\begin{align}
x_{i_1}-x_{i_2}=G_i^T\boldsymbol{x}\\
d_i=S_i^T\boldsymbol{d}
\end{align}\]

&lt;p&gt;Notice here we only discuss one dimension (x/y/z). If considering 3 dimensions, we will need to do $L^3=L\otimes I_{33},G^3=G\otimes I_{33}$. $\otimes$ denotes Kronecker product. It is easy to see both $M/\Delta t^2$ and $L$ are symmetric and positive semi-definite.&lt;/p&gt;

&lt;h3 id=&quot;arap-material&quot;&gt;ARAP Material&lt;/h3&gt;

&lt;p&gt;We start from how to calculate deformation gradient $F$ for a tetrahedron with 4 vertices $\boldsymbol{x_1,x_2,x_3,x_4}$ in deformed state and $\boldsymbol{X_1,X_2,X_3,X_4}$ in reference state . We know that&lt;/p&gt;

\[\begin{align}
\boldsymbol{x_1-x_4}&amp;amp;=F(\boldsymbol{X_1-X_4})\\
\boldsymbol{x_2-x_4}&amp;amp;=F(\boldsymbol{X_2-X_4})\\
\boldsymbol{x_3-x_4}&amp;amp;=F(\boldsymbol{X_3-X_4})\\
\end{align}\]

&lt;p&gt;So $F=D_sD_m^{-1}$ where $D_s=[\boldsymbol{x_1-x_4};\boldsymbol{x_2-x_4};\boldsymbol{x_3-x_4}]$ and $D_m=[\boldsymbol{X_1-X_4};\boldsymbol{X_2-X_4};\boldsymbol{X_3-X_4}]$.&lt;/p&gt;

&lt;p&gt;For an object containing $m$ tetrahedrons and $n$ vertices, we denote $F_i^T=D_{mi}^{-T}D_{si}^T$ for i-th tetrahedron where $D_{si}^T\in R^{3\times3}$ can be represented as a linear combination of the position vector $\boldsymbol{x}$ (note here $\boldsymbol{x} \in R^{n\times 3}$ is different from above in $R^{3n\times 1}$) as $D_{si}^T=A_i\boldsymbol{x}$ where&lt;/p&gt;

\[A_i=\begin{bmatrix}
...&amp;amp;1&amp;amp;...&amp;amp;0&amp;amp;...&amp;amp;0&amp;amp;...&amp;amp;-1&amp;amp;...\\
...&amp;amp;0&amp;amp;...&amp;amp;1&amp;amp;...&amp;amp;0&amp;amp;...&amp;amp;-1&amp;amp;...\\
...&amp;amp;0&amp;amp;...&amp;amp;0&amp;amp;...&amp;amp;1&amp;amp;...&amp;amp;-1&amp;amp;...\\
\end{bmatrix}\]

&lt;p&gt;where the non-zero columns are corresponding to the indices of the first second third and fourth vertex of the i-th element. If we denote $G_i^T=D_{mi}^{-T}A_i$ then $F_i^T=G_i^T\boldsymbol{x}$.&lt;/p&gt;

&lt;p&gt;Consider we now define potential density as&lt;/p&gt;

\[\psi(F(\boldsymbol{x}))=\mathop{\rm min}_{p\in SO(3)}||F-p||_F^2=||F-R||_F^2\]

&lt;p&gt;where $F=RS$ is the polar decomposition of $F$. So the potential is equivalent with ARAP. Then&lt;/p&gt;

\[E(\boldsymbol{x})=\frac{1}{2}\sum_iV_ik_i\mathop{\rm min}_{p\in SO(3)}||F_i-p_i||_F^2=\frac{1}{2}\sum_iV_ik_i\mathop{\rm min}_{p\in SO(3)}||F_i^T-p_i^T||_F^2\]

&lt;p&gt;$p_i$ can also be represented with selector matrix $p_i=S_i^T\boldsymbol{p}$ where $\boldsymbol{p}\in R^{3m\times3}$ is the stacked version of $p_i$. And $S_i=\delta_i\otimes I_{33}\in R^{3m\times 3}$.&lt;/p&gt;

&lt;p&gt;So the energy can be rewritten in&lt;/p&gt;

\[\begin{align}
E(\boldsymbol{x})&amp;amp;=\frac{1}{2}\sum_iV_ik_i\mathop{\rm min}_{p\in SO(3)}||G_i^T\boldsymbol{x}-S_i^T\boldsymbol{p}||_F^2\\
&amp;amp;=\mathop{\rm min}_{p\in SO(3)}\frac{1}{2}{\rm tr}(\boldsymbol{x^T}L\boldsymbol{x})-{\rm tr}(\boldsymbol{x^T}J\boldsymbol{p})+C\\
L=&amp;amp;\sum_{i=1}^mV_ik_iG_iG_i^T,\ J=\sum_{i=1}^mV_ik_iG_iS_i^T
\end{align}\]

&lt;h2 id=&quot;gradient-descent&quot;&gt;Gradient Descent&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231009161202361.png&quot; alt=&quot;image-20231009161202361&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-10-06-Projective-Dynamics/image-20231009161224260.png&quot; alt=&quot;image-20231009161224260&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Material Point Method</title>
   <link href="https://blog.sigfeng.com/2023/09/12/mpm"/>
   <updated>2023-09-12T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2023/09/12/MPM</id>
   <content type="html">&lt;h2 id=&quot;divergence-theorem&quot;&gt;Divergence Theorem&lt;/h2&gt;

\[\int_{\partial V} \boldsymbol{u}\cdot\boldsymbol{n}\ dS=\int_V\nabla\cdot\boldsymbol{u}\ dV\]

&lt;p&gt;where $\boldsymbol{u}$ is a vector field defined in volume $V$, $\boldsymbol{n}$ is the outward facing normal of boundary $\partial V$.&lt;/p&gt;

&lt;h2 id=&quot;convection-thereom&quot;&gt;Convection Thereom&lt;/h2&gt;

&lt;p&gt;Following notation in &lt;a href=&quot;https://www.math.ucla.edu/~cffjiang/research/mpmcourse/mpmcourse.pdf&quot;&gt;mpm course&lt;/a&gt;:&lt;/p&gt;

\[\begin{align}
\frac{d}{dt}\int_{\Omega_t}f(\boldsymbol{x},t)dv&amp;amp;=\frac{d}{dt}\int_{\Omega_0}f(\Phi(\boldsymbol{X},t),t)JdV\\
&amp;amp;=\int_{\Omega_0}\frac{df(\Phi(\boldsymbol{X},t),t)}{dt}JdV+\int_{\Omega_0}f(\Phi(\boldsymbol{X},t),t)\frac {dJ}{dt}dV\\
&amp;amp;=\int_{\Omega_0}(\frac{\partial f}{\partial t}|_{(\Phi(\boldsymbol{X},t),t)}+\frac{\partial f}{\partial \boldsymbol{x}}|_{(\Phi(\boldsymbol{X},t),t)}\frac{\partial \Phi}{\partial t}|_{(\boldsymbol{X},t)}) JdV+\int_{\Omega_0}f(\Phi(\boldsymbol{X},t),t)(\nabla\cdot \boldsymbol{u})JdV\\
&amp;amp;=\int_{\Omega_0}(\frac{\partial f}{\partial t}|_{(\Phi(\boldsymbol{X},t),t)}+\nabla f|_{(\Phi(\boldsymbol{X},t),t)}\cdot \boldsymbol{U}|_{(\boldsymbol{X},t)}) JdV+\int_{\Omega_0}f|_{(\Phi(\boldsymbol{X},t),t)}(\nabla\cdot \boldsymbol{u})|_{(\Phi(\boldsymbol{X},t),t)}JdV\\
&amp;amp;=\int_{\Omega_t}(\frac{\partial f}{\partial t}|_{(\boldsymbol{x},t)}+\nabla f|_{(\boldsymbol{x},t)}\cdot \boldsymbol{u}|_{(\boldsymbol{x},t)}) dv+\int_{\Omega_t}f|_{(x,t)}(\nabla\cdot \boldsymbol{u})|_{(x,t)}dv\\
&amp;amp;=\int_{\Omega_t}\left(\frac{\partial f}{\partial t}+\nabla f\cdot \boldsymbol{u} + f(\nabla\cdot \boldsymbol{u})\right)dv\\
&amp;amp;=\int_{\Omega_t}\left(\frac{\partial f}{\partial t}+\nabla\cdot (f\boldsymbol{u})\right)dv\\
&amp;amp;=\int_{\Omega_t}\frac{\partial f}{\partial t}dv+\int_{\partial\Omega_t}f\boldsymbol{u}\cdot \boldsymbol{n}\ dS
\end{align}\]

&lt;p&gt;where $f$ is a scalar field defined on eulerian coordinate $x$. To be more specific, $f$ is a physical quantity that defined by “$f$ per unit volume”, for example, the density. If $f$ is defined by “$f$ per unit mass”, for example, acceleration is defined by “force per unit mass”, then we have another convection identity&lt;/p&gt;

\[\frac{d}{dt}\int_{\Omega_t}\rho f(\boldsymbol{x},t)dv=\int_{\Omega_t}\left(\frac{\partial \rho f}{\partial t}+\nabla\cdot (\rho f\boldsymbol{u})\right)dv\]

&lt;p&gt;where $\rho$ is the density.&lt;/p&gt;

&lt;p&gt;In the 6th equality, we use $\boldsymbol{U}(\boldsymbol{X},t)=\boldsymbol{u}(\Phi(\boldsymbol{X},t),t)$&lt;/p&gt;

&lt;p&gt;In the 8th equality, we use divergence theorem&lt;/p&gt;

&lt;p&gt;In the 3rd equality, we use $dJ/dt={\rm div}(\boldsymbol{u})J$&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;derivative-of-a-determinant&quot;&gt;Derivative of a determinant&lt;/h3&gt;

\[\frac{dJ}{dt}=\frac{d\left|F\right|}{dt}=\left|\begin{matrix}
dr_{0}/dt\\
r_1\\
r_2
\end{matrix}\right|+\left|\begin{matrix}
r_0\\
dr_{1}/dt\\
r_2
\end{matrix}\right|+\left|\begin{matrix}
r_0\\
r_1\\
dr_2/dt
\end{matrix}\right|\]

&lt;p&gt;where $r_i=(\partial x_i/\partial X_0\quad\partial x_i/\partial X_1\quad…\quad\partial x_i/\partial X_{n-1})$ and $dr_i/dt=(\partial u_i/\partial X_0\quad\partial u_i/\partial X_1\quad…\quad\partial u_i/\partial X_{n-1})$. Notice that we have following relation&lt;/p&gt;

\[\partial u_0/\partial X_0=\frac{\partial u_0}{\partial x_0}\frac{\partial x_0}{\partial X_0}+\frac{\partial u_0}{\partial x_1}\frac{\partial x_1}{\partial X_0}+\frac{\partial u_0}{\partial x_2}\frac{\partial x_2}{\partial X_0}\]

&lt;p&gt;So the first term can be expanded as&lt;/p&gt;

\[\left|\begin{matrix}
dr_{0}/dt\\
r_1\\
r_2
\end{matrix}\right|=\frac{\partial u_0}{\partial x_0}\left|\begin{matrix}r_0\\r_1\\r_2\end{matrix}\right|+\frac{\partial u_0}{\partial x_1}\left|\begin{matrix}r_1\\r_1\\r_2\end{matrix}\right|+\frac{\partial u_0}{\partial x_2}\left|\begin{matrix}r_2\\r_1\\r_2\end{matrix}\right|=\frac{\partial u_0}{\partial x_0}{\rm det}(F)\]

&lt;p&gt;It further simplies to&lt;/p&gt;

\[\frac{dJ}{dt}=\frac{\partial u_0}{\partial x_0}{\rm det}(F)+\frac{\partial u_1}{\partial x_1}{\rm det}(F)+\frac{\partial u_2}{\partial x_2}{\rm det}(F)=(\nabla\cdot\boldsymbol{u})J\]

&lt;hr /&gt;

&lt;h2 id=&quot;conservation-of-mass&quot;&gt;Conservation of Mass&lt;/h2&gt;

&lt;p&gt;Using convection theorem and subsitituting density into scalar field $f$, we have&lt;/p&gt;

\[\frac{d}{dt}\int_{\Omega_t}\rho dv=0=\int_{\Omega_t}\left(\frac{\partial \rho}{\partial t}+\nabla \rho\cdot \boldsymbol{u} + \rho(\nabla\cdot \boldsymbol{u})\right)dv=\int_{\Omega_t}\left(\frac{D \rho}{D t}+\rho(\nabla\cdot \boldsymbol{u})\right)dv\]

&lt;p&gt;This equation holds for any $\Omega_t$. So we further have&lt;/p&gt;

\[\frac{D \rho}{D t}+\rho(\nabla\cdot \boldsymbol{u})=0\]

&lt;p&gt;which is equation of conservation of mass. A positive value of the divergence, is associated with an expansive flow, thereby reducing local density.&lt;/p&gt;

&lt;h2 id=&quot;conservation-of-momentum&quot;&gt;Conservation of Momentum&lt;/h2&gt;

&lt;p&gt;The momentum of a fluid is defined to be $\rho \boldsymbol{u}$, per unit volume. Then the total momentum is&lt;/p&gt;

\[\int_{\Omega_t}\rho(\boldsymbol{x},t)\boldsymbol{u}(\boldsymbol{x},t)dv\]

&lt;p&gt;The conservation is expressed as&lt;/p&gt;

\[\begin{align}
\frac{d}{dt}\int_{\Omega_t}\rho(\boldsymbol{x},t)\boldsymbol{u}(\boldsymbol{x},t)dv&amp;amp;=\frac{d}{dt}\int_{\Omega_0}\rho(\Phi(\boldsymbol{X},t),t)\boldsymbol{u}(\Phi(\boldsymbol{X},t),t)JdV\\
&amp;amp;=\frac{d}{dt}\int_{\Omega_0}\rho(\Phi(\boldsymbol{X_0},t),t)\boldsymbol{u}(\Phi(\boldsymbol{X},t),t)dV\\
&amp;amp;=\int_{\Omega_0}\rho(\Phi(\boldsymbol{X_0},t),t)\frac{D\boldsymbol{u}}{Dt}|_{(\Phi(\boldsymbol{X},t),t)}dV\\
&amp;amp;=\int_{\Omega_0}R_0\boldsymbol{A}dV=\int_{\Omega_t}\rho\boldsymbol{a}dv\\
\end{align}\]

&lt;p&gt;where $\boldsymbol{A}(\boldsymbol{X},t)=\partial \boldsymbol{U}/\partial t=D\boldsymbol{u}/Dt=\boldsymbol{a}(\boldsymbol{x},t)$. The applied forces are expressed as&lt;/p&gt;

\[\int_{\Omega_t}\boldsymbol{f^{ext}}dv+\int_{\partial\Omega_t}\boldsymbol{\sigma}\boldsymbol{n}ds=\int_{\Omega_t}(\boldsymbol{f^{ext}}+\nabla\cdot\boldsymbol{\sigma})dv\]

&lt;p&gt;So the conservation in eulerian view is&lt;/p&gt;

\[\rho\boldsymbol{a}=\boldsymbol{f^{ext}}+\nabla\cdot\boldsymbol{\sigma}\]

&lt;p&gt;In lagrangian we have&lt;/p&gt;

\[R_0\boldsymbol{A}=\nabla\cdot\boldsymbol{P}+J\boldsymbol{F^{ext}}\]

&lt;p&gt;since $\int_{\partial\Omega_t}\boldsymbol{\sigma}\boldsymbol{n}ds=\int_{\partial\Omega_0}J\boldsymbol{\sigma}F^{-T}\boldsymbol{N}dS=\int_{\partial\Omega_t}\boldsymbol{P}\boldsymbol{N}dS$. Here $\boldsymbol{P}=J\boldsymbol{\sigma}\boldsymbol{F}^{-T}$ is the first Piola Kirchoff stress tensor.&lt;/p&gt;

&lt;hr /&gt;

&lt;h3 id=&quot;the-cauchy-stress-tensor&quot;&gt;The Cauchy Stress Tensor&lt;/h3&gt;

&lt;p&gt;Following &lt;a href=&quot;https://www.youtube.com/watch?v=CGDziWoEEgo&quot;&gt;Solid Mechanics Theory&lt;/a&gt;, a traction &lt;strong&gt;vector&lt;/strong&gt; at a point on a virtual surface passing through the point is defined by&lt;/p&gt;

\[\boldsymbol{t}=\lim\limits_{\Delta A\rightarrow 0}\frac{\Delta \boldsymbol{F}}{\Delta A}\]

&lt;p&gt;Where $\Delta A$ is the area of the virtual surface surrounding the point. It can be seen that every point on this surface will experience an internal force, and so there is a internal force distribution on this surface. $\Delta F$ means the sum of these forces on the surface. So actually, the traction vector means the average force on this virtual surface.&lt;/p&gt;

&lt;p&gt;Notice that the traction is not necessary to be aligned with surface normal. That is, the traction not only has a component perpendicular to the surface (which results in a stretch or compression), but also has a component parallel to the surface (which results in a shearing effect).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-09-12-MPM/QQ%E5%9B%BE%E7%89%8720230912094239.png&quot; alt=&quot;QQ图片20230912094239&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now we choose $\boldsymbol{e_1,e_2,e_3}$ to be world basis vectors. Consider a infinitesimal tetrahedron surrounding a point. It has 4 faces. Since the tetrahedron is infinitesimal small, the traction on each surface, is same with the traction at the interpoint with the same surface orientation. As mentioned above, every traction can be represented as $\boldsymbol{t_i}=\sigma_{i1}\boldsymbol{e_1}+\sigma_{i2}\boldsymbol{e_2}+\sigma_{i3}\boldsymbol{e_3},i=1,2,3$. Now we want to calculate $\boldsymbol{t_4}$. We have following equilibrium equation&lt;/p&gt;

\[\sum F_{e_1}=-\sigma_{11}A_1-\sigma_{21}A_2-\sigma_{31}A_3+t_{n_1}A_n+mg_1=ma_1\]

&lt;p&gt;And we will finally get&lt;/p&gt;

\[\begin{align}
t_{n1}&amp;amp;=\sigma_{11}n_1+\sigma_{21}n_2+\sigma_{31}n_3\\
t_{n2}&amp;amp;=\sigma_{12}n_1+\sigma_{22}n_2+\sigma_{32}n_3\\
t_{n3}&amp;amp;=\sigma_{13}n_1+\sigma_{23}n_2+\sigma_{33}n_3
\end{align}\]

&lt;p&gt;That is the relationship between traction $\boldsymbol{t}$, surface normal $\boldsymbol{n}$ and cauchy stress tensor $\boldsymbol{\sigma}$ (symmetric)&lt;/p&gt;

\[\boldsymbol{t}=\boldsymbol{\sigma}^T\boldsymbol{n}=\boldsymbol{\sigma}\boldsymbol{n}\]

&lt;ol&gt;
  &lt;li&gt;$\boldsymbol{\sigma}$ is symmetric&lt;/li&gt;
  &lt;li&gt;There exists a coordinate system where $\boldsymbol{\sigma}$ is diagonal (principal stress)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;To conclude, we have&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;conservation of mass (in case that there is no mass sink or add source)&lt;/p&gt;

    &lt;p&gt;&lt;strong&gt;Eulerian&lt;/strong&gt;&lt;/p&gt;

\[\frac{D \rho}{D t}+\rho(\nabla\cdot \boldsymbol{u})=0\]

    &lt;p&gt;&lt;strong&gt;Lagrangian&lt;/strong&gt;&lt;/p&gt;

\[RJ=R_0\]
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;conservation of momentum&lt;/p&gt;

    &lt;p&gt;&lt;strong&gt;Eulerian&lt;/strong&gt;&lt;/p&gt;

\[\rho\boldsymbol{a}=\nabla\cdot\boldsymbol{\sigma}+\boldsymbol{f^{ext}}\]

    &lt;p&gt;&lt;strong&gt;Lagrangian&lt;/strong&gt;&lt;/p&gt;

\[R_0\boldsymbol{A}=\nabla\cdot\boldsymbol{P}+J\boldsymbol{F^{ext}}\]
  &lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;weak-form&quot;&gt;Weak Form&lt;/h2&gt;

&lt;p&gt;Consider an arbitrary test function $\boldsymbol{Q}(\boldsymbol{X},t):\Omega_0\rightarrow R^d$ and its eulerian counter part $\boldsymbol{q}(\boldsymbol{x},t):\Omega_t\rightarrow R^d$  where $\boldsymbol{Q}(\boldsymbol{X},t)=\boldsymbol{q}(\Phi(\boldsymbol{X},t),t)$. Since we have&lt;/p&gt;

\[R_0\boldsymbol{A}=\nabla\cdot\boldsymbol{P}+J\boldsymbol{F^{ext}}\]

&lt;p&gt;so we further get following equation if we ignore external force for simplicity&lt;/p&gt;

\[\int_{\Omega_0}\boldsymbol{Q}\cdot (R_0\boldsymbol{A})dV=\int_{\Omega_0}\boldsymbol{Q}\cdot (\nabla\cdot\boldsymbol{P})dV\]

&lt;p&gt;where $\boldsymbol{P}=P_{\alpha\beta}$ and&lt;/p&gt;

\[\begin{align}
\boldsymbol{Q}\cdot(\nabla\cdot\boldsymbol{P})&amp;amp;=\boldsymbol{Q}\cdot\begin{pmatrix}
\nabla\cdot(P_{11},P_{12},P_{13})^T\\
\nabla\cdot(P_{21},P_{22},P_{23})^T\\
\nabla\cdot(P_{21},P_{32},P_{33})^T\\
\end{pmatrix}=\boldsymbol{Q}\cdot\begin{pmatrix}
P_{1j,j}\\
P_{2j,j}\\
P_{3j,j}
\end{pmatrix}=Q_iP_{ij,j}\\
\nabla\cdot(\boldsymbol{P^TQ})&amp;amp;=(P^TQ)_{j,j}=(P^T_{ji}Q_{i})_{,j}=(P_{ij}Q_{i})_{,j}=Q_iP_{ij,j}+P_{ij}Q_{i,j}
\\
\boldsymbol{Q}\cdot(\nabla\cdot\boldsymbol{P})&amp;amp;=\nabla\cdot(\boldsymbol{P^TQ})-P_{ij}Q_{i,j}=\nabla\cdot(\boldsymbol{P^TQ})-P_{ij}Q_{i,j}
\end{align}\]

&lt;p&gt;So&lt;/p&gt;

\[\begin{align}
\int_{\Omega_0}\boldsymbol{Q}\cdot (R_0\boldsymbol{A})dV&amp;amp;=\int_{\Omega_0}\boldsymbol{Q}\cdot (\nabla\cdot\boldsymbol{P})dV\\
&amp;amp;=\int_{\Omega_0}\nabla\cdot(\boldsymbol{P^TQ})dV-\int_{\Omega_0}P_{ij}Q_{i,j}dV\\
&amp;amp;=\int_{\partial\Omega_0}(\boldsymbol{P^TQ})\cdot\boldsymbol{N}dS-\int_{\Omega_0}P_{ij}Q_{i,j}dV\\
&amp;amp;=\int_{\partial\Omega_0}\boldsymbol{Q^TPN}dS-\int_{\Omega_0}P_{ij}Q_{i,j}dV\\
\end{align}\]

&lt;p&gt;where&lt;/p&gt;

\[Q_{i,j}=\frac{\partial Q_i}{\partial X_j}=\frac{\partial q_i}{\partial x_k}\frac{\partial x_k}{\partial X_j}=\frac{\partial q_i}{\partial x_k}F_{kj}=q_{i,k}F_{kj}\]

&lt;p&gt;Push forward we have&lt;/p&gt;

\[\begin{align}
\int_{\Omega_t}\boldsymbol{q}\cdot (\rho\boldsymbol{a})dv&amp;amp;=\int_{\partial\Omega_0}\boldsymbol{Q^TT}dS-\int_{\Omega_t}P_{ij}q_{i,k}F_{kj}dV\\
&amp;amp;=\int_{\partial\Omega_0}\boldsymbol{Q^T}\boldsymbol{F}^{int}-\int_{\Omega_t}q_{i,k}\frac{1}{J}P_{ij}F_{kj}dv\\
&amp;amp;=\int_{\partial\Omega_t}\boldsymbol{q^T}\boldsymbol{f}^{int}-\int_{\Omega_t}q_{i,k}\sigma_{ik}dv\\
&amp;amp;=\int_{\partial\Omega_t}\boldsymbol{q^T}\boldsymbol{t}ds-\int_{\Omega_t}{\rm tr}(\nabla\boldsymbol{q\ \sigma})dv\\
&amp;amp;=\int_{\partial\Omega_t}\boldsymbol{q^T}\boldsymbol{\sigma n}ds-\int_{\Omega_t}{\rm tr}(\nabla\boldsymbol{q\ \sigma})dv\\
&amp;amp;=\int_{\partial\Omega_t}q_it_ids-\int_{\Omega_t}q_{i,k}\sigma_{ik}dv
\end{align}\]

&lt;h2 id=&quot;evolve-of-internal-force&quot;&gt;Evolve of Internal Force&lt;/h2&gt;

\[\frac{\partial F(\boldsymbol{X},t)}{\partial t}=\frac{\partial(\partial \boldsymbol{x}/\partial \boldsymbol{X})}{\partial t}=\frac{\partial(\partial \boldsymbol{x}/\partial t)}{\partial \boldsymbol{X}}=\frac{\partial \boldsymbol{v}(\Phi(\boldsymbol{X},t))}{\partial \boldsymbol{X}}=\frac{\partial \boldsymbol{v}}{\partial \boldsymbol{x}}\frac{\partial\boldsymbol{x}}{\partial \boldsymbol{X}}=\frac{\partial \boldsymbol{v}}{\partial \boldsymbol{x}}F\]

&lt;p&gt;Say particles have a deformation gradient of $F_p^n$ at time $t^n$, then we have&lt;/p&gt;

\[\frac{F_p^{n+1}-F_p^n}{\Delta t}=\frac{\partial \boldsymbol{v}^{n+1}}{\partial \boldsymbol{x}}F_p^n\\
F_p^{n+1}=(\boldsymbol{I}+\Delta t\frac{\partial \boldsymbol{v}^{n+1}}{\partial \boldsymbol{x}})F_p^n\]

&lt;p&gt;where&lt;/p&gt;

\[\begin{align}
\frac{\partial \boldsymbol{v}^{n+1}}{\partial \boldsymbol{x}}&amp;amp;=\frac{\partial\sum_iN(\boldsymbol{x}-\boldsymbol{x}_i^n)\boldsymbol{\widetilde{v}}_i^{n+1}}{\partial\boldsymbol{x}}=\sum\limits_i\boldsymbol{\widetilde{v}}_i^{n+1}(\nabla w_{ip})^T
\end{align}\]

&lt;hr /&gt;

&lt;h3 id=&quot;derivative-of-a-scalar-vector-function&quot;&gt;Derivative of a scalar-vector function&lt;/h3&gt;

&lt;p&gt;Consider a scalar function $f:R^d\rightarrow R$ and a constant vector $\boldsymbol{v}\in R^d$.&lt;/p&gt;

\[\frac{\partial f\boldsymbol{v}}{\partial \boldsymbol{x}}=(fv)_{i,j}=f_{,j}v_i+fv_{i,j}=f_{,j}v_i=\boldsymbol{v}(\nabla f)^T\]

&lt;hr /&gt;

&lt;p&gt;So,&lt;/p&gt;

\[F_p^{n+1}=(\boldsymbol{I}+\Delta t\sum\limits_i\boldsymbol{\widetilde{v}}_i^{n+1}(\nabla w_{ip})^T)F_p^n\]

&lt;p&gt;Now, we want to identify how much stress does grid node has experienced. To figure out that, we need to define a energy function represented as $\boldsymbol{x}_i$. If the grid node $\boldsymbol{x_i}$ can move virtually to $\hat{\boldsymbol{x}_i}=\boldsymbol{x_i}+\Delta t\boldsymbol{\widetilde{v}_i^{n+1}}$, then the elastic energy can be represented as a function of $\hat{\boldsymbol{x}_i}$&lt;/p&gt;

\[e(\hat{\boldsymbol{x}})=\sum_p\psi(F_p(\hat{\boldsymbol{x}}))V_p^0\]

&lt;p&gt;where $\hat{\boldsymbol{x}}$ is the assembled vector of all Eulerian $\hat{\boldsymbol{x}_i}$. Here we think of the deformation gradient as a function of $\hat{\boldsymbol{x}}$.&lt;/p&gt;

&lt;p&gt;This is because we are temporarily thinking of the motion of the material as defined in terms of the $\boldsymbol{X}_i=\Phi^{-1}(\boldsymbol{x}_i,t)$ and $\hat{\boldsymbol{x}_i}$. So,&lt;/p&gt;

\[F_p(\hat{\boldsymbol{x}})=(\boldsymbol{I}+\sum_i\boldsymbol(\hat{\boldsymbol{x}_i}-\boldsymbol{x}_i)(\nabla w_{ip})^T)F_p^n\]

&lt;p&gt;Then&lt;/p&gt;

\[\begin{align}
-\boldsymbol{f}_{i}&amp;amp;=\frac{\partial e(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}_i}}=\frac{\partial e(\hat{\boldsymbol{x}})}{\partial F_p(\hat{\boldsymbol{x}})}\frac{\partial F_p(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}_i}}\\&amp;amp;=\sum\limits_pP_p^{n+1}V_p^0\frac{\partial F_p(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}_i}}\\
-\boldsymbol{f}_{i\alpha}&amp;amp;=\sum\limits_{p}\sum\limits_{\beta\gamma}P_{p\beta\gamma}^{n+1}V_p^0\frac{\partial F_{p\beta\gamma}(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}}_{i\alpha}}
\end{align}\]

&lt;p&gt;where&lt;/p&gt;

\[\begin{align}
\frac{\partial F_p(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}_i}}&amp;amp;=\frac{\partial \left(\sum_j\hat{\boldsymbol{x}}_j(\nabla w_{jp})^TF_p^n\right)}{\partial \hat{\boldsymbol{x}_i}}=\frac{\partial \left(\hat{\boldsymbol{x}}_i(\nabla w_{ip})^TF_p^n\right)}{\partial \hat{\boldsymbol{x}_i}}\\
\frac{\partial F_{p\beta\gamma}(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}}_{i\alpha}}&amp;amp;=\frac{\partial \left(\hat{\boldsymbol{x}}_{i\beta}\sum_{\epsilon}(\nabla w_{ip})^T_\epsilon F_{p\epsilon\gamma}^n\right)}{\partial \hat{\boldsymbol{x}}_{i\alpha}}=\delta_{\beta\alpha}\sum_{\epsilon}(\nabla w_{ip})^T_\epsilon F_{p\epsilon\gamma}^n
\end{align}\]

&lt;hr /&gt;

&lt;h3 id=&quot;derivative-of-a-outer-product-function&quot;&gt;Derivative of a outer-product function&lt;/h3&gt;

&lt;p&gt;Consider a constant vector $\boldsymbol{v}\in R^d$&lt;/p&gt;

\[\frac{\partial \boldsymbol{xv^T}}{\partial \boldsymbol{x}}=(xv)_{ij,k}=(x_iv_j)_{,k}=x_{i,k}v_j=\delta_{i,k}v_j\]

&lt;hr /&gt;

&lt;p&gt;So&lt;/p&gt;

\[-\boldsymbol{f}_{i\alpha}=\sum\limits_{p}\sum\limits_{\gamma}P_{p\alpha\gamma}^{n+1}V_p^0\sum_{\epsilon}(\nabla w_{ip})^T_\epsilon F_{p\epsilon\gamma}^n\]

&lt;p&gt;In APIC, we use&lt;/p&gt;

\[C_p^{n+1}=\sum\limits_i\boldsymbol{\widetilde{v}}_i^{n+1}(\nabla w_{ip})^T=\sum\limits_iw_{ip}\boldsymbol{\widetilde{v}}_i^{n+1}((D_p^n)^{-1}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n))^T\]

&lt;p&gt;It is also mentioned in &lt;a href=&quot;https://www.math.ucla.edu/~cffjiang/research/apicjcp/apicjcp.pdf&quot;&gt;APIC(JCP-version)&lt;/a&gt;: page 23, paragraph 1.&lt;/p&gt;

&lt;p&gt;So&lt;/p&gt;

\[F_p^{n+1}=(\boldsymbol{I}+\Delta tC_p^{n+1})F_p^n\]

&lt;p&gt;Now, the nodal force comes to&lt;/p&gt;

\[\begin{align}
-\boldsymbol{f_i}&amp;amp;=\frac{\partial e(\hat{\boldsymbol{x}})}{\partial \hat{\boldsymbol{x}_i}}=\sum_pV_p^0\frac{\partial \psi(F_p(\hat{\boldsymbol{x}}))}{\partial \hat{\boldsymbol{x}_i}}\\
&amp;amp;=\sum_pV_p^0\frac{\partial \psi(F_p)}{\Delta t\ \partial \boldsymbol{v}_i}\\
&amp;amp;=\sum_p\frac{V_p^0}{\Delta t}\frac{\partial \psi(F_p)}{\partial F_p}\frac{\partial F_p}{\partial C_{p}}\frac{\partial C_p}{\partial \boldsymbol{v}_i}\\
&amp;amp;=\sum_p\frac{V_p^0}{\Delta t}P_p(F_p^{})\left(\Delta t (F_p^n)^T\right)\left(\frac{4}{\Delta x^2}w_{ip}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\right)\\
&amp;amp;=\sum_p\frac{4}{\Delta x^2}V_p^0P_p(F_p^{})(F_p^n)^Tw_{ip}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)
\end{align}\]

&lt;p&gt;the grid node positions $\boldsymbol{\hat{x}}_i$ are simply a function of grid node velocities $\boldsymbol{\widetilde{v}}_i$. The explicit force is simply the force assuming zero grid motion, that is,&lt;/p&gt;

\[P_p(F_p(\hat{\boldsymbol{x}}))=P_p(F_p({\boldsymbol{x}}))=P_p(F_p^n)=P_p^n\]

&lt;p&gt;Then&lt;/p&gt;

\[-\boldsymbol{f_i}=\sum_p\frac{4}{\Delta x^2}V_p^0P_p^{n}(F_p^n)^Tw_{ip}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\]

&lt;p&gt;Meanwhile, we have&lt;/p&gt;

\[\begin{align}
dv&amp;amp;=JdV\\
\boldsymbol{ds}\cdot\boldsymbol{dl_3}&amp;amp;=J\boldsymbol{dS}\cdot\boldsymbol{dL_3}\\
\boldsymbol{ds}\cdot F\boldsymbol{dL_3}&amp;amp;=J\boldsymbol{dS}\cdot\boldsymbol{dL_3}\\
\boldsymbol{ds}^TF\boldsymbol{dL_3}&amp;amp;=J\boldsymbol{dS}^T\boldsymbol{dL_3}\\
\boldsymbol{ds}^TF&amp;amp;=J\boldsymbol{dS}^T\\
\boldsymbol{ds}&amp;amp;=JF^{-T}\boldsymbol{dS}\\
\end{align}\]

&lt;p&gt;and&lt;/p&gt;

\[\begin{align}
\boldsymbol{t}ds&amp;amp;=\boldsymbol{T}dS\\
\sigma\boldsymbol{n}ds&amp;amp;=P\boldsymbol{N}dS\\
\sigma\boldsymbol{ds}&amp;amp;=P\boldsymbol{dS}\\
\sigma JF^{-T}\boldsymbol{dS}&amp;amp;=P\boldsymbol{dS}\\
J\sigma F^{-T}&amp;amp;=P\\
\sigma&amp;amp;=\frac{1}{J}PF^T\\
\end{align}\]

&lt;p&gt;So the nodal force can be rewritten as&lt;/p&gt;

\[\begin{align}
-\boldsymbol{f_i}&amp;amp;=\sum_p\frac{4}{\Delta x^2}V_p^0P_p^{n}(F_p^n)^Tw_{ip}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\\
&amp;amp;=\sum_p\frac{4}{\Delta x^2}J^n_pV_p^0\sigma_p^nw_{ip}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\\
\end{align}\]

&lt;p&gt;why&lt;/p&gt;

\[C_p^{n+1}=\sum\limits_i\boldsymbol{v}_i^{n+1}(\nabla w_{ip})^T=\sum\limits_iw_{ip}\boldsymbol{v}_i^{n+1}((D_p^n)^{-1}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n))^T\]

&lt;p&gt;Though &lt;a href=&quot;https://www.math.ucla.edu/~cffjiang/research/apicjcp/apicjcp.pdf&quot;&gt;APIC(JCP-version)&lt;/a&gt;: page 22, paragraph 1 said that&lt;/p&gt;

\[w_{ip}(D_p^n)^{-1}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)=\nabla w_{ip}\]

&lt;p&gt;After my validation, this is not true. Perhaps it comes from assuming the velocity field is a affine veloctiy field&lt;/p&gt;

\[\boldsymbol{v}_q=\sum_iw_{iq}\boldsymbol{v}_i=\boldsymbol{v}_p+C_p^{n+1}(\boldsymbol{x}_q-\boldsymbol{x}_p)\]

&lt;p&gt;Differentiating this with respect to $\boldsymbol{x}_q$ goes to&lt;/p&gt;

\[\sum\limits_i\boldsymbol{v}_i(\nabla w_{iq})^T=C_p^{n+1}=\sum\limits_iw_{ip}\boldsymbol{v}_i^{n+1}((D_p^n)^{-1}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n))^T\]

&lt;p&gt;That is, for any point $q$, inside the supporting area, the velocity gradient equals to $C_p$.&lt;/p&gt;

&lt;h2 id=&quot;mpm-scheme-explicit-integration&quot;&gt;MPM Scheme (explicit integration)&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;Initial values
    &lt;ul&gt;
      &lt;li&gt;position of particle $\boldsymbol{x}_p^0$&lt;/li&gt;
      &lt;li&gt;momentum of particle $m_p\boldsymbol{v}_p^0$&lt;/li&gt;
      &lt;li&gt;deformation gradient of material point(particle) $F_p^0$&lt;/li&gt;
      &lt;li&gt;affine matrix of particle $C_p^0$&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;At time $t^n(n=0,1,2,…)$
    &lt;ol&gt;
      &lt;li&gt;particle to grid
        &lt;ol&gt;
          &lt;li&gt;
            &lt;p&gt;mass transfer&lt;/p&gt;

\[m_i=\sum_pw_{ip}m_p\]
          &lt;/li&gt;
          &lt;li&gt;
            &lt;p&gt;momentum transfer:&lt;/p&gt;
          &lt;/li&gt;
        &lt;/ol&gt;

\[m_i\boldsymbol{v}^n_i=\sum_pw_{ip}m_p\boldsymbol{v}_p^n+\sum_pw_{ip}m_pC_p^n(\boldsymbol{x}_i-\boldsymbol{x}_p^n)\]
      &lt;/li&gt;
      &lt;li&gt;add explicit internal force
        &lt;ol&gt;
          &lt;li&gt;
            &lt;p&gt;calculate PK1 stress:&lt;/p&gt;

\[P_p^{n}=P_p(F_p^{n})\]
          &lt;/li&gt;
          &lt;li&gt;
            &lt;p&gt;calculate node force:&lt;/p&gt;
          &lt;/li&gt;
        &lt;/ol&gt;

\[\boldsymbol{f}_i=-\sum_p(4/\Delta x^2)w_{ip}V_p^0P_p^{n}(F_p^n)^T(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)\]

        &lt;ol&gt;
          &lt;li&gt;
            &lt;p&gt;momentum update:&lt;/p&gt;

\[m_i\boldsymbol{\widetilde{v}}_i^{n+1}=m_i\boldsymbol{v}_i^{n}+\Delta t\boldsymbol{f}_i\]
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;add external force (e.g. gravity):&lt;/p&gt;

\[\boldsymbol{\widetilde{v}}_i^{n+1}=\boldsymbol{\widetilde{v}}_i^{n+1}+\boldsymbol{a}\Delta t\]
      &lt;/li&gt;
      &lt;li&gt;evolve deformation gradient
        &lt;ol&gt;
          &lt;li&gt;
            &lt;p&gt;affine field:&lt;/p&gt;

\[C_p^{n+1}=(4/\Delta x^2)\sum_iw_{ip}\boldsymbol{\widetilde{v}}_i^{n+1}(\boldsymbol{x}_i^n-\boldsymbol{x}_p^n)^T\]
          &lt;/li&gt;
          &lt;li&gt;
            &lt;p&gt;evolve deformation gradient:&lt;/p&gt;
          &lt;/li&gt;
        &lt;/ol&gt;

\[F_p^{n+1}=(\boldsymbol{I}+\Delta t\sum_i\boldsymbol{\widetilde{v}}_i^{n+1}(\nabla w_{ip})^T)F_p^n=(\boldsymbol{I}+\Delta tC_p^{n+1})F_p^n\]
      &lt;/li&gt;
      &lt;li&gt;
        &lt;p&gt;grid to particle&lt;/p&gt;

        &lt;ol&gt;
          &lt;li&gt;velocity:&lt;/li&gt;
        &lt;/ol&gt;

\[\boldsymbol{v}^{n+1}_p=\sum_iw_{ip}\boldsymbol{\widetilde{v}_i^{n+1}}\]

        &lt;ol&gt;
          &lt;li&gt;
            &lt;p&gt;advection:&lt;/p&gt;

\[\boldsymbol{x}_p^{n+1}=\boldsymbol{x}_p^{n}+\Delta t\boldsymbol{v}^{n+1}_p\]
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>3D motion of rigid body</title>
   <link href="https://blog.sigfeng.com/2023/08/24/inertia"/>
   <updated>2023-08-24T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2023/08/24/Inertia</id>
   <content type="html">&lt;h2 id=&quot;moment-of-inertia&quot;&gt;Moment of inertia&lt;/h2&gt;

&lt;p&gt;Moment of inertia（转动惯量 $I$）是和mass（质量 $m$）类似的一个概念。&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$\boldsymbol{F}=m\boldsymbol{a}$，质量描述了力对物体产生加速效果的难易程度&lt;/li&gt;
  &lt;li&gt;$\boldsymbol{\tau}=I\boldsymbol{\alpha}$，转动惯量描述了力矩对物体产生角加速度效果的难易程度&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;需要注意的是，虽然inertia和mass的概念非常相似，但是有所不同&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;$m$描述了物体自身的性质。因此给定一个物体，它的$m$就定下来了&lt;/li&gt;
  &lt;li&gt;$I$的测量除了给一个物体，还要指定旋转的转轴/参考点。&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;2d-planar-rigid-body&quot;&gt;2D planar rigid body&lt;/h3&gt;

&lt;p&gt;2D的旋转和3D的旋转是&lt;strong&gt;非常不同&lt;/strong&gt;的。让我们先考虑2D的旋转。假设一个planar rigid body，他在绕轴 $\boldsymbol\omega$ 进行旋转。这根轴与rigid body的交点为 $G$。根据角动量的定义，我们可以得到关于G点的角动量$\boldsymbol{H_G}$&lt;/p&gt;

\[\boldsymbol{H_G}=\sum\limits_i(\boldsymbol{r_i}\times \boldsymbol{v_i} m_i)=\sum\limits_i(\boldsymbol{r_i}\times (\boldsymbol{\omega}\times\boldsymbol{r_i}) m_i)\]

&lt;p&gt;因为我们考虑的是2维的旋转，转轴 $\boldsymbol{\omega}$ 一定垂直于位矢 $\boldsymbol{r_i}$，因此$\boldsymbol{r_i}\times(\boldsymbol{\omega}\times\boldsymbol{r_i})=(\boldsymbol{r_i}\cdot\boldsymbol{r_i})\boldsymbol{\omega}$，因此&lt;/p&gt;

\[\boldsymbol{H_G}=\sum\limits_i(\boldsymbol{r_i}\times (\boldsymbol{\omega}\times\boldsymbol{r_i}) m_i)=\boldsymbol{\omega}\sum\limits_im_i(\boldsymbol{r_i}\cdot\boldsymbol{r_i})=I_G\boldsymbol{\omega}\]

&lt;p&gt;其中，$I_G$是关于G点的转动惯量，是一个标量。因此你可以观察到，在此处角速度和角动量的方向是一样的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;非惯性系&lt;/strong&gt; 需要注意的是，我们现在的坐标系是定义在G上，也就是说，位矢是以G为原点，指向每一个质元的。如果我们的坐标原点 $G$ 是匀速运动的（相对于另一个惯性系），且坐标轴是不带旋转的，那么很显然，这个坐标系也是惯性系。但是有两种情况不同，&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;如果 $G$ 以加速度 $\boldsymbol{a}$ 变速运动，那么此时相对于G的坐标系是一个非惯性系，在进行力矩分析时，要引入惯性力。具体来说，惯性力产生的力矩为&lt;/p&gt;

\[\boldsymbol{\tau}=\sum\limits_i\boldsymbol{r_i}\times(-m_i\boldsymbol{a})=-\left(\sum\limits_i\boldsymbol{r_i}m_i\right)\times\boldsymbol{a}\]

    &lt;p&gt;如果 $G$ 与质心 $CM$ 重合，则 $\sum\limits_i\boldsymbol{r_i}m_i=\boldsymbol{0}$，于是力矩为0，我们可以不考虑由惯性力产生的力矩对角动量产生的影响。&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;如果坐标系在绕着 $G$ 转动，那情况可就复杂的多了。具体的分析可以参考 &lt;a href=&quot;https://zhuanlan.zhihu.com/p/114586511&quot;&gt;《糕记理论力学》——03非惯性系&lt;/a&gt;。幸运的是，如果我们的坐标系是定义在body space的话（就像和刚体捆绑着一起运动），这样的情况下，由于刚体上的点相对与刚体捆绑的坐标系没有相对运动，因此不用考虑科里奥利力，只用考虑由坐标系变速平动产生的惯性力，和坐标系转动产生的离心力。由于离心力指向原点，因此不产生力矩。在这种情况下，我们的分析又回到了上一种情况。&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;因此，选择刚体的质心作为torque pivot进行运动分析会大大简化问题。选择其他的点虽然也能得出相同的结论，但要复杂得多。&lt;/p&gt;

&lt;h3 id=&quot;3d-rigid-body&quot;&gt;3D rigid body&lt;/h3&gt;

&lt;p&gt;当问题来到3D，情况一下子就变得复杂了。小二，上图 （&lt;a href=&quot;https://ocw.snu.ac.kr/sites/default/files/NOTE/Lecture%208%20Rigid%20Body%20Dynamics%20in%203D.pdf&quot;&gt;出处&lt;/a&gt;）！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-07-24-Inertia/image-20230726130841534.png&quot; alt=&quot;image-20230726130841534&quot; /&gt;&lt;/p&gt;

&lt;p&gt;根据推导，转动惯量 $\boldsymbol{I_G}$ 现在不再是一个标量了！它变成了一个3x3的矩阵。如此，角动量 $\boldsymbol{H_G}$ 和角速度 $\boldsymbol{\omega}$ 甚至可能方向不同。但神奇的是，最后计算出来的角速度$\boldsymbol{\omega}$还是会和实际的转动保持一致的！让我们来看一个小例子。&lt;/p&gt;

&lt;p&gt;假设一个质量为 $m$ 的小球在绕Z轴做匀速圆周运动，此刻它在XOZ平面上的位置为 $(1,0,2)$，速度为 $(0,v_y,0)$。我们可以发现，角动量的方向和角速度的方向是不一样的！但是它们通过一个转动惯量矩阵联系了起来！&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-07-24-Inertia/image-20230726133334495.png&quot; alt=&quot;image-20230726133334495&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们还注意到，上面的这张ppt中，$I_G$其实是会随着时间变化的。这是为什么呢？因为 $I_G$ 的三个轴实际上是和一个固定的惯性系OXYZ align的，而刚体在绕着G进行旋转，这就意味着，在不同的时间，同一个质元相对G的位矢 $\boldsymbol{r_i}$ 会发生变化，进而导致转动惯量改变。&lt;/p&gt;

&lt;p&gt;如果认为在$t=0$时刻，G和O重合，IJK和XYZ重合。我们可以推出一个相对关系。假设位矢$\boldsymbol{r_i}$（相对于点O）&lt;/p&gt;

\[\boldsymbol{r_i}(t)=R(t)\boldsymbol{r_i}(0)+\boldsymbol{T}(t)\\
令\ \boldsymbol{g_i}(t)=\boldsymbol{r_i}(t)-\boldsymbol{T}(t)=R(t)\boldsymbol{r_i}(0)\]

&lt;p&gt;其中，$\boldsymbol{g}_i$是相对于G的位矢，$R(t)$可以认为是相对于$R(0)$的旋转，我们可以令$R(0)={\rm identity}$，$\boldsymbol{T}(t)$是G点的位移，$\boldsymbol{T}(t)=\boldsymbol{0}$，则&lt;/p&gt;

\[\begin{align}
I_G(t)&amp;amp;=\sum m_i\begin{bmatrix}
g_{iy}^2+g_{iz}^2 &amp;amp; g_{ix}g_{iy} &amp;amp; -g_{ix}g_{iz} \\
-g_{ix}g_{iy} &amp;amp; g_{ix}^2+g_{iz}^2 &amp;amp; -g_{iy}g_{iz} \\
-g_{ix}g_{iz} &amp;amp; -g_{iy}g_{iz} &amp;amp; g_{ix}^2+g_{iy}^2
\end{bmatrix}\\
&amp;amp;=\sum m_i((\boldsymbol{g_i}^T\boldsymbol{g_i})\boldsymbol{1}-\boldsymbol{g_i}\boldsymbol{g_i}^T)\\
&amp;amp;=\sum m_i((R\boldsymbol{r_0})^T(R\boldsymbol{r_0})\boldsymbol{1}-(R\boldsymbol{r_0})(R\boldsymbol{r_0})^T)\\
&amp;amp;=\sum m_i((\boldsymbol{r_0}^T\boldsymbol{r_0})\boldsymbol{1}-R\boldsymbol{r_0}\boldsymbol{r_0}^TR^T)
\end{align}\]

&lt;p&gt;由于$(\boldsymbol{r_0}^T\boldsymbol{r_0})\boldsymbol{1}$是标量x单位阵，因此可以等效成再乘一个单位阵$RR^T$，&lt;/p&gt;

\[I_G(t)=R(t)\left(\sum m_i(\boldsymbol{r_{i0}}^T\boldsymbol{r_{i0}})\boldsymbol{1}-\boldsymbol{r_{i0}}\boldsymbol{r_{i0}}^T\right)R(t)^T=R(t)I_OR(t)^T\]

&lt;p&gt;&lt;strong&gt;body space axes&lt;/strong&gt; 如果我们一开始就把坐标系和body绑定在一起，那$I_G$就不会随着时间变化而变化，就像这样&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2023-07-24-Inertia/image-20230726141120139.png&quot; alt=&quot;image-20230726141120139&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但此时，由于坐标系实际上相对惯性系在旋转（可能还是变速的），就会产生上面说的非惯性系问题。&lt;/p&gt;

&lt;p&gt;其中，向心加速度导致的力矩为&lt;/p&gt;

\[\sum\boldsymbol r_i\times m(\boldsymbol{\omega}\cdot(\boldsymbol\omega\cdot \boldsymbol r_i)-\boldsymbol\omega^2\boldsymbol r_i)=\sum m(\boldsymbol\omega\cdot \boldsymbol r_i)(\boldsymbol r_i\times \boldsymbol{\omega})\]

&lt;p&gt;太复杂了，不会算，分析到此为止x&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Embedded Rotation</title>
   <link href="https://blog.sigfeng.com/2022/12/06/embedded-rotation"/>
   <updated>2022-12-06T00:00:00+01:00</updated>
   <id>https://blog.sigfeng.com/2022/12/06/Embedded-Rotation</id>
   <content type="html">&lt;p&gt;假设我们要训练一个神经网络$f_{\theta}$，这个神经网络只有全连接层没有激活层，因此只是线性变换。由是网络参数可以表示为 weight和bias，即$\theta={A,b}$，因为不管多少层都可以统一视作一个线性变换。假设该网络接受3维输入，输出D维feature&lt;/p&gt;

\[f_{\theta}(n^g)=A_{D\times3}\cdot n^g+b_{D\times1}\]

&lt;p&gt;假设$n^g=M\cdot n^l$，此处$M$是local space到global space的旋转变换&lt;/p&gt;

\[f_{\theta}(M\cdot n^l)=A_{D\times3}\cdot M\cdot n^l+b_{D\times1}\]

&lt;p&gt;这个$f_\theta$就是normal feature的上限。&lt;/p&gt;

&lt;p&gt;现在我们要寻找另一个神经网络$f_{\theta’}(\theta’={A’,b’})$，他接受local space下的输入$n^l$，输出feature之后再用M进行旋转。我们希望这一套操作，达到和$f_\theta$一样的效果&lt;/p&gt;

\[\begin{align}
f_{\theta}(n^g)&amp;amp;=M\cdot f_{\theta&apos;}(n^l)\\
A_{D\times3}\cdot n^g+b_{D\times1}&amp;amp;=M\cdot(A&apos;_{D\times3}\cdot n^l+b&apos;_{D\times1})\\
A_{D\times3}\cdot M\cdot n^l+b_{D\times1}&amp;amp;=M\cdot A&apos;_{D\times3}\cdot n^l+M\cdot b&apos;_{D\times1}
\end{align}\]

&lt;p&gt;因此我们只要使得&lt;/p&gt;

\[\begin{align}
M\cdot A&apos;_{D\times3}&amp;amp;=A_{D\times3}\cdot M\\
M\cdot b&apos;_{D\times1}&amp;amp;=b_{D\times1}
\end{align}\]

&lt;p&gt;由于M是可逆的&lt;/p&gt;

\[\begin{align}
A&apos;_{D\times3}&amp;amp;=M^{-1}\cdot A_{D\times3}\cdot M\\
b&apos;_{D\times1}&amp;amp;=M^{-1}\cdot b_{D\times1}
\end{align}\]

&lt;p&gt;这就找到了。这说明&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;global space输入，线性变换输出&lt;/li&gt;
  &lt;li&gt;local space输入，线性变换输出，再变换回global space&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;要想使得这俩等价，网络还必须接受关于旋转$M$的信息。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Radiometry</title>
   <link href="https://blog.sigfeng.com/2022/10/31/radiometry"/>
   <updated>2022-10-31T00:00:00+01:00</updated>
   <id>https://blog.sigfeng.com/2022/10/31/Radiometry</id>
   <content type="html">&lt;p&gt;$线性相机\Rightarrow {\rm RGB} \propto {\rm Enery [J]}$&lt;/p&gt;

&lt;p&gt;$固定曝光时间\Rightarrow {\rm Energy[J]} \propto {\rm Flux[W]}$&lt;/p&gt;

&lt;p&gt;$固定传感器大小\Rightarrow {\rm Flux[W]} \propto {\rm Radiosity[W/m^2]}$&lt;/p&gt;

&lt;p&gt;$针孔相机模型\Rightarrow {\rm Radiosity[W/m^2]} \propto {\rm Radiance[W/(m^2\cdot sr)]}$&lt;/p&gt;

&lt;p&gt;$色块反射\Rightarrow{\rm Radiance[W/(m^2\cdot sr)]} = f_{\rm brdf} \cdot {\rm Irradiance[W/m^2]}$&lt;/p&gt;

&lt;p&gt;$色块接收光照\Rightarrow{\rm Irradiance[W/m^2]} = \int{\rm Radiance[W/(m^2\cdot sr)] \cdot d\omega[sr]\cdot cos\beta}=\sum{\rm Radiance[W/(m^2\cdot sr)] \cdot \Delta\omega[sr]\cdot cos\beta}$&lt;/p&gt;

&lt;p&gt;$面光源\Rightarrow{\rm Radiance[W/(m^2\cdot sr)]}={\rm Intensity[W/sr]}\cdot{\rm Attenuation[1/m^2]}\cdot {\rm cos\alpha}$&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;${\rm Factor[1/m^2] = Attenuation[1/m^2]\cdot cos\alpha\cdot cos\beta},f_{\rm brdf}\propto {\rm RGB_{gt}}$&lt;/p&gt;

&lt;p&gt;${\rm k_R[m^2\cdot sr/W]}\cdot{\rm Intensity[W/sr]}\cdot{\rm Factor[1/m^2]}\cdot {\rm R_{gt}}={\rm R}$&lt;/p&gt;

&lt;p&gt;${\rm k_G[m^2\cdot sr/W]}\cdot{\rm Intensity[W/sr]}\cdot{\rm Factor[1/m^2]}\cdot {\rm G_{gt}}={\rm G}$&lt;/p&gt;

&lt;p&gt;${\rm k_B[m^2\cdot sr/W]}\cdot{\rm Intensity[W/sr]}\cdot{\rm Factor[1/m^2]}\cdot {\rm B_{gt}}={\rm B}$&lt;/p&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Radiometry&quot;&gt;Wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 
 <entry>
   <title>Homography</title>
   <link href="https://blog.sigfeng.com/2022/08/31/homography"/>
   <updated>2022-08-31T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/08/31/Homography</id>
   <content type="html">&lt;p&gt;首先简化问题，我们假设有一个相机，其内参矩阵为$K_{3\times 3}$。用它拍了两幅照片，也就是两个view，对应两个相机坐标系，其中一个与世界坐标系重合：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;view 1：该相机坐标系与世界坐标系重合，即其对应的外参为$(I, 0)$
    &lt;ul&gt;
      &lt;li&gt;$I$为旋转矩阵，此处为单位矩阵&lt;/li&gt;
      &lt;li&gt;0为位移向量，此处没有位移&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;view 2：该相机坐标系对应的外参为$(R, T)$
    &lt;ul&gt;
      &lt;li&gt;对应的外参矩阵$\begin{bmatrix}R\mid T\end{bmatrix}_{4\times 4}$为世界坐标系到相机坐标系的变换&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;现在考虑在view 1下拍到的图片，看到的某一点P $(u, v)$，$u,v$为像素坐标。那么，$P_{v1}=K^{-1}\begin{bmatrix}u,v,1\end{bmatrix}^T=\begin{bmatrix}x,y,1\end{bmatrix}^T$就表示的是该点在view 1的相机坐标系下的坐标。另外$P_{v1}$的z坐标算出来会是1，也就是说$P_{v1}$落在第一个相机的成像平面上（z=1）。如果要把它转换成齐次坐标下的点，则xyz坐标保持不变，在w坐标上设置为1即可。&lt;/p&gt;

&lt;p&gt;因为view 1的相机坐标系和世界坐标系重合，所以$P_{v1}$即为P在世界坐标系下的坐标。&lt;/p&gt;

&lt;p&gt;然后我们将该点转换到相机2的坐标系。&lt;/p&gt;

&lt;p&gt;我们知道，世界坐标系到相机2的变换矩阵为$\begin{bmatrix}R\mid T\end{bmatrix}_{4\times 4}$&lt;/p&gt;

&lt;p&gt;则点P在相机2坐标系下的坐标为
\(\begin{bmatrix}R_{3\times 3}&amp;amp;T_{3\times1}\\0_{1\times3}&amp;amp;1\end{bmatrix}\begin{bmatrix}P_{v1}^T\mid 1\end{bmatrix}^T\)&lt;/p&gt;

&lt;p&gt;该计算式和$R\cdot P_{v1}+T$是等价的。&lt;/p&gt;

&lt;p&gt;另一方面，我们需要注意到一些东西：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;由于我们在讨论单应性，所以假设$P_{v1}$落在一个平面上。&lt;/li&gt;
  &lt;li&gt;考虑平面$n^T\cdot (X,Y,Z)^T+d=0$，d为相机1距离平面的距离，$n$为平面的法向量，此处表示在相机1的坐标系下。&lt;/li&gt;
  &lt;li&gt;则$P_{v1}$满足$-\frac{n^T\cdot P_{v1}}{d}=1$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;因此实际上，$R\cdot P_{v1}+T=R\cdot P_{v1}-T\cdot \frac{n^T\cdot P_{v1}}{d}$。
虽然点乘没有结合律，但经过笔者验算，此式可成立：
\(\begin{align}R\cdot P_{v1}-T\cdot \frac{n^T\cdot P_{v1}}{d}=(R-\frac{T\cdot n^T}{d})_{3\times 3}P_{v1}\notag\end{align}\)&lt;/p&gt;

&lt;p&gt;这样，我们就利用平面约束，将原来4x4的$\begin{bmatrix}R\mid T\end{bmatrix}$变换，改写成了3x3的形式。&lt;/p&gt;

&lt;p&gt;由此，$\begin{bmatrix}U,V,W\end{bmatrix}^T=K(R-\frac{T\cdot n^T}{d})K^{-1}\begin{bmatrix}u,v,1\end{bmatrix}^T$，$(u,v)=(U/W, V/W)$即为点P在相机2相片上的像素坐标，$K(R-\frac{T\cdot n^T}{d})K^{-1}$即为单应性矩阵。&lt;/p&gt;

&lt;p&gt;现在，考虑更复杂一点的情况，即：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;view 1：该相机坐标系对应的外参为$(R_1, T_1)$&lt;/li&gt;
  &lt;li&gt;view 2：该相机坐标系对应的外参为$(R_2, T_2)$&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;这种情况的推导可参照&lt;a href=&quot;https://zhuanlan.zhihu.com/p/363830541&quot;&gt;zhuanlan.zhihu.com/p/363830541&lt;/a&gt;，最后的结果为：&lt;/p&gt;

\[H=KR_2(I-(R_2^TT_2-R_1^TT_1)n_1^TR_1/d)R_1^TK^{-1}\]

&lt;p&gt;其中$n_1$为平面法线在相机1坐标系下的表示。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Multiview Correspondence</title>
   <link href="https://blog.sigfeng.com/2022/06/18/multiview-correspondence"/>
   <updated>2022-06-18T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/06/18/Multiview-Correspondence</id>
   <content type="html">&lt;p&gt;假设世界坐标系中有一个点$(X_w,Y_w,Z_w)$，首先变换到相机坐标系：
\((X_c,Y_c,Z_c,1)^T=\rm{worldToCamera}\cdot(X_w,Y_w,Z_w,1)^T\)
因为这个变换只包含旋转和位移，因此齐次坐标的w部分不会改变，恒为1，也不需要做归一化，直接把矩阵乘法乘出来的齐次坐标的xyz拿过来用。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;需要注意的是，Mitsuba中使用的相机坐标系的x/y轴与本文所使用的相机坐标系的x/y轴（下图中紫色的轴）相反，但都是右手系。Mitsuba的y轴与Lookat的up方向一致，而本文则是相反。本文受computer vision的影响，采用的相机坐标系的xy轴与最终图像的xy轴一致，因此在camera space到raster space的变换中不需要逆反xy轴。而Mitsuba则是将这个逆反放在了camera space到raster space的变换中。因此，区别只是在处理先后的问题。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-06-18-Multiview-Correspondence/image-20220618215239437.png&quot; alt=&quot;image-20220618215239437&quot; /&gt;&lt;/p&gt;

&lt;p&gt;接着进行透视变换，首先以图形学中的perspective caemra为例：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perspectiveTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nearz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;farz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;mat4f_zero&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;farz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nearz&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* Perform a scale so that the field of view is mapped
   * to the interval [-1, 1] */&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tanf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nv_to_rad&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a00&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ctot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a11&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// ctot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a22&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;farz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a23&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;nearz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;farz&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到ctot被注释掉，我们等会儿来讨论来讨论他的作用。&lt;/p&gt;

&lt;p&gt;现在，原来的点被变换到齐次坐标$(X_p’,Y_p’,Z_p’,W_p’)$：&lt;/p&gt;

\[\begin{align}\begin{bmatrix} X_p&apos;\\Y_p&apos;\\Z_p&apos;\\W_p&apos; \end{bmatrix}=&amp;amp;\begin{bmatrix} 1&amp;amp;0&amp;amp;0&amp;amp;0\\0&amp;amp;1&amp;amp;0&amp;amp;0\\0&amp;amp;0&amp;amp;\rm{farz}\cdot\rm{recip}&amp;amp;-\rm{nearz}\cdot\rm{farz}\cdot\rm{recip}\\0&amp;amp;0&amp;amp;1&amp;amp;0\end{bmatrix}\cdot\begin{bmatrix} X_c\\Y_c\\Z_c\\W_c \end{bmatrix}\\=&amp;amp;\begin{bmatrix} X_c\\Y_c\\(Z_c-\rm{nearz})\cdot\rm{farz}\cdot recip\\Z_c\end{bmatrix}\end{align}\]

&lt;p&gt;因为是点的变换，所以最后出来的坐标实际上还要归一化。齐次坐标实际表示的三维空间中的点是$(X_p,Y_p,Z_p)$。&lt;/p&gt;

\[\begin{bmatrix} X_p\\Y_p\\Z_p \end{bmatrix}=\begin{bmatrix} X_p&apos;/W_p&apos;\\Y_p&apos;/W_p&apos;\\Z_p&apos;/W_p&apos; \end{bmatrix}=\begin{bmatrix} X_c/Z_c\\Y_c/Z_c\\\frac{(Z_c-\rm{nearz})\cdot\rm{farz}\cdot recip}{Z_c} \end{bmatrix}\]

&lt;p&gt;此时$(X_p,Y_p)$表示的是将点$(X_c,Y_c,Z_c)$投射到z=1的平面所产生的平面坐标。$Z_p$处于0和1之间，对应的是nearz和farz。&lt;/p&gt;

&lt;p&gt;好，现在我们知道$(X_p,Y_p)$表示的是z=1平面上的坐标，根据fov (field of view，假设为$2\theta$)，我们就可以算出z=1平面上有效的窗口范围(clip window)。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-06-18-Multiview-Correspondence/image-20220618220728230.png&quot; alt=&quot;image-20220618220728230&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看到此时$X_p$的范围是$(-\rm{tan}\theta,\rm{tan}\theta)$。这里可以回想一下我们刚才把ctot注释掉了，如果考虑上ctot，那就是对x和y都做了一个 1/tan$\theta$ 的缩放，使得$X_p$的范围是$(-1,1)$。然后我们再考虑$Y_p$的范围。假设宽高比是aspect，高宽比是invAspect，则$Y_p$的范围是$(-\rm{invAspect,invAspect})$。&lt;/p&gt;

&lt;p&gt;然后再来看相机空间到光栅化空间的变换：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;cameraToRasterTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;VkExtent2D&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filmSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                             &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;far&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;aspect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filmSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filmSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invAspect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;aspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// This gives a transformation from camera space to screen space.&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// In x this space ranges from -1 to 1 but y ranges from -invAspect&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// to invAspect, since aspect ratio is not taken into account.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perspectiveTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;far&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// x and y ranges from (-1,1) x (-1,1)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;aspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// x and y ranges from (0,2) x (0,2)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;translation_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// x and y ranges from (0,1) x (0,1)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToNdc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToScreen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// x and y ranges from (0,0) to (width,height)&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToRaster&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;filmSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;filmSize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;cameraToNdc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cameraToRaster&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;cameraToScreen就是我们之前讲的变换。之后我们将screen space变换到raster space：首先都缩放到$(-1,1)$也就是ndc空间，紧接着在y方向上平移了1，在x方式上平移了1，然后进行了一个缩放；ndc space和screen space没啥区别，唯一的区别就是，ndc空间的xy范围都是-1到1。&lt;/p&gt;

&lt;p&gt;ndc到raster空间的变化如图所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-06-18-Multiview-Correspondence/image-20220618233657640.png&quot; alt=&quot;image-20220618233657640&quot; /&gt;&lt;/p&gt;

&lt;p&gt;现在总算知道之前写Asuna渲染器的时候怪在哪儿了。Asuna（当前版本）里面，Loader载入的shot中的up向量，用它来算Lookat的时候，产生的worldToCamera变换会使得相机的y轴和up重合。这其实也挺合理的，因为从相机最后拍摄得到的图像来看，up就是上面嘛~哪有说y和up是反着的。但是vision那边，相机的y轴和图像的y轴重合，指向地面orz，怪不得之前怎么写怎么不对劲呢。&lt;/p&gt;

&lt;p&gt;那想清楚了之后怎么设计才会比较优雅呢？现在比较麻的一点是因为要用现有的view更新框架（这样gui操作比较方便），perspective camera中直接套用了nvpro_core的camera，但是这个camera space的y是和up重合的，x指向右侧，z指向backward。&lt;/p&gt;

&lt;p&gt;好像也挺简单的，就是把y和z的方向都反一下，就变成我第一幅图中所画的相机坐标系了。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CameraManip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getMatrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后现在还有个怎么都写不对的两相机correspondence。基本的思路就是：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;目标是寻找reference view中某个pixel在source view中的对应&lt;/li&gt;
  &lt;li&gt;思路：
    &lt;ol&gt;
      &lt;li&gt;从reference pixel射出射线与场景相交于点p&lt;/li&gt;
      &lt;li&gt;检查点p到source camera的射线是否会被遮挡&lt;/li&gt;
      &lt;li&gt;如果被遮挡就说明在source view中看不到该点&lt;/li&gt;
      &lt;li&gt;没被遮挡就运用worldToRaster找到source view中的对应像素&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;我怎么想都觉得思路肯定没错，明天再看看吧（写于2022/6/18&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;擦，今天（2022/6/19）终于发现错在哪儿了。一路下来到生成对应像素的flow、存到图片都没有错。错的是，可视化的时候，用的是opencv读flow，会把RGB channel反一下，导致了我flow valid（我存在B通道，结果用了R通道）判断错误。正确的结果如下：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-06-18-Multiview-Correspondence/cbox_corr.png&quot; alt=&quot;cbox_corr&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-06-18-Multiview-Correspondence/rabbit_corr.png&quot; alt=&quot;rabbit_corr&quot; /&gt;&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Delta Distribution in Light Transport</title>
   <link href="https://blog.sigfeng.com/2022/06/11/delta-distribution-in-light-transport"/>
   <updated>2022-06-11T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/06/11/Delta-Distribution-in-Light-Transport</id>
   <content type="html">&lt;ol&gt;
  &lt;li&gt;按照mitsuba的传统，正确的写法为
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::sample()&lt;/code&gt; 给出
        &lt;ol&gt;
          &lt;li&gt;采样出的方向&lt;/li&gt;
          &lt;li&gt;采样方向对应的pdf（如果delta，则为1）&lt;/li&gt;
          &lt;li&gt;采样lobe的属性（delta或者solidAngle）&lt;/li&gt;
          &lt;li&gt;采样方向的入射出射比bsdfVal
            &lt;ol&gt;
              &lt;li&gt;如果是delta，bsdfVal不用乘dot(n, l)&lt;/li&gt;
              &lt;li&gt;如果不是delta，bsdfVal要乘dot(n, l)&lt;/li&gt;
            &lt;/ol&gt;
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::pdf()&lt;/code&gt; 给出由其他sampler产生bsdf在这个方向的pdf
        &lt;ol&gt;
          &lt;li&gt;需要传进sampler产生方向的属性measureType（是delta还是solidAngle）&lt;/li&gt;
          &lt;li&gt;如果bsdf只有delta lobe，返回0，表示采不到&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::eval&lt;/code&gt; 给出由其他sampler产生bsdf在这个方向的入射出射比
        &lt;ol&gt;
          &lt;li&gt;需要传进sampler产生方向的属性measureType（是delta还是solidAngle）&lt;/li&gt;
          &lt;li&gt;对于delta lobe，只接受delta形式的采样方向&lt;/li&gt;
          &lt;li&gt;对于solid angle lobe，只接受solid angle形式的采样方向&lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;做Direct Illumination的时候：
    &lt;ol&gt;
      &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::eval()&lt;/code&gt; 和 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::pdf()&lt;/code&gt; 全部传入solid angle形式measureType
        &lt;ol&gt;
          &lt;li&gt;这样做之后，唯一看起来不对劲的就是万一采到了点光源（delta）怎么办&lt;/li&gt;
          &lt;li&gt;首先这个时候不会和bsdf做MIS，所以&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::pdf()&lt;/code&gt;怎么算都无所谓&lt;/li&gt;
          &lt;li&gt;对于&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bsdf::eval()&lt;/code&gt;
            &lt;ol&gt;
              &lt;li&gt;如果它eval到了非specular lobe，比如点光源和完美漫反射，没啥问题啊没啥问题&lt;/li&gt;
              &lt;li&gt;如果eval到了specular lobe，这个lobe只接受delta形式的采样方向；specular lobe不会在direct illumination做计算，也很合理啊；因为没有MIS，算了direct illumination之后，如果我再hit到这个光源（入射反射对称）不就多算了一次吗&lt;/li&gt;
            &lt;/ol&gt;
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
      &lt;li&gt;只会对有非specular lobe的bsdf表面做。
        &lt;ol&gt;
          &lt;li&gt;反证：如果bsdf全是specular lobe，由sampler产生available入射方向的pdf为0，因为它是一个delta分布。&lt;/li&gt;
          &lt;li&gt;如果bsdf是specular lobe和非specular lobe混合
            &lt;ol&gt;
              &lt;li&gt;eval非specular lobe一切都很合理，因为传入的measure是solid angle&lt;/li&gt;
              &lt;li&gt;eval specular lobe无效，因为此lobe只接受delta measure&lt;/li&gt;
            &lt;/ol&gt;
          &lt;/li&gt;
        &lt;/ol&gt;
      &lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Hit light的时候：
    &lt;ol&gt;
      &lt;li&gt;这个时候应该这可能hit到solid angle形式的light，像点光源、方向光这种应该是hit不到的&lt;/li&gt;
      &lt;li&gt;如果bsdf sample出来的方向是specular，就不用算light pdf，不做mis&lt;/li&gt;
      &lt;li&gt;如果bsdf sample出来的不是specular，那就做mis&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
&lt;/ol&gt;

</content>
 </entry>
 
 <entry>
   <title>Artifacts with Phong Shading Normal</title>
   <link href="https://blog.sigfeng.com/2022/05/21/artifacts-with-phong-shading-normal"/>
   <updated>2022-05-21T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/05/21/Artifacts-with-Phong-Shading-Normal</id>
   <content type="html">&lt;h2 id=&quot;问题物体边缘会有奇怪的黑边&quot;&gt;问题：物体边缘会有奇怪的黑边&lt;/h2&gt;

&lt;p&gt;最近用别人开源光追软件和自己光追软件的时候遇到了点bug，如下所示。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/169484049-dda6f763-b0a4-4ded-964a-cdfcfa2ba6b2.png&quot; alt=&quot;1&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/169484059-0b0c8d0e-252f-474c-b540-2c4d8310e349.png&quot; alt=&quot;2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/169489299-6d189797-86cb-4b9f-8d7b-ba6c403a930b.png&quot; alt=&quot;3&quot; /&gt;&lt;/p&gt;

&lt;p&gt;自己的渲染器也会遇上类似的问题。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/image-20220521130708361.png&quot; alt=&quot;image-20220521130708361&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;解答法线插值的固有问题&quot;&gt;解答：法线插值的固有问题&lt;/h2&gt;

&lt;p&gt;后来上别人的仓库提了个issue，没想到是个typical question（又向歪果仁暴露了自己图形基础不扎实的事实）。原回答如下：&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;That is a usual artifact when rendering low-poly models with interpolated per-vertex shading normals.&lt;/p&gt;

  &lt;p&gt;At high geometry curvatures, which appear along the silhouettes of this object, it can happen that the interpolated shading normal across a triangle primitive actually doesn’t lie inside the same hemisphere as the direction to the viewer (negative ray direction) and that’s a false condition for the lighting test. For the shading calculation that’s like looking at the backface of an opaque object. (This would not happen when using the face-normals instead of the shading normals, but then you get a facetted look.)&lt;/p&gt;

  &lt;p&gt;Similar effects would arise when the sampled continuation ray in a path tracer would continue below the geometric surface of opaque materials, which also happens regularly with bump- or normal-maps.&lt;/p&gt;

  &lt;p&gt;This site has some nice images explaining interpolated shading normals:
&lt;a href=&quot;https://www.scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/shading-normals&quot;&gt;scratchapixel.com/lessons/3d-basic-rendering/introduction-to-shading/shading-normals&lt;/a&gt;&lt;/p&gt;

  &lt;p&gt;The black areas are running into the dot(V, N) &amp;lt; 0.0 case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;blockquote&gt;
  &lt;p&gt;it’s know topic with possible mitigation stragegies
as per the publication “Consistent Normal Interpolation”, Reshetov et al., 2010
&lt;a href=&quot;https://blog.yiningkarlli.com/2015/01/consistent-normal-interpolation.html&quot;&gt;blog.yiningkarlli.com/2015/01/consistent-normal-interpolation.html&lt;/a&gt;
and an &lt;a href=&quot;https://twitter.com/j_bikker/status/1107938914868908032&quot;&gt;implementation here&lt;/a&gt;&lt;/p&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;a href=&quot;https://github.com/jbikker/lighthouse2/blob/dcc64b4a401b7d0db4b3e75c1f3dbcfd8adb3102/lib/rendercore_vulkan_rt/shaders/tools.glsl#L259-L279&quot;&gt;jbikker/lighthouse2@&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dcc64b4&lt;/code&gt;/lib/rendercore_vulkan_rt/shaders/tools.glsl#L259-L279&lt;/a&gt;&lt;/li&gt;
  &lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2 id=&quot;理解直观感受&quot;&gt;理解：直观感受&lt;/h2&gt;

&lt;p&gt;解释一下。&lt;/p&gt;

&lt;p&gt;首先，我们做渲染时，一般会用插值的方法得到面片内某一点的法线。具体来说，我们shoot了一根ray，这根ray打到了一个三角形面片上的一个点。我们可以得到三角形三个顶点的法线和hit point的重心坐标，进而就可以插值出hit point的法线，一般管它叫shading normal。&lt;/p&gt;

&lt;p&gt;这里会有一个问题，我们shoot出的ray打到了三角形面片，那我们其实是可以看到三角形面片上的所有点的，因为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot(viewDir, faceNormal)&amp;gt;0&lt;/code&gt;。但实际上在渲染时，我们会把某些点视为不可见，因为测试可见性的条件为&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot(viewDir, shadingNormal)&amp;gt;0&lt;/code&gt;。如下图（灵魂画师）：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/image-20220521132007448.png&quot; alt=&quot;image-20220521132007448&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我们射出的ray（&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;viewDir=-rayDir&lt;/code&gt;），打到了一个三角形面片上的点，和三角形的相交测试是使用face normal的，&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot(faceNormal, viewDir)&amp;gt;0&lt;/code&gt;。但我们插值出来的shading normal，又会出现&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dot(shadingNormal, viewDir)&amp;lt;0&lt;/code&gt;的情况，进一步我们就不会在这个点上做光照计算，因此这块最后的颜色就黑了。&lt;/p&gt;

&lt;p&gt;上面那篇siggraph asia的文章对这个问题提出了缓解办法。但我试过了，效果并不好。其实最简单的做法就是用face normal来做光照计算的测试，但这样最后渲染出来会有很强的面片效应。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/media/2022-05-21-Artifacts-with-Phong-Shading-Normal/image-20220521132406574.png&quot; alt=&quot;image-20220521132406574&quot; /&gt;&lt;/p&gt;

&lt;p&gt;我已经放弃了对这个artifact进行进一步解决（摆烂ing）。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Perspective Camera Model</title>
   <link href="https://blog.sigfeng.com/2022/05/18/perspective-camera-model"/>
   <updated>2022-05-18T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/05/18/Perspective-Camera-Model</id>
   <content type="html">&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;perspectiveTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;far&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
	&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0f&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;far&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.0&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;radians&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ctot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ctot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;far&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;far&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;recip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.f&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;perspectiveTransform&lt;/code&gt;将camera space下，在fov（filed of view）中、在near plane和farplane间的点映射到screen space。这里的screen space还没有考虑到width和height不等的因素。例如，如果我们随便拿一个符合上述条件的camera space点，比如&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(near / ctot, invAspect * near / ctot, near)&lt;/code&gt;, 你会发现其实他被transform到了&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(1,invAspect,0)&lt;/code&gt;。所以这个screen space是定义在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(-1, 1) x (-invAspect, invAspect) x (0, 1)&lt;/code&gt;的坐标范围上的。&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invAspect&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;perspectiveTransform&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fov&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;far&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// homoTransformedPoint = (near, invAspect * near, 0, near);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vec4&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;homoTransformedPoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;persp&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;vec4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tanFovAng&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tanFovAng&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;invAspect&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;near&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// transformedPoint = (1, invAspect, 0)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transformedPoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;homoTransformedPoint&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;homoTransformedPoint&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;而根据pbrt的描述：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// pbrt&lt;/span&gt;
&lt;span class=&quot;c1&quot;&gt;// Raster space: This is almost the same as NDC space, except the x and y coordinates range from (0,0) to (w,h).&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;ScreenToRaster&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;film&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fullResolution&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                       &lt;span class=&quot;n&quot;&gt;film&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&amp;gt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fullResolution&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
    &lt;span class=&quot;nf&quot;&gt;Scale&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt;
          &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;Translate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Vector3f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMin&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;screenWindow&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pMax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;raster space定义在&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, w) x (0, h)&lt;/code&gt;上。所以我们先将这个空间平移&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec3(1, invAspect, 0)&lt;/code&gt;变换到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 2) x (-2 * invAspect, 0) x (0, 1)&lt;/code&gt;。然后再将这个空间进行缩放&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;vec3(0.5f, -0.5f * aspect, 1.f)&lt;/code&gt;变换到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(0, 1) x (0, 1) x (0, 1)&lt;/code&gt;，最后再缩放到&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;(w, h)&lt;/code&gt;。&lt;/p&gt;

&lt;p&gt;这里一定要注意x和y的变换不太一样，这是因为pbrt使用的是左手系，camera space中，y轴朝上，z轴朝前，x轴朝右。而图像其实左上角为（0,0），因此y需要反一下。但是比较坑爹的是vision那边的人惯用右手系，y轴朝下，z轴朝前，x轴朝右。对于vision的这种情况，则不需要逆转y。&lt;/p&gt;

&lt;p&gt;还有一个坑爹的点是用lookat计算worldToCamera。默认实现是这样的：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;template&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;kr&quot;&gt;inline&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;matrix4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;look_at&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;vector3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;amp;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;matrix4&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;vector3&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// make rotation matrix&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Z vector&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;center&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Y vector&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;up&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// X vector = Y cross Z&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cross&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// Recompute Y = Z cross X&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cross&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// cross product gives area of parallelogram, which is &amp;lt; 1.0 for&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// non-perpendicular unit-length vectors; so normalize x, y here&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;normalize&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a00&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a01&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a02&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a03&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a10&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a11&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a12&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a13&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a20&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a21&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a22&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a23&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;x&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;y&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;eye&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;z&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a30&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a31&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a32&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a33&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;T&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;M&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;可以看到这里的z轴是从相机看向的点指向相机的位置，而这和我们讨论的相机坐标系是相悖的。所以这里有个坑。&lt;/p&gt;

&lt;p&gt;我的解决办法是在z轴上scale -1，如下所示：&lt;/p&gt;

&lt;div class=&quot;language-cpp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;mat4&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;nvmath&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scale_mat4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;vec3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CameraManip&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getMatrix&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

</content>
 </entry>
 
 <entry>
   <title>Path Tracing Notes</title>
   <link href="https://blog.sigfeng.com/2022/05/07/path-tracing"/>
   <updated>2022-05-07T00:00:00+02:00</updated>
   <id>https://blog.sigfeng.com/2022/05/07/Path-Tracing</id>
   <content type="html">&lt;p&gt;Simple path tracing:&lt;/p&gt;

&lt;div class=&quot;language-glsl highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// miss&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// emitter hit test&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hitIsEmitter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;misWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;powerHeuristic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;brdfPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;radiance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;misWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throughput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;emittance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;stop&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// direct light&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// importance sample the light&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sampleLight&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;emittance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;numLights&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;isShadowed&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkVisibility&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hitPos&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightSample&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;isShadowed&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;evalBrdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;misWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;powerHeuristic&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;lightPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brdfPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;radiance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;misWeight&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;emittance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brdfVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cosTheta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throughput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;lightPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// sample next ray&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;sampleBrdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;throughput&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brdfVal&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;cosTheta&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;/&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brdfPdf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;trace&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;local to global:
  wi ==&amp;gt; lightDir
  wo ==&amp;gt; viewDir
  (1,0,0) ==&amp;gt; tangent
  (0,1,0) ==&amp;gt; bitangent
  (0,0,1) ==&amp;gt; ffnormal
global to local:
  wi &amp;lt;== lightDir
  wo &amp;lt;== viewDir
  (1,0,0) &amp;lt;== tangent
  (0,1,0) &amp;lt;== bitangent
  (0,0,1) &amp;lt;== ffnormal
calc:
 [tangent[0:3]       [tangent.x    [1
  bitangent[0:3]   *  tangent.y  =  0
  normal[0:3]    ]    tangent.z]    0]
&lt;/code&gt;&lt;/pre&gt;
</content>
 </entry>
 
 <entry>
   <title>隐式马尔科夫链</title>
   <link href="https://blog.sigfeng.com/2022/03/04/hidden-markov-model"/>
   <updated>2022-03-04T00:00:00+01:00</updated>
   <id>https://blog.sigfeng.com/2022/03/04/Hidden-Markov-Model</id>
   <content type="html">&lt;h2 id=&quot;graph-model&quot;&gt;Graph Model&lt;/h2&gt;

&lt;pre&gt;&lt;code class=&quot;language-txt&quot;&gt;X_1 --&amp;gt; X_2 --&amp;gt; X_3 --&amp;gt; ... --&amp;gt; X_n
 |       |       |               |
Y_1     Y_2     Y_3             Y_n
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
  &lt;li&gt;观测值 $Y_1, Y_2, …, Y_n$，记作 $\boldsymbol{Y}$
    &lt;ul&gt;
      &lt;li&gt;样本值空间 \(V=\{v_i\mid i=1,2,...,N_V\}\)，$Y_i\in V$&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;隐藏状态 $X_1,X_2,…,X_n$，记作 $\boldsymbol{X}$
    &lt;ul&gt;
      &lt;li&gt;状态值空间 \(S=\{s_i\mid i=1,2,...,N_S\}\)，$X_i\in S$&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;根据马尔科夫链模型：&lt;/p&gt;

\[\begin{align}
&amp;amp;P(X_{t+1}\mid X_1,...,X_t,Y_1,...,Y_t)=P(X_{t+1}\mid X_t)\\
&amp;amp;P(Y_t\mid X_1,...,X_t,Y_1,...,Y_t)=P(Y_t\mid X_t)\\
\end{align}\]

&lt;p&gt;第一条对应状态转移概率矩阵：&lt;/p&gt;

\[\boldsymbol{A}=\{a_{ij}\}_{N_S\times N_S},a_{ij}=P(X_{t+1}=s_j\mid X_t=s_i)\]

&lt;p&gt;第二条对应发射概率矩阵：&lt;/p&gt;

\[\boldsymbol{B}=\{b_{ij}\}_{N_S\times N_V},b_{ij}=P(Y_t=v_j\mid X_t=s_i)\]

&lt;p&gt;我们用一个三元组 $\lambda=(\boldsymbol{A},\boldsymbol{B},\boldsymbol{\pi})$ 就可以刻画一个hidden markov model。其中 $\pi$ 为初始概率矩阵：&lt;/p&gt;

\[\boldsymbol{\pi_i}=P(X_1=s_i)\]

&lt;h2 id=&quot;problem&quot;&gt;Problem&lt;/h2&gt;

&lt;h3 id=&quot;概率问题&quot;&gt;概率问题&lt;/h3&gt;

&lt;p&gt;已知参数 $\lambda=(\boldsymbol{A},\boldsymbol{B},\boldsymbol{\pi})$ 的情况下，求一组观测 $Y_1,…,Y_n$ 的概率 $P(Y_1,…,Y_T)$。&lt;/p&gt;

&lt;h3 id=&quot;forward-algorithm&quot;&gt;Forward Algorithm&lt;/h3&gt;

&lt;p&gt;求 $P(Y_{1:T})$ 即求 $\sum_{X_{T}} P(Y_{1:T},X_{T})$&lt;/p&gt;

&lt;p&gt;我们令 $\alpha_t(s_i)=P(Y_{1:t},X_t=s_i)$，则 $P(Y_{1:t})=\sum_{i} \alpha_n(s_i)$，分析得&lt;/p&gt;

\[\begin{align}P(Y_{1:t},X_{t})&amp;amp;=\sum_{X_{t-1}}P(Y_{1:t},X_t,X_{t-1})\\
&amp;amp;=\sum_{X_{t-1}}P(Y_{1:t-1},X_{t-1})P(Y_{t},X_t\mid Y_{1:t-1},X_{t-1})\\
&amp;amp;=\sum_{X_{t-1}}P(Y_{1:t-1},X_{t-1})P(Y_t\mid X_t)P(X_t\mid Y_{1:t-1},X_{t-1})\\
&amp;amp;=\sum_{X_{t-1}}P(Y_{1:t-1},X_{t-1})P(Y_t\mid X_t)P(X_t\mid Y_{1:t-1}X_{t-1})\\
&amp;amp;=\sum_{X_{t-1}}P(Y_{1:t-1},X_{t-1})P(Y_t\mid X_t)P(X_t\mid X_{t-1})\\
&amp;amp;=P(Y_t\mid X_t)\sum_{X_{t-1}}P(Y_{1:t-1},X_{t-1})P(X_t\mid X_{t-1})\\
\end{align}\]

&lt;p&gt;可以看到有一个递推关系为&lt;/p&gt;

\[\begin{align}
&amp;amp;\alpha_1(s_i)=\pi(s_i)B(s_i,Y_1)\\
&amp;amp;\alpha_{t+1}(s_i)=B(s_i,Y_{t+1})\sum_{j}\alpha_t(s_j)A(s_j,s_i)\\
&amp;amp;P(Y_{1:T})=\sum_{s_i}\alpha_T(s_i)
\end{align}\]

&lt;h3 id=&quot;backword-algorithm&quot;&gt;Backword Algorithm&lt;/h3&gt;

&lt;p&gt;求 $P(Y_{1:T})$ 即求 $\sum_{X_{1}} P(Y_{1:T},X_{1})$&lt;/p&gt;

&lt;p&gt;我们令 $\beta_t(s_i)=P(Y_{t+1:T}\mid X_t=s_i)$，则 $\beta_T(s_i)=1$，则&lt;/p&gt;

\[\begin{align}
P(Y_{1:T})&amp;amp;=\sum_{X_1}P(Y_{1:T},X_1)\\
&amp;amp;=\sum_{X_1}P(X_1)P(Y_{1:T}\mid X_1)\\
&amp;amp;=\sum_{X_1}P(X_1)P(Y_{2:T}\mid X_1)P(Y_1\mid X_1)
\end{align}\]

&lt;p&gt;可得 $P(Y_{1:T})=\sum_{i}\pi(s_i)B(s_i,Y_1)\beta_1(s_i)$&lt;/p&gt;

&lt;p&gt;继续分析寻找递推关系&lt;/p&gt;

\[\begin{align}
P(Y_{t+1:T}\mid X_{t})&amp;amp;=\sum_{X_{t+1}}P(Y_{t+1:T},X_{t+1}\mid X_{t})\\
&amp;amp;=\sum_{X_{t+1}}P(X_{t+1}\mid X_{t})P(Y_{t+1:T}\mid X_{t+1},X_t)\\
&amp;amp;=\sum_{X_{t+1}}P(X_{t+1}\mid X_{t})P(Y_{t+1}\mid X_{t},X_{t+1})P(Y_{t+2:T}\mid X_{t},X_{t+1})\\
&amp;amp;=\sum_{X_{t+1}}P(X_{t+1}\mid X_{t})P(Y_{t+1}\mid X_{t+1})P(Y_{t+2:T}\mid X_{t+1})\\
\end{align}\]

&lt;p&gt;可以看到有一个递推关系为&lt;/p&gt;

\[\begin{align}
&amp;amp;\beta_T(s_i)=1\\
&amp;amp;\beta_{t}(s_i)=\sum_{s_j}B(s_i,Y_{t+1})\beta_{t+1}(s_j)A(s_i,s_j)\\
&amp;amp;P(Y_{1:T})=\sum_{s_i}\pi(s_i)B(s_i,Y_1)\beta_1(s_i)
\end{align}\]

&lt;h3 id=&quot;推测问题&quot;&gt;推测问题&lt;/h3&gt;

&lt;p&gt;已知参数 $\lambda=(\boldsymbol{A},\boldsymbol{B},\boldsymbol{\pi})$ 的情况下，求使得 $P(X_{1:T}\mid Y_{1:T})$ 最大的状态序列 $X^*_{1:T}$。&lt;/p&gt;

&lt;p&gt;使用的是vertbi算法（动态规划最短路）。&lt;/p&gt;

&lt;p&gt;注意到 $P(\boldsymbol{X}\mid\boldsymbol{Y})=P(\boldsymbol{X},\boldsymbol{Y})/P(\boldsymbol{Y})$，而且我们已经在上一步中学会求解 $P(\boldsymbol{Y})$，所以只需要最大化 $P(\boldsymbol{X,Y})$。&lt;/p&gt;

&lt;p&gt;令 $\delta_t(s_i)=max_{Y_{1:t-1}}P(X_{1:t-1},X_t=s_i,Y_{1:t})$，则&lt;/p&gt;

\[\begin{align}
\delta_{t+1}(s_i)&amp;amp;=\mathop{max}\limits_{Y_{1:t}}P(X_{1:t},X_{t+1}=s_i,Y_{1:t+1})\\
&amp;amp;=\mathop{max}\limits_{s_j}\mathop{max}\limits_{Y_{1:t-1}}P(X_{1:t-1},X_t=s_j,X_{t+1}=s_i,Y_{1:t+1})\\
&amp;amp;=\mathop{max}\limits_{s_j}\mathop{max}\limits_{Y_{1:t-1}}P(X_{1:t-1},X_t=s_j,Y_{1:t})P(X_{t+1}=s_i\mid X_t=s_j)P(Y_{t+1}\mid X_{t+1}=s_i)\\
&amp;amp;=\mathop{max}\limits_{s_j}(\delta_t(s_j)A(s_j,s_i))B(s_i,Y_{t+1})
\end{align}\]

&lt;p&gt;且&lt;/p&gt;

\[\begin{align}
&amp;amp;\delta_1(s_i)=P(X_1=s_i,Y_1)=\pi(s_i)B(s_i,Y_1)\\
&amp;amp;\mathop{max}\limits_{\boldsymbol{Y}}P(\boldsymbol{X},\boldsymbol{Y})=\mathop{max}\limits_{s_i}\delta_T(s_i)
\end{align}\]

&lt;p&gt;我们只需要把递推过程中的最优路径都记录下来就可以了。&lt;/p&gt;

&lt;h3 id=&quot;训练问题&quot;&gt;训练问题&lt;/h3&gt;

&lt;p&gt;这里不做讨论。&lt;/p&gt;

&lt;h2 id=&quot;reference&quot;&gt;Reference&lt;/h2&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/64356349&quot;&gt;隐马尔科夫模型教程(Hidden Markov Model Tutorial)&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://zhuanlan.zhihu.com/p/85454896&quot;&gt;一站式解决：隐马尔可夫模型（HMM）全过程推导及实现&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.zhihu.com/question/20136144&quot;&gt;Viterbi最短路算法-动态规划&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
</content>
 </entry>
 

</feed>
