“贝塞尔曲线”和“贝塞尔曲面”,这些方法通常用于高效地对各种曲面 3D 物体进行建模。用它们可以生成任意精度的曲线。
二次贝塞尔曲线
二次贝塞尔曲线
由一组参数方程定义, 方程组中使用 3 个控制点指定特定的曲线的形状,每个控制点都是 2D 空间中的一个点。
针对各种 t 值收集大量的点 P(t),则会产生一条曲线,如上图所示。采样的 t 的参数值越多,生成的点 P(t)越多,得到的曲线则越平滑。
现在可以导出二次贝塞尔曲线的分析定义。首先,我们注意到连接两个点 pa 和 pb 的线段 pa−pb 上的任意点 p 可以用参数 t 表示如下:
p
(
t
)
=
t
p
a
+
(
1
−
t
)
p
b
p(t)=tp_a+(1-t)p_b
p(t)=tpa+(1−t)pb
使用该等式,我们解出点 p01 和 p12(分别在 p0−p1 和 p1−p2 上的点)如下:
p
01
=
t
p
1
+
(
1
−
t
)
p
0
p
12
=
t
p
2
+
(
1
−
t
)
p
1
p_{01}=tp_1+(1-t)p_0\\ p_{12}=tp_2+(1-t)p_1
p01=tp1+(1−t)p0p12=tp2+(1−t)p1
P
(
t
)
=
(
1
−
t
)
2
p
0
+
(
−
2
t
2
+
2
t
)
p
1
+
t
2
p
2
P(t) = (1-t)^2p_0 + (-2t^2+2t)p_1+t^2p_2
P(t)=(1−t)2p0+(−2t2+2t)p1+t2p2
可参见Blog“第7章 CustomView绘图进阶-贝济埃曲线”
三次贝塞尔曲线
P
(
t
)
=
∑
i
=
0
3
p
i
B
i
(
t
)
其
中
:
B
0
(
t
)
=
(
1
−
t
)
3
B
1
(
t
)
=
3
t
3
−
6
t
2
+
3
t
B
2
(
t
)
=
−
3
t
3
+
3
t
2
B
3
(
t
)
=
t
3
P(t)=\sum_{i=0}^3{p_iB_i(t)}\\ 其中:\\ B_0(t)=(1-t)^3\\ B_1(t)=3t^3-6t^2+3t\\ B_2(t)=-3t^3+3t^2\\ B_3(t)=t^3
P(t)=i=0∑3piBi(t)其中:B0(t)=(1−t)3B1(t)=3t3−6t2+3tB2(t)=−3t3+3t2B3(t)=t3
渲染贝塞尔曲线时,可以使用许多不同的技术。其中一种方法是,使用固定的增量,在0.0~1.0 范围内,迭代增加得出 t 的后继值。例如,当增量为 0.1 时,我们可以使用 t 值为0.0、 0.1、 0.2、 0.3 等的循环。对于 t 的每个值,计算贝塞尔曲线上的对应点,并绘制连接连续点的一系列线段,算法如下:
void drawBezierCurve(controlPointVector C) {
currentPoint = C[0];// 曲线从第一个控制点开始
t = 0.0;
while (t <= 1.0) {
// 计算混合函数在t时对控制点的加权和,作为曲线的下一个点
nextPoint = (0, 0);
for (int i = 0; i <= 3; i++)
nextPoint += blending(i, t) * C[i];
drawLine(currentPoint, nextPoint);
currentPoint = nextPoint;
t += increment;
}
}
double blending(int i, double t) {
switch(i) {
case 0: return (1-t)*(1-t)*(1-t);// (1-t)^3
case 1: return 3*t*(1-t)*(1-t);// 3t(1-t)^2
case 2: return 3*t*t*(1-t);// 3t^2(1-t)
case 3: return t*t*t;// t^3
}
}
下面画一个曲线:
vector<glm::vec3> drawBezierCurve(vector<glm::vec3> CtrlP) {
vector<glm::vec3> points;
points.push_back(CtrlP[0]);
double t = 0.0;
while (t <= 1.0) {
glm::vec3 nextPoint;
nextPoint.x = 0;
nextPoint.y = 0;
nextPoint.z = 0;
for (int i = 0; i <= 3; i++) {
nextPoint.x += blending(i, t) * C[i].x;
nextPoint.y += blending(i, t) * C[i].y;
}
points.push_back(nextPoint);
t += 0.1;
}
return points;
}
void setupVertices(void) {
vector<glm::vec3> CtrlP;
CtrlP.push_back(glm::vec3(0.1, 0.5, 0));
CtrlP.push_back(glm::vec3(0.4, 0.9, 0));
CtrlP.push_back(glm::vec3(0.6, 0.95, 0));
CtrlP.push_back(glm::vec3(0.9, 0.55, 0));
vector<glm::vec3> points = drawBezierCurve(CtrlP);
...
}
void display(...) {
...
glLineWidth(1.0);
glDrawArrays(GL_LINE_STRIP, 0, values.size() / 3);
}
在XY平面画了一条三次贝塞尔曲线,如下图:
二次贝塞尔[曲面]
贝塞尔曲线定义了曲线(在 2D 或 3D 空间中),而贝塞尔曲面定义了 3D 空间中的曲面。将我们在曲线中看到的概念扩展到曲面, 需要将参数方程组中的参数个数从一个扩展到两个。对于贝塞尔曲线,我们将参数称为 t。对于贝塞尔曲面,我们将参数称为 u 和 v。曲线由点 P(t)组成,而曲面将由点 P(u, v)组成,如下图所示。
对于二次贝塞尔曲面,每个轴 u 和 v 上有3 个控制点,总共 9 个控制点。下图使用蓝色展示了一组共 9 个控制点(通常称为控制点“网格”
)的示例,以及相应的曲面(红色)。
P
(
u
,
v
)
=
∑
i
=
0
2
∑
j
=
0
2
p
i
j
B
i
(
u
)
B
j
(
v
)
其
中
,
B
0
(
u
)
=
(
1
−
u
)
2
B
1
(
u
)
=
−
2
u
2
+
2
u
B
2
(
u
)
=
u
2
B
0
(
v
)
=
(
1
−
v
)
2
B
1
(
v
)
=
−
2
v
2
+
2
v
B
2
(
v
)
=
v
2
P(u,v)=\sum_{i=0}^2\sum_{j=0}^2{p_{ij}B_i(u)B_j(v)}\\ 其中,\\ B_0(u)=(1-u)^2\\ B_1(u)=-2u^2+2u\\ B_2(u)=u^2\\ B_0(v)=(1-v)^2\\ B_1(v)=-2v^2+2v\\ B_2(v)=v^2\\
P(u,v)=i=0∑2j=0∑2pijBi(u)Bj(v)其中,B0(u)=(1−u)2B1(u)=−2u2+2uB2(u)=u2B0(v)=(1−v)2B1(v)=−2v2+2vB2(v)=v2
三次贝塞尔[曲面]
从二次曲面到三次曲面需要使用更大的网格—— 4×4 而非 3×3。下图显示了 16 控制点网格(蓝色)和相应曲面(红色)的示例。
P
(
u
,
v
)
=
∑
i
=
0
3
∑
j
=
0
3
p
i
j
B
i
(
u
)
B
j
(
v
)
其
中
,
B
0
(
u
)
=
(
1
−
u
)
3
B
1
(
u
)
=
−
3
u
3
−
6
u
2
+
3
u
B
2
(
u
)
=
−
3
u
3
+
3
u
2
B
3
(
u
)
=
u
3
B
0
(
v
)
=
(
1
−
v
)
3
B
1
(
v
)
=
−
3
v
3
−
6
v
2
+
3
v
B
2
(
v
)
=
−
3
v
3
+
3
v
2
B
3
(
v
)
=
v
3
P(u,v)=\sum_{i=0}^3\sum_{j=0}^3{p_{ij}B_i(u)B_j(v)}\\ 其中,\\ B_0(u)=(1-u)^3\\ B_1(u)=-3u^3-6u^2+3u\\ B_2(u)=-3u^3+3u^2\\ B_3(u)=u^3\\ B_0(v)=(1-v)^3\\ B_1(v)=-3v^3-6v^2+3v\\ B_2(v)=-3v^3+3v^2\\ B_3(v)=v^3\\
P(u,v)=i=0∑3j=0∑3pijBi(u)Bj(v)其中,B0(u)=(1−u)3B1(u)=−3u3−6u2+3uB2(u)=−3u3+3u2B3(u)=u3B0(v)=(1−v)3B1(v)=−3v3−6v2+3vB2(v)=−3v3+3v2B3(v)=v3
补充说明
在 3D 图形中,使用贝塞尔曲线建模对象有许多优点。首先,理论上,这些物体可以任意缩放,并且仍然保持光滑的表面而不“像素化”。其次,许多由复杂曲线组成的物体可以使用贝塞尔控制点集合进行更有效的存储,而不是存储数千个顶点。