🌀曲面细分求交:在无限细节中捕捉交点的浪漫

"我不是为了找到你,而是为了在无尽细节中一次次靠近你。"

------ 一个光线对着曲面说的话


一、故事开头:谁在曲面上等着谁?

在图形学的世界里,光线穿梭于空间、穿过镜子、掠过水面。它们不是来观光的,而是为了找到那些与它们发生交集的表面 ------这就是我们今天要讲的浪漫故事主线:曲面细分求交(Subdivision Surface Intersection)

想象你手里拿着一束光线,正对着一个用猫咪一样柔软曲线构成的物体,而你需要回答一个问题:

"我们在哪里相遇?"


二、交点何来?图形学的哲学三问

  1. 我们真的有交点吗?
  2. 如果有,它在哪里?
  3. 我怎么找它?(而不算到崩溃)

对于光线和三角形这样的刚体,我们有经典的 Möller-Trumbore 算法。

但当你的目标是光滑连续的曲面,比如 Pixar 用的 Subdivision Surfaces,你会发现自己仿佛面对一个没有边界的宇宙:它是由多边形经过迭代细化、慢慢逼近一条丝滑的线条所形成的。

这就引出了我们的主角技能:


三、曲面细分(Subdivision)是什么?

简单来说,它是把多边形网格不断细分成更小的多边形,直到你的眼睛看不出它其实是很多小面拼起来的。

常见的细分方法包括:

  • 🧱 Catmull-Clark:四边形细分神器(适合盒子和有角的模型)
  • 🕸️ Loop subdivision:三角形友好款,适合曲面(我们今天的主角)
  • Doo-Sabin:老派但优雅,适合工业模型

它们都像是"美容滤镜":让棱角分明的模型逐步变成丝滑光滑的现代网红脸。


四、求交基本套路:从粗到细,再到极致

既然曲面无限细腻,那我们怎么找光线和它的交点?难道要穷尽世界的每一丝可能?

其实不然,我们有一套高效又优雅的流程:


1. Bounding First:别在错误的区域浪费青春

在开始前,先看看光线和曲面的**包围盒(Bounding Box)**有没有交集。如果根本没碰上,那就别白费力气了。

arduino 复制代码
const box = new THREE.Box3().setFromObject(surfaceMesh)
if (!raycaster.ray.intersectsBox(box)) {
  console.log('❌ 没有交集,直接告辞')
  return
}

2. Recursive Subdivide:以细致换真相

从一个较粗的三角面开始,我们不断将其分为更小的三角面,每次细分,都会更接近真实的曲面。

scss 复制代码
function subdivideTriangle(tri, depth) {
  if (depth === 0) return [tri]

  const [a, b, c] = tri
  const ab = midpoint(a, b)
  const bc = midpoint(b, c)
  const ca = midpoint(c, a)

  return [    ...subdivideTriangle([a, ab, ca], depth - 1),
    ...subdivideTriangle([ab, b, bc], depth - 1),
    ...subdivideTriangle([ca, bc, c], depth - 1),
    ...subdivideTriangle([ab, bc, ca], depth - 1),
  ]
}

function midpoint(p1, p2) {
  return new THREE.Vector3().addVectors(p1, p2).multiplyScalar(0.5)
}

3. Intersection Test:局部平面上的爱情故事

当细分到一定程度时,每一个小面可以近似认为是平的。这时,就可以用经典三角形求交算法啦!

javascript 复制代码
function intersectsRay(ray, triangle) {
  const intersect = new THREE.Triangle(...triangle).intersectRay(ray, new THREE.Vector3())
  return intersect || null
}

4. 精度判断:不要太贪,差不多就行

你可以设置一个"精度阈值"或"最大细分深度",来决定啥时候收手:

ini 复制代码
const MAX_DEPTH = 6
const EPSILON = 0.001

太细会烧电脑,太粗又不准,艺术与工程的边界就在这之间摇摆。


五、实际应用场景:Ray Picking、阴影、碰撞

这个方法适用于:

  • 🧲 鼠标拾取超高精度模型(点击角色衣服上的一根毛)
  • 🕳️ 精准阴影计算(避免奇怪漏光)
  • ⚡ 精密物理碰撞检测(比如绸缎和手掌互动)

六、用 Three.js 模拟一个 Demo?

当然!这是一个基础框架,帮你从简单三角网格开始实现细分求交:

javascript 复制代码
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()

canvas.addEventListener('click', (event) => {
  mouse.x = (event.clientX / window.innerWidth) * 2 - 1
  mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
  raycaster.setFromCamera(mouse, camera)

  // 假设 mesh 是你细分的对象
  const triangles = getInitialTriangles(mesh)
  const subdivided = []

  triangles.forEach(tri => {
    subdivided.push(...subdivideTriangle(tri, MAX_DEPTH))
  })

  for (const tri of subdivided) {
    const hit = intersectsRay(raycaster.ray, tri)
    if (hit) {
      console.log('🎯 命中点:', hit)
      break
    }
  }
})

七、结语:在无限细节中,做一个有限清醒的开发者

曲面细分求交不仅是一个图形算法的技术挑战,更是一种对连续空间近似离散化的艺术探索。

它告诉我们:

  • 真实的曲面可能无穷无尽,但你可以通过细分和递归逼近真相
  • 图形学,不只是画图,它也是哲学、工程与美的结合
  • 最重要的是 ------ 别用无限递归卡死浏览器

"我不想知道曲面上的每个点,我只想知道和我有关的那个。"

------ 来自一束投射进 VR 世界的光线


如果你想继续探讨:

  • 如何用 GPU 实现曲面细分交点检测?
  • 如何对 NURBS 曲面求交?
  • 或者想让这套算法在你的 Three.js 项目中跑起来?

随时召唤我: "Subdivide me, Ray!" 🌟


相关推荐
我命由我123453 分钟前
VSCode - VSCode 快速跳转标签页
开发语言·前端·ide·vscode·编辑器·html·js
爱学习的茄子30 分钟前
告别 useState 噩梦:useReducer 如何终结 React 状态管理混乱?
前端·深度学习·react.js
油丶酸萝卜别吃44 分钟前
怎么判断一个对象是不是vue的实例
前端·javascript·vue.js
科技D人生1 小时前
Vue.js 学习总结(18)—— Vue 3.6.0-alpha1:性能“核弹“来袭,你的应用准备好“起飞“了吗?!
前端·vue.js·vue3·vue 3.6·vue3.6
鬼鬼_静若为竹1 小时前
我终于也是会写3d小游戏的人了,发个掘金显摆显摆
前端
Mintopia1 小时前
Three.js 滚动条 3D 视差动画原理解析
前端·javascript·three.js
啃火龙果的兔子2 小时前
在 React 中根据数值动态设置 SVG 线条粗细
前端·react.js·前端框架
蓝乐2 小时前
Angular项目IOS16.1.1设备页面空白问题
前端·javascript·angular.js
归于尽2 小时前
揭秘:TypeScript 类型系统是如何给代码穿上 “防弹衣” 的
前端·typescript
today喝咖啡了吗2 小时前
uniapp 动态控制横屏(APP 端)
前端·javascript·uni-app