Unraveling Beziér Splines (转载)
Unraveling Beziér Splines
by Justin Reynen
原文网址:http://www.gamedev.net/reference/articles/article888.asp
简介
毫无疑问,您已经听说过这些用于处理曲线和曲面的新技术,二次Bezier曲线,三次Bezier曲线,非均匀有理B样条(NURBS),并且非常迫切的想要把所有这些东西搞清楚,甚至可能尝试将这些技术中的一种加入到您正在制作的游戏或演示中。
这篇文章是帮您揭开这些谜团的最好的开始。
在本文中,我会向你解释Bezier曲线的数学原理,编写程序画出一条二次Bezier样条(最简单的样条),然后你就可以自己实现任意你所需要次数的Bezier曲线了。
Bezier曲线的数学原理
好了,我们将要作的第一件事是温习一下Bernsteins基本方程。在下面我会给出一个公式,别着急,你看,她很简单!
1 = t + (1 - t)
T为任意值等式都成立,但是我们所关注的T值是在0和1之间。
好,现在我们已经了解Bernsteins基本方程了,下面将要开始生成曲线的工作。我们选择二次Bezier曲线作为切入点,因为它是最容易实现的。。
好了,现在已经确定我们想要一条二次Bezier曲线,我们需要从万能的基本方程派生出我们需要的方程,现在就开始吧!
对于二次Bezier曲线,我们需要将Bernsteins基本方程的两段同时取平方;对于三次Bezier曲线,我们就去立方,以此类推。下面是两边取平方后的Bernsteins基本方程
1^2 = (t + (1 - t))^2
1 = t^2 + 2*t*(1-t) + (1-t)*(1-t)
可能你注意到有很多项我没有展开,因为这样更有助于理解和编码。
好的,现在我们获得了从基本方程衍生出来的3个函数。你可能会问他们在哪儿,就在你的面前!我们的函数就是等式右侧的那3个多项式。我们将这3个函数取名为Bx(t)。
#define B1(t) (t*t)
#define B2(t) (2*t*(1-t))
#define B3(t) ((1-t)*(1-t))
你应该注意到,对于二次Bezier曲线有三个函数,对于三次Bezier曲线有四个参数,以此类推。在本文的演示程序中我已经计算了三次和四次曲线的方程,请查看"Functions.txt"
好了,现在我们已经搞清楚Bezier曲线的简单数学意义,接下来让我们谈一下编码吧。
编码实现Bezier曲线
好的,我们已经准备好了基本函数,现在是充分利用他们的时候了。
我认为现在你应该意识到与这3个函数相对应,有3个控制点,我们将这些点起名叫Cx。你可以向这样来存储这些点:
typedef struct sCPoint
{
int x;
int y;
} C_POINT;
注:我们将要生成的是2DBezier曲线,如果你想要生成3DBezier曲线的话可以在结构中加入一个z元素。
C_POINT controlP[3];
好了,现在我们将要接触在屏幕上绘制Bezier曲线的真正代码了。目前,我们的曲线只存在于想象之中,而且我们只有数学方程。我们需要做的就是利用数学方程,沿着曲线将点画在屏幕上。
这就给我们带来了最后一个问题“采样率”。采样率就是我们在曲线上画点时,每绘制一个点需要沿着曲线移动的距离。采样率是个小数,你可以简单的把它理解为百分比。例如采样率0.5的含义就是:从曲线的一端开始,在这里绘制一个点;然后沿曲线移动50%,然后绘制一个点,然后再移动50%(到达曲线的末端),绘制最后一个点。这样就会在曲线的左端、中间和右端共绘制了3个点。我们需要一个计数器来记录已经绘制的曲线的百分比,然后我们每次对计数器累加,直到计数器到达1为止。定制的采样率越小,我们绘制的点就越多,曲线看起来就越精细。
//Note: it is assumed that each controlP has already been filled out with
//an x and y coordinate.
double count; //used as our counter
double detailBias; //how many points should we put on our curve.
double x,y; //used as accumulators to make our code easier to read
detailBias = 1 / 50; //we'll put 51 points on out curve (0.02 detail bias)
do
{
x = controlP[0].x*B1(count) + controlP[1].x*B2(count) + controlP.x[2]*B2(count);
y = controlP[0]*B1.y(count) + controlP[1].y*B2(count) + controlP.y[2]*B2(count);
PutPixel(x,y,RGB(255,255,255));
count += detailBias;
}while( count <= 1);
请注意我们是如何获得x和y的值。我们用控制点与相应的基本方程相乘,然后将他们加在一起。我们对x和y坐标进行了这样的操作,如果我们是工作在3维空间,那么我们也要对z坐标进行一样的运算。
用Bezier曲线创建曲面
我现在就要揭开如何绘制Bezier曲面的神秘面纱。这是个很大的课题,完全可以把它单独拿出来写一篇文章,而且它也超出了本文的研究范畴。我将只介绍二次Bezier曲面,并且没有提供演示代码。我将会介绍如何生成曲面上的点,你可以很容易的将其改造成3次或者4次曲面。
上面这幅图是最基本的差值曲面,它由2次Bezier曲线组成。它有9个控制点,你可以想象这幅图的x轴沿7、8、9方向,y轴沿7、4、1方向。为了让事情简单,我把采样率定为0.25,这样我们在每条线上都会产生5个点。在整个面上将会生成25个点。我们可以简单的把这些点以行和列分组,这样我们的数据结构就会像这样:
C_POINT points[5][5];
我们要做的工作就是对每一条曲线进行插值,产生新的控制点,然后再由新生成的控制点继续插值,这样就生成了我们需要的曲面。
In Conclusion
我希望这篇文章可以帮助你了解Bezier曲线的用法并且能够将他们应用到你的游戏或者演示当中。在本文结束之前,提出一些关于Bezier曲线的优点和缺点:
Pro's:
在层次细节模型当中,曲面是一个非常好的用于减少绘制多边形数量的工具。你可以很容易的将这些面减少到只有4个多边形(对于2次方程),而且你可以很容易的做到这一点(通过改变采样率)。它也可以很容易的使表面变得平滑。另外,如果你计划添加摄像机移动功能的话,Bezier曲线对于生成平滑的移动路线,动画移动来说是至关重要的。
Con's:
曲面对于大多数引擎来说最主要的缺点就是要花费太长的时间将曲面变成多边形,你在载入时做这些事情也不会有太大改变。但是如果你每一帧都进行计算的话,你可能会注意到它存在巨大的性能问题。这就是大多数引擎开发者对曲面发牢骚的最主要原因。
(happykevins译)