3d 从三维三次bezier路径获取一致法线

3d 从三维三次bezier路径获取一致法线,3d,unity3d,bezier,normals,3d,Unity3d,Bezier,Normals,我正在编写一个包含BezierPoints列表的BezierPath类。每个BezierPoint都有一个位置、一个内切线和一个外切线: BezierPath包含用于从路径获取线性位置和切线的函数。我的下一步是提供从路径获取法线的功能 我知道3D中任何给定的直线都会有无限数量的垂直直线,所以不会有固定的答案 我的目标是让用户能够在每个Bezier点指定法线(或滚动角度?),我将在这些点之间进行插值,以获得沿路径的法线。我的问题是我不知道如何选择起始切线(默认切线应该是什么?) 我第一次尝试获取

我正在编写一个包含BezierPoints列表的BezierPath类。每个BezierPoint都有一个位置、一个内切线和一个外切线:

BezierPath包含用于从路径获取线性位置和切线的函数。我的下一步是提供从路径获取法线的功能

我知道3D中任何给定的直线都会有无限数量的垂直直线,所以不会有固定的答案

我的目标是让用户能够在每个Bezier点指定法线(或滚动角度?),我将在这些点之间进行插值,以获得沿路径的法线。我的问题是我不知道如何选择起始切线(默认切线应该是什么?)

我第一次尝试获取起始切线是使用Unity3D方法:

这将产生以下结果(绿线为切线,蓝色为法线):

在大多数情况下,这似乎是一个很好的基本结果,但在某些方向上似乎有突然的变化:

所以我的问题是:有没有一种好方法可以获得3D中线条的一致默认法线

谢谢,
Ves

获取贝塞尔曲线上一点的法线实际上是非常直接的,因为法线只是垂直于函数的切线(朝向曲线的行进方向平面),而贝塞尔曲线的切线函数实际上只是另一条贝塞尔曲线,比它低1阶。让我们找到三次贝塞尔曲线的法线。正则函数,其中(a,b,c,d)是单个维度中的曲线坐标:

function computeBezier (t, a, b, c, d) {
  return a * (1-t)³ + 3 * b * (1-t)² * t + 3 * c * (1-t) * t² + d * t³
}
请注意,贝塞尔曲线是对称的,
t
1-t
之间的唯一区别是曲线的哪一端代表“起点”。使用
a*(1-t)³
表示曲线从
a开始。使用
a*t³
将使其从
d
开始

让我们用以下坐标定义一条快速曲线:

a = (-100,100,0)
b = (200,-100,100)
c = (0,100,-500)
d = (-100,-100,100)

为了获得该函数的法线,我们首先需要:

完成了。计算导数非常简单(贝塞尔曲线的一个奇妙特性)

现在,为了得到法线,我们需要取某个值
t
处的归一化切线向量,并将其旋转四分之一圈。我们可以在很多方向上转动它,所以进一步的限制是我们只想在由切向量定义的平面上转动它,切向量“紧挨着它”,相隔无限小的间隔

任何Bezier曲线的切线向量都可以通过获取任意数量的尺寸并分别计算它们来形成,因此对于3D曲线:

             | computeBezierDerivative(t, x values) |    |x'|
Tangent(t) = | computeBezierDerivative(t, y values) | => |y'|
             | computeBezierDerivative(t, z values) |    |z'|
同样,计算起来也很简单。要使该向量(或实际上任何向量)正常化,我们只需按其长度执行向量除法:

                   |x'|
NormalTangent(t) = |y'| divided by sqrt(x'² + y'² + z'²)
                   |z'|
让我们把这些画成绿色:

现在唯一的技巧是找到旋转切线向量的平面,将切线转换为法线。我们知道我们可以使用另一个任意接近我们想要的t值,并将其转化为在同一点附近的第二个切线向量,以找到具有任意正确性的平面,因此我们可以这样做:

给定一个原点
f(t1)=p
我们取一个点
f(t2)=q
t2=t1+e
,其中e是一些很小的值,比如0.001——这个点
q
有一个导数
q'=点导数(t2)
,为了让我们更容易,我们通过
p-q
移动切向量一点点,这样两个向量都从
p
开始。很简单

然而,这相当于在
p
处计算一阶导数和二阶导数,然后通过将二者相加形成第二个向量,因为二阶导数给出了一点切线的变化,因此,将二阶导数向量与一阶导数向量相加,得到平面上
p
处的两个向量,而无需找到相邻点。这在导数中存在不连续性的曲线中非常有用,即具有尖点的曲线

我们现在有两个向量,在同一个坐标上偏离:我们的实切线和“下一个”点的切线,这两个点非常接近,可能是同一个点。谢天谢地,由于贝塞尔曲线的工作原理,第二条切线从来都不相同,但略有不同,而“略有不同”正是我们所需要的:如果我们有两个归一化向量,从同一点开始,但指向不同的方向,我们可以找到一个轴,在这个轴上,我们需要旋转一个来得到另一个,只需在它们之间取一个,这样我们就可以找到它们都经过的平面

顺序问题:我们计算c=正切₂ ×切线₁,因为如果我们计算c=切线₁ ×切线₂我们将在“错误”方向上计算旋转轴和结果法线。纠正这一点实际上只是一个“取向量,乘以-1”的结尾,但是为什么要在我们能够正确得到它之后再纠正呢,这里。让我们看看蓝色的旋转轴:

现在我们有了我们所需要的一切:为了将我们的归一化切线向量转换成法向量,我们所要做的就是围绕我们刚刚找到的轴旋转四分之一圈。如果我们把它们转到一边,我们得到法线,如果我们把它们转到另一边,我们得到背面法线

对于3D中绕轴的任意旋转,四分之一旋转通常很特殊,因为它们大大简化了数学:要在旋转轴c上旋转一个点,旋转矩阵为:

    |     c₁²     c₁*c₂ - c₃  c₁*c₃ + c₂ |
R = | c₁*c₂ + c₃      c₂²     c₂*c₃ - c₁ |
    | c₁*c₃ - c₂  c₂*c₃ + c₁      c₃²    |
其中1、2和3下标实际上只是向量的x、y和z分量。这仍然很容易,剩下的就是矩阵旋转我们的法线切线:

n = R * Tangent "T"
即:

    | T₁ * R₁₁ + T₂ * R₁₂ + T₃ * R₁₃ |    |nx|
n = | T₁ * R₂₁ + T₂ * R₂₂ + T₃ * R₂₃ | => |ny|
    | T₁ * R₃₁ + T₂ * R₃₂ + T₃ * R₃₃ |    |nz|
我们有法向量
n = R * Tangent "T"
    | T₁ * R₁₁ + T₂ * R₁₂ + T₃ * R₁₃ |    |nx|
n = | T₁ * R₂₁ + T₂ * R₂₂ + T₃ * R₂₃ | => |ny|
    | T₁ * R₃₁ + T₂ * R₃₂ + T₃ * R₃₃ |    |nz|
                    |nx|
n = c × tangent₁ => |ny|
                    |nz|