“探照灯是怎么扫出那堵墙的?“:连续碰撞检测的底层计算揭秘
引子小李的知其然不知其所以然上回说到小李学会了用连续碰撞检测根治高速子弹穿墙的毛病。功能是修好了可他这人有股钻研的劲儿用着用着心里又痒痒起来跑来刨根问底老师傅,上回您教我’连续碰撞检测’,说它像’探照灯全程扫描运动轨迹’,把高速子弹穿墙的病根治了,我照着改成 Continuous,果然灵!**可我这心里,总有个疙瘩解不开——**您说它’扫描整条运动轨迹’,这话我懂了个大概,可它到底是怎么’扫’的?难道真有一条看不见的’光’射出去?那颗飞得飞快的子弹,和那堵墙,在电脑里到底发生了怎样的一番’计算’,才被判定’撞上了’的**?****还有,我改成 Continuous 就说它’费性能’,它到底费在哪一步的计算上?为啥扫一条轨迹,就比拍一张照费那么多力气?**老师傅,我不光想知道’怎么用’,我更想knowing’它骨子里到底是怎么算出来的’!这底层的门道,您能给我掰开揉碎了讲讲吗?老师傅一听眼中露出赞许“问得好‘知其然更要知其所以然’——你能问到这一层说明真往里钻了很多人用了一辈子 Continuous也说不清它底下那台’计算机器’到底怎么转的。今天我就带你钻到最底层看看那束’看不见的探照灯’究竟是靠怎样一套精密的计算扫出那堵墙的”第一章核心第一步——它不算点而是算一段扫过的空间老师傅说要懂底层先得破除一个直觉误会。你以为的扫描轨迹不是真射出一道光它的第一步是在脑子里把物体这一帧扫过的整段空间给想象出来┌────────────────────────────────────────────────┐ │ 第一步:不算点,而是构造扫过的整段空间! │ │ │ │ 离散检测算什么? │ │ 只算物体这一帧的位置,一个静止的形状 │ │ → 拿这个形状去和墙比对,重叠没有? │ │ │ │ 连续检测算什么?(关键!) │ │ 它拿到两个关键信息: │ │ · 物体【上一帧的位置】A(起点) │ │ · 物体【这一帧的位置】B(终点) │ │ 然后在脑中【构造出】一个几何体—— │ │ 物体从A移动到B,一路上身体扫过的 │ │ 那整块空间! │ │ │ │ 举例:一个球,从A飞到B │ │ A● ●B │ │ └──── 它扫过的空间 ────┘ │ │ ╭────────────────────────╮ │ │ │ ●━━━━━━━━━━━━━━━● │ ← 像个胶囊 │ │ ╰────────────────────────╯ (球扫出的体积) │ │ │ │ → 核心思想:把运动的球,在计算上,变成一个 │ │ 静止的、拉长的胶囊体—— │ │ 这个胶囊,就代表了它这一帧走过的全部空间! │ │ │ │ → 于是问题就转化成了: │ │ 这个拉长的胶囊,和墙,重叠了吗? │ └────────────────────────────────────────────────┘第一步·从点到扫过的空间先破一个直觉误会——连续检测并非真的射出一道光去照。它的第一步是在计算上构造出一个几何体。它怎么构造连续检测拿到两个关键信息——物体上一帧的位置 A起点和这一帧的位置 B终点然后在脑中构造出一个几何体“物体从 A 移动到 B一路上身体扫过的那整块空间”。一个直观的例子一个球从 A 飞到 B它扫过的空间形状就像一个被拉长的胶囊体两端是半球、中间是圆柱——这个胶囊就代表了球这一帧走过的全部空间。精髓所在连续检测最核心的思想是把一个运动的球在计算上等价转化成一个静止的、被拉长的胶囊体。于是那个棘手的运动中会不会撞上的动态问题就被巧妙地转化成了一个这个拉长的胶囊和墙有没有重叠的静态几何问题——而静态的重叠判断计算机可就拿手多了。小李恍然“原来第一步这么妙!它不是真射光,而是在计算上’构造’一个几何体——拿到球上一帧位置A和这一帧位置B,把’从A飞到B一路扫过的整块空间’想象成一个’拉长的胶囊体’!核心思想是把’运动的球’等价转化成’静止的拉长胶囊’,于是’运动中会不会撞上’的动态难题,就变成了’这个胶囊和墙有没有重叠’的静态几何问题——静态重叠判断电脑最拿手!那……接下来它怎么算这个’胶囊和墙重不重叠’?”第二章核心第二步——“扫掠检测”求出最早在哪一刻撞上老师傅说光知道重叠了还不够得算出在飞行途中的哪一个时刻、哪一个位置撞上的。这一步叫扫掠检测Sweep Test“——它不满足于回答撞没撞”还要精确解出最早在哪一刻撞上┌────────────────────────────────────────────────┐ │ 第二步:扫掠检测——解出最早撞上的时刻! │ │ │ │ 光知道胶囊和墙重叠了还不够,必须精确算出: │ │ 子弹从A飞向B的途中,【最早】在哪个 │ │ 时间点t、哪个位置,первый次碰到墙? │ │ │ │ 怎么算?把移动参数化成一个关于时间t的方程: │ │ │ │ 位置(t) A (B - A) × t │ │ t0 → 在起点A │ │ t1 → 在终点B │ │ t0.5 → 在正中间 │ │ (t从0到1,描述整段飞行过程) │ │ │ │ 于是问题变成一道【求解方程】的数学题: │ │ 求出最小的 t,使得【位置(t)处的子弹形状】 │ │ 刚好与墙的表面【相切/接触】 │ │ │ │ A●─────────╳═════════●B │ │ ↑墙[███] │ │ t≈0.4处,子弹刚好碰到墙面! │ │ → 解得 t0.4 → 这就是【碰撞时刻】! │ │ │ │ ✅ 解出这个t,就全知道了: │ │ · 碰撞【时刻】(t0.4,飞行到40%时) │ │ · 碰撞【位置】(代入方程算出精确坐标) │ │ · 于是把子弹【精确挪到那个撞击点】停下! │ │ │ │ → 它不是事后发现穿了,而是解方程算出 │ │ 途中最早的那个精确撞击点! │ └────────────────────────────────────────────────┘第二步·扫掠检测Sweep Test光知道胶囊和墙重叠了还不够——引擎必须精确算出子弹从 A 飞向 B 的途中最早在哪个时间点、哪个位置第一次碰到墙“这个求解过程就叫扫掠检测”。它如何求解·参数化方程引擎把子弹的移动参数化成一个关于时间t的方程位置(t) A (B - A) × t这里的t从 0 到 1描述了整段飞行过程——t0时在起点 At1时在终点 Bt0.5时在正中间。于是变成一道数学题问题就转化成了求解方程——“求出最小的那个 t使得’位置(t) 处的子弹形状’刚好和墙面相切、接触”。比如解得t0.4就意味着子弹飞行到全程 40% 的位置时正好撞上了墙。✅解出 t一切皆知一旦解出这个碰撞时刻t引擎就掌握了全部信息——碰撞的精确时刻、碰撞的精确位置把 t 代回方程即可算出坐标于是就能把子弹精确地挪到那个撞击点停下、判定命中。精髓所在连续检测的高明在于它不是事后才发现子弹穿过去了而是通过解方程主动算出了飞行途中最早的那个精确撞击点——它对碰撞的把握精确到了飞行进度的百分之几这个程度。小李眼睛一亮“妙啊!第二步叫扫掠检测——不满足于’撞没撞’,还要解出’最早在哪一刻撞上’!把移动参数化成关于时间t的方程’位置(t)A(B-A)×t’,t从0到1描述整段飞行;然后求解’最小的t使子弹刚好碰到墙面’,解得比如t0.4;有了t就全知道了——碰撞时刻、碰撞位置,把子弹精确挪到撞击点停下!它不是事后发现穿了,而是解方程算出途中最早的精确撞击点!那……这一算,不就正好解释了为啥它比拍照费性能吗?”第三章性能真相——费,就费在从算一个点到解一段方程老师傅赞许地点头——这正是费性能三个字的根源离散检测只需比对一个静止形状而连续检测要构造扫掠体、还要解出碰撞方程——计算量是天差地别的┌────────────────────────────────────────────────┐ │ ⚖️ 性能真相:费在从比一个点,到解一段方程! │ │ │ │ 离散检测的计算量(轻): │ │ 只做一件事:拿子弹此刻的静止形状, │ │ 去和附近的墙比对有没有重叠—— │ │ → 一次静态的重叠判断,又快又省! │ │ │ │ 连续检测的计算量(重): │ │ ① 要根据A、B构造出扫掠体(拉长的胶囊) │ │ ② 要沿着这个扫掠体的路径,去和沿途 │ │ 【所有可能挡路的墙】做扫掠相交计算 │ │ ③ 每一次都要解方程、求出最小的t、 │ │ 比较谁是最早撞上的那个 │ │ → 一连串的构造求解比较,自然重得多! │ │ │ │ ┌──────────────────────────────────┐ │ │ │ 离散:回答此刻这个点,重叠没?(判断) │ │ │ │ 连续:回答这一路,最早在哪撞?(求解) │ │ │ │ │ │ │ │ 判断一个点 vs 求解一段过程 │ │ │ │ → 计算量的差距,就在这里! │ │ │ └──────────────────────────────────┘ │ │ │ │ → 所以它费性能,不是费在别处,而是费在 │ │ 把简单的一次比对,升级成了一整套 │ │ 构造扫掠体、求解碰撞方程的精密计算! │ │ │ │ → 这也正是好钢用在刀刃上的底层缘由: │ │ 这套精密计算贵,只给真正高速的物体用! │ └────────────────────────────────────────────────┘性能真相为什么连续检测费性能把两者的计算量摊开一比答案就一目了然——离散检测·计算量轻它只做一件事——拿子弹此刻的静止形状去和附近的墙比对有没有重叠。这是一次静态的重叠判断又快又省。连续检测·计算量重它要做一连串更重的活——① 根据 A、B 两点构造出扫掠体拉长的胶囊② 沿着这个扫掠体的路径去和沿途所有可能挡路的墙做扫掠相交计算③ 每一次都要解方程、求出最小的 t还要比较出谁才是最早撞上的那一个。一句话点破本质离散回答的是此刻这个点重叠没有——这是一次判断连续回答的是这一路走来最早在哪撞上——这是一次求解。“判断一个点” 和 “求解一段过程”计算量的差距就在这里。所以连续检测费性能不是费在什么玄乎的地方而是费在**“把简单的一次比对升级成了一整套构造扫掠体、求解碰撞方程的精密计算”**。回扣上篇这也正是好钢用在刀刃上的底层缘由——这套精密计算成本高昂所以才只舍得给那些真正高速、真会穿墙的物体用绝不能挥霍在满场慢吞吞的物体上。小李彻底通透“全懂了!费性能的真相,就费在’从比一个点、到解一段方程’!离散只做一次静态重叠判断——回答’此刻这个点重叠没’,又快又省;连续要①构造扫掠体②沿路径和所有挡路的墙做扫掠相交③每次都解方程求最小t、还要比较谁最早撞上——回答’这一路最早在哪撞’,是一整套构造求解比较!‘判断一个点’和’求解一段过程’,计算量差距就在这!这也正是好钢用在刀刃上的底层缘由——这套精密计算贵,只给真正高速的物体用!这底层原理我彻底钻透了!”第四章终极总结——连续检测底层计算的完整图谱小李把这场钻到底层的领悟浓缩成一张表┌────────────────┬──────────────────────────────────┐ │ 连续检测·底层原理│ 要点 │ ├────────────────┼──────────────────────────────────┤ │ 破除误会 │ 不是真射光,是计算上构造几何体 │ │ 第一步·构造 │ 把运动的球→静止拉长的胶囊体 │ │ 转化的本质 │ 动态会不会撞→静态重不重叠 │ │ 第二步·扫掠 │ 参数化方程 位置(t)A(B-A)×t │ │ 求解目标 │ 求最小t,使子弹刚好接触墙面 │ │ 解出t能干嘛 │ 知碰撞时刻、位置→精确挪到撞击点 │ │ 高明之处 │ 非事后发现穿了,是主动解出撞击点 │ │ 费性能真相 │ 从判断一个点→求解一段过程 │ │ 好钢用刀刃的根 │ 精密计算成本高,只给高速物体用 │ │ 一句话 │ 把运动的疑难,化成静态的方程, │ │ │ 精确解出那个最早的答案! │ └────────────────┴──────────────────────────────────┘小李摸着这张表悟出了连续检测底层原理的题眼我总算把这’探照灯’骨子里的计算门道,钻到底了——**原来它那看似神奇的’全程盯防’,骨子里是一套极朴素的智慧:先把一个’运动中、难以捉摸’的动态难题,巧妙地’凝固’成一个’静止的、可以从容求解’的几何问题;再用一个含’时间t’的方程,把整段过程参数化**,一举解出那个’最早、最精确’的答案——它不靠’事后补救’,而靠’提前算清’!****而它给我最深的启示是:面对一个’瞬息万变、难以直接把握’的复杂难题,高手的破解之道,往往是先想办法把它’转化’成一个自己熟悉的、可以从容求解的简单模型(化动为静、化未知为方程);然后不满足于’知道有没有问题’,更要精确地解出’问题最早出现在哪个临界点’——化繁为简的转化之力、与求根究底的精确之心,正是穿透一切复杂难题的底层功夫。尾声一套化动为静、解出临界的计算亦是人生的智慧小李这场对连续检测底层原理的深钻从知其然不知其所以然的痒痒出发一路钻到了最底层——看清了它构造扫掠体化动为静、参数化方程解出临界点的精密内核也终于明白了费性能三字的计算根源。但当我们合上书会发现这套化动为静、解出临界的计算背后竟也舒展着几分耐人寻味的人生哲理。第一破解动态难题的高招是把它转化成一个能从容求解的静态模型。这套底层计算最精妙的第一手是——它不去硬碰运动中会不会撞这个瞬息万变的动态难题而是巧妙地把运动的球凝固成静止的胶囊体将棘手的动态问题转化成了自己拿手的静态几何问题。这何尝不是一记对解决问题的深刻点拨我们面对难题时常常有一种硬碰硬的执拗——问题以什么面貌出现,就死死盯着那个面貌去强攻,结果被它瞬息万变、难以捉摸的表象搞得焦头烂额、无从下手。而真正的高手却懂得转化的力量:不与难题的棘手表象死磕,而是先退一步,想办法把这个陌生的、动态的、难以直接把握的问题,翻译成一个自己熟悉的、可以从容处理的模型——化动为静、化陌生为熟悉、化无从下手为按部就班。就像那连续检测面对高速运动的难题,不去追那颗快得抓不住的球,而是聪明地把它凝固成一根静止的胶囊,难题便迎刃而解。解决复杂问题的关键往往不在于更用力地正面强攻,而在于更巧妙地换个模型:能把动态化为静态、把未知化为已知、把陌生化为熟悉的转化之力,才是四两拨千斤、穿透复杂难题的真正高招。第二与其事后补救穿了帮不如提前算清那个临界点——防患于精确的预判。那解出最早撞击时刻 t的精妙藏着一份深远的智慧——连续检测的高明不在于事后发现子弹穿墙了再补救而在于提前解方程、精确算出飞行途中最早撞上的那个临界点从而防患于未然。这道破了一个关于预见与主动的深刻真理:面对可能出问题的事,存在两种截然不同的境界。低一等的是离散式的被动——只在一个个孤立的时间点上抽查,一旦问题恰好在盲区里发生,便事后才发现已经穿帮,只能手忙脚乱地补救,甚至已然酿成大祸、无从挽回。高一等的是连续式的主动——不满足于知道有没有出问题,更要提前推演整个过程、精确地算出问题最早会在哪个临界点爆发,从而在它真正发生之前,就从容地预置好应对。这份提前解出临界点的功夫正是防患于未然最精确的形态:它不靠运气抽查、不靠事后补救,而靠对全过程的透彻推演,把风险扼杀在那个精确的临界点之前。真正有远见的人做事从不只盯着眼前的孤立瞬间、赌问题不会发生;而是习惯于推演全程、算清那个最早出岔子的临界点,在风险萌芽的精确刹那之前,就已从容布好了局。预见临界,方能主动;算清全程,才无后患。第三不满足于知道有没有更要求解精确到哪一刻——粗知与精解境界迥异。那不止判断撞没撞、更要解出精确到 t0.4 的撞击点的执着藏着一份对精确的敬意——离散检测只回答重叠没有(一个模糊的是非),连续检测却要解出最早在飞行进度百分之几处撞上(一个精确的答案);从粗略的判断,到精确的求解,境界天差地别。这道破了一个关于认知深度的处世智慧:对同一件事,“粗略地知道个大概和精确地掌握到细节”,是两种截然不同的境界,也往往决定了成败的高下。许多人做事满足于差不多知道有没有问题这种模糊的粗知,便以为已经掌握了——可正是这种知其大概、不求甚解的粗糙,让他们在真正需要精确的关键处露了怯、栽了跟头。而那些真正专业、可靠的人从不满足于是非有无的模糊判断,而是有一股求根究底、精确到刻度的较真——他们要知道的不是大概会不会出事,而是具体在哪个精确的临界点、以怎样的方式出事。正是这种从粗知到精解的深挖,这份对精确的执着与敬意,把外行与内行、平庸与卓越,清晰地分了界。粗知者只见轮廓精解者洞见刻度;而世间那些真正棘手的问题,往往就败在那个差之毫厘的精确细节上——肯下功夫求得精解的人,才配得上驾驭真正复杂的挑战。下次当你面对瞬息万变的动态难题只会硬碰硬地强攻或只在孤立瞬间抽查、赌问题不发生而疏于预见又或满足于差不多知道个大概的粗知时请记得这套化动为静、解出临界的智慧——像那 **化动为静的扫掠体构造**那样面对难以直接把握的动态难题先巧妙地把它转化成一个能从容求解的静态模型以转化之力四两拨千斤像那 **解出临界的扫掠方程**那样不满足于事后补救而是提前推演全程、精确算出那个最早出岔子的临界点防患于精确的预判更像那 **求解到刻度的精确之心**那样不满足于知道有没有更要求解精确到哪一刻以求根究底的较真把粗知升华为精解。于是你成了那个善转化、能预见、又求精确的通达之人。“Unity 连续碰撞检测的底层计算原理”就是这门关于化动态为静态的转化之力、防患于临界的预见之明、求精确到刻度的较真之心的、朴素而深刻的智慧。它告诉我们破解动态难题的高招是把它转化成能从容求解的静态模型与其事后补救穿帮不如提前算清那个精确的临界点不满足于知道有没有、更要求解精确到哪一刻。它像一句朴素的箴言提醒着我们——别对着瞬息万变的动态难题一味硬碰硬地强攻学会用转化的巧劲把它凝固成一个自己熟悉、能从容求解的静态模型化动为静、化繁为简方能四两拨千斤别只在孤立的瞬间抽查、赌问题不会发生学会提前推演整个过程、精确算出那个最早出岔子的临界点把风险扼杀在它真正爆发之前这才是防患于未然最精确的形态别满足于差不多知道个大概的粗知学会求根究底、精确到刻度因为世间真正棘手的问题往往败在差之毫厘的细节上肯求精解的人才配驾驭真正复杂的挑战——一个懂得化动为静、预见临界、求解精确的人才能像那精密的连续碰撞检测把捉摸不定的动态难题凝固成从容可解的静态方程提前解出那个最早的临界刹那精确到飞行进度的百分之几于是遇难题者善转化谋长远者能预见求卓越者不苟且活成一个既有化繁为简的巧思、又有防患未然的远见、更有求根究底的较真的精密而通达之人。这就是藏在Unity 连续碰撞检测底层计算原理那套化动为静、解出临界的精密计算背后最深、也最美的浪漫。