文章出处:http://www.pinxue.net/OpenGL/credbook/evaluator.html
思路:
1、定义定值器 | 指定:定值器类型, 每个方向的起止范围、次数(控制点数), 每个方向步进一个单位对应的浮点值个数 控制点清单。 |
2、打开定值器 | viod glEnable(定值器类型); |
3、引用定定值器 | glEvalCoord(Ui,Vi);当前生成的点的值域 注意与1、中取得一致,如果总的值域 为[0,1],则100个点组成的曲线上第10点的 值域为(1-0)*10/100 |
贝泽尔曲线是一个单变量矢量函数:C(u) = [X(u) Y(u) Z(u)] 其中u在某个值域中变化(如[0,1]),
贝泽尔曲面片是双变量矢量函数:S(u,v) = [X(u,v) Y(u,v) Z(u,v)] 其中u和v都在某个值域中变化。排列不一定非要象这所展示的那样是三维的。例如,你也许想在一个平面或纹理坐标中输出两维输出值,或许你想在描述RGBA信息时得到四维值。甚至在处理灰度级时一维输出值也是合理的。
对每个u(或u和v,在曲面中),C()或S()的公式计算得出曲线或曲面上的一个点。要使用一个定值器,首先定义函数C()或S(),启用它(Enable),然后用glEvalCoord1()或glEvalCoord2()命令取代glVertex()。这样,曲线或曲面的顶点就可以和其它任何顶点一样地使用,如用于形成点或线。此外,别一些命令自动生成一系列的顶点,用心生成一个在u上等分的网格。一维和二维定值器是相似的,但一维的描述要简单些,因此先说它。
一维定值器:
这部分提供了一个使用一维定值器以绘制一个曲线的例子。其中控制曲线的的命令和公式稍后说明。
1-D例子:一个简单的贝泽尔曲线
例子11-1所展示的程序用4个控制点绘制一条立方贝泽尔曲线,如图所示:
Example 11-1:用4个控制点绘制一条立方贝泽尔曲线:bezcurve.c
//------------------------------------------------------------------------------------
#include <GL/gl.h>
#include <GL/glu.h>
#include "aux.h"
GLfloat ctrlpoints[4][3] = { //定义控制点
{ -4.0, -4.0, 0.0}, { -2.0, 4.0, 0.0},
{2.0, -4.0, 0.0}, {4.0, 4.0, 0.0}};
void myinit(void)
{
glClearColor(0.0, 0.0, 0.0, 1.0);
glMap1f(GL_MAP1_VERTEX_3, 0.0, 1.0, 3, 4,&ctrlpoints[0][0]);
glEnable(GL_MAP1_VERTEX_3);
glShadeModel(GL_FLAT);
}
void display(void)
{
int i;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord1f((GLfloat) i/30.0);
glEnd();
/* The following code displays the control points as dots. */
glPointSize(5.0);
glColor3f(1.0, 1.0, 0.0);
glBegin(GL_POINTS);
for (i = 0; i < 4; i++)
glVertex3fv(&ctrlpoints[i][0]);
glEnd();
glFlush();
}
void myReshape(GLsizei w, GLsizei h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
if (w <= h)
glOrtho(-5.0, 5.0, -5.0*(GLfloat)h/(GLfloat)w,
5.0*(GLfloat)h/(GLfloat)w, -5.0, 5.0);
else
glOrtho(-5.0*(GLfloat)w/(GLfloat)h,
5.0*(GLfloat)w/(GLfloat)h, -5.0, 5.0, -5.0, 5.0);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
}
int main(int argc, char** argv)
{
auxInitDisplayMode (AUX_SINGLE | AUX_RGBA);
auxInitPosition (0, 0, 500, 500);
auxInitWindow (argv[0]);
myinit();
auxReshapeFunc (myReshape);
auxMainLoop(display);
}
//------------------------------------------------------------------------------------
一条立方贝泽尔曲线由四个控制点描述,这些控制点出现在本例的ctrlpoints[ ][ ]数组中。这个数组是传给glMap1f()的参数之一。该命令的所有参数如下:
GL_MAP1_VERTEX_3 :产生三维顶点
0 :参数u的最小值
1 :参数u的最大值
3 :每前进一个控制点应在生成数据中前进的浮点值个数
4 :spline的次数+1;在本例中度数是3(因为是立方曲线)
&ctrlpoints[0][0] :指向第一个控制点数据的指针
注意,第二、三个参数控制了曲线的参数化--即变量u从0变到1时曲线从一端走到另一端。对glEnalbe()的调用为产生二维顶点而打开(enable)一维定值器。
曲线是在例程display()中的glBegin()和glEnd()调用之间绘制的。由于定值器是打开的,命令glEvalCoord1f()就象发出了一条带有坐标参数的glVertex()命令一样,这些坐标就是曲线上对应于输入参数u的顶点的坐标。
定义并评估一个一维定值器
n度(或n+1次)伯恩斯坦(Bernstein)多项式由由下式给定:
如果Pi描述一套控制点(1、2、3维甚至4维),那么,公式
在u从0变到1时就描述了一条贝泽尔曲线。要描述相同的曲线而允许u在u1到u2间变化,定值:
命令glMap1()定义一个使用这些公式的一维定值器。
Void glMap1{fd}(Glenum target, TYPEu1, TYPEu2, Glint stride, Glint order, const TYPE *points);
定义一个一维定值器。Target参数指明控制点代表什么,清单见Table 11-1,以及在points中需要提供多少个值。这些点可以代表顶点、RGBA颜色数据、法向量或纹理坐标。例如,用GL_MAP1_COLOR_4,定值器生成属于某一条曲线的四维色彩空间中的颜色数据。在引用定值器之前,你也用Table 11-1列出的参数值打开每个定义好的定值器。传递适当的值给glEnable()或glDisable()以打开或禁用定值器。
glMap1*()的第二个参数,u1和u2,指出了变量u的范围。变量stride是每个存储器块中单精度或双精度值(根据需要)的个数。即,它是一个控制点开始到下一个控制点开始间的偏移值。
orders是度数加一,它应当与控制点数一致。pionts参数指向第一个控制点的第一个坐标。例如将数据结构传给glMap1*()时,对pionts使用如下的值:
(GLfloat)(&ctlpoints[0].x)
Table 11-1 : Types of Control Points for Use with glMap1*()
Paramete | Meaning |
GL_MAP1_VERTEX_3 | x, y, z vertex coordinates |
GL_MAP1_VERTEX_4 | x, y, z, w vertex coordinates |
GL_MAP1_INDEX | color index |
GL_MAP1_COLOR_4 | R, G, B, A |
GL_MAP1_NORMAL | normal coordinates |
GL_MAP1_TEXTURE_COORD_1 | s texture coordinates |
GL_MAP1_TEXTURE_COORD_2 | s, t texture coordinates |
GL_MAP1_TEXTURE_COORD_3 | s, t, r texture coordinates |
GL_MAP1_TEXTURE_COORD_4 | s, t, r, q texture coordinates |
一次可以有多个定值器被定值。例如,如果你同时定义并打开GL_MAP1_VERTEX_3和GL_MAP1_COLOR_4两个定值器,然后调用glEvalCoord1()同时生成一个位置和一个颜色。同一时间,只可有一个顶点定值器打开,尽管你或许已经定义两个。类似的,只能有一个纹理定值器可能是活动的。然而不同的是,定值器可以用于生成(顶点、法向、颜色和纹理坐标)数据的任意组合。如果同一类型的多个定值器被定义并打开,只有维数最高的那个被使用。
使用glEvalCoord1*()以定值一个已经定义并打开的一维图(map)。
void glEvalCoord1{fd}{v}(TYPE u);
引起对打开的一维图进行定值。参数u是坐标值域的值(或指针,用于本命令的向量版)。
定义一维坐标的等分空间值
你可以用任意的u值调用glEvalCoord1(),但通常使用等分空间值(evenly spaced value),正如前边例子Example 11-1所示。为获取等分空间值,用glMapGrid1*()定义一个一维格子,用glEvalMesh1()应用它。
void glMapGrid1{fd}(GLint n, TYPE u1, TYPE u2);定义一个分n步从u1进到u2的格子,这个格子是等分空间的。
void glEvalMesh1(GLenum mode, GLint p1, GLint p2);
将当前已经定义的图的格子应用于所有打开的定值器。mode可以是GL_POINT或GL_LINE,看你是想画曲线上的点还是相连的线了。这个调用与在[p1,p2]之间每一步发一条glEvalCoord1()作用完全相同,其中0<=p1,p2<=n。从程序的角度年,它等价于:
glBegin(GL_POINTS); /* OR glBegin(GL_LINE_STRIP); */
for (i = p1; i <= p2; i++)
glEvalCoord1(u1 + i*(u2-u1)/n);
glEnd();
例外:如果i=0或i=n,那么glEvalCoord()将以u1或u2作为参数被调用。
两维定值器
二维的每件事与一维时的情况相似,除了所有的命令都必须用两个参数,u和v。点.颜色.法向量或纹理坐标都必须作用于一个曲面而非曲线。数学点,贝泽尔曲面下这个式子指定:
其中,Pij是一套m*n的控制点,Bi是与一维时相同的伯恩斯坦多项式。正如前面所述,Pij可惟代表顶点、法向量、颜色或纹理坐标。
使用二维定值器的过程与一维相似:
1.定义定值器,用glMap2*()
2.打开定值器,传适当的值给glEnalbe()
3.在glBegin/glEnd()对之间调用glEvalCoord2()来引用这些定值器。
定义并定值一个二维定值器
用glMap2*()和glEvalCoord2*()以定义然后引用一个两维定值器。
void glMap2{fd}(GLenum target, TYPE u1, TYPE u2, GLint ustride, GLint uorder,
TYPE v1, TYPE v2, GLint vstride, GLint vorder, TYPE pionts);
target参数可以取Table 11-1中的任何一个值,注意用MAP2代替MAP1。如前所述,这些值也与glEnabled()一起用以打开相应的定值器。u和v的最小和最多值由u1,u2,v1,v2提供。参数ustride和vstride说明了一套单(双)精度值(看情况)的个数,这套值允许用户从较大的控制点数组中选择一个子块。例如,如果在表单(form)中出现了如下数据:
GLfloat ctlpoints[100][100][3];
并且你想用人tlpoints[20[30]开始的一个4X4子集,选择ustride为100*3,且vstride为3。启点,points,应该设为&ctlpoints[20[30][0],最后,次元参数uorder和vorder可以不同,允许在一个方向上为三次而另一个方向上为二次的曲面片,例如:
void glEvalCoord2{fd}{v}(TYPE u, TYPE v);
引起对打开的二维图的定值。参数u和v是坐标值域的值(或指向值的指针,用于命令的矢量版)。如果顶点定值器中的一个被打开了(GL_MAP2_VERTEX_3或GL_MAP2_VERTEX_4),那么曲面的法向将被分析计算。这个法向量是与生成的顶点相关联的,如果自动法向量生成已经打开的话(通过以GL_AUTO_NORMAL调用glEnalbe())。如果它被关闭了,相应的打开的法向量图将被用于产生一个法向量。如果没有这样的图存在,那么使用当前法向量。
二维例子:一个贝泽尔曲面
Example 11-2用定值器绘制一个线框的贝泽尔曲面,如图Figure 11-2所示。在样例中,曲面是在每个方向上用9条曲线绘成的。每条曲线画成30段。要得到完整的程序请将例子Example 11-1中的myReshape()和main()例子加进来。
Figure 11-2:一个贝泽尔曲面
Example 11-2:画一个贝泽尔曲面
#include <GL/gl.h>
#include <GL/glu.h>
#include "aux.h"
GLfloat ctrlpoints[4][4][3] = {
{{-1.5, -1.5, 4.0}, {-0.5, -1.5, 2.0},
{0.5, -1.5, -1.0}, {1.5, -1.5, 2.0}},
{{-1.5, -0.5, 1.0}, {-0.5, -0.5, 3.0},
{0.5, -0.5, 0.0}, {1.5, -0.5, -1.0}},
{{-1.5, 0.5, 4.0}, {-0.5, 0.5, 0.0},
{0.5, 0.5, 3.0}, {1.5, 0.5, 4.0}},
{{-1.5, 1.5, -2.0}, {-0.5, 1.5, -2.0},
{0.5, 1.5, 0.0}, {1.5, 1.5, -1.0}}
};
void display(void)
{
int i, j;
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glColor3f(1.0, 1.0, 1.0);
glPushMatrix ();
glRotatef(85.0, 1.0, 1.0, 1.0);
for (j = 0; j <= 8; j++) {
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord2f((GLfloat)i/30.0, (GLfloat)j/8.0);
glEnd();
glBegin(GL_LINE_STRIP);
for (i = 0; i <= 30; i++)
glEvalCoord2f((GLfloat)j/8.0, (GLfloat)i/30.0);
glEnd();
}
glPopMatrix ();
glFlush();
}
void myinit(void)
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
0, 1, 12, 4, &ctrlpoints[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_DEPTH_TEST);
glShadeModel(GL_FLAT);
}
定义二维空间等分坐标
在二维中,glMapGrid2*()和glEvalMesh2()命令与一维版是相似的,除了必须同时包含u和v的信息。
void glMapGrid2{fd}(GLint nu, TYPE u1, TYPE u2, GLint nv, TYPE v1, TYPE v2);
void glEvalMesh2(GLenum mode, GLint p1, GLint p2, GLint q1, GLint q2);
定义一个二维图网格,按nu个空间等分步从u1进到u2、分nv步从v1到v2(glMapGrid2*()),然后将这个网格作用于所有打开的定值器(glEvalMesh2())。这两个命令与一维时唯一明显不同的是在glEvalMesh2()中,mode参数可以是GL_POINT、GL_LlINE之外的GL_FILL,它产生用quad-mesh图元填充的多边形。确切的说,glEvalMesh2()与下面的代码片断基本等价。(基本等价,因为当i等于从nu或j到nv,那么参数确切地等于u2或v2,而不等于u1+nu*(u2-u1)/nu,这有点可能引起舍入错误)。
glBegin(GL_POINTS); /* mode == GL_POINT */
for (i = nu1; i <= nu2; i++)
for (j = nv1; j <= nv2; j++)
glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
or
for (i = nu1; i <= nu2; i++) { /* mode == GL_LINE */
glBegin(GL_LINES);
for (j = nv1; j <= nv2; j++)
glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
for (j = nv1; j <= nv2; j++) {
glBegin(GL_LINES);
for (i = nu1; i <= nu2; i++)
glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
or
for (i = nu1; i < nu2; i++) { /* mode == GL_FILL */
glBegin(GL_QUAD_STRIP);
for (j = nv1; j <= nv2; j++) {
glEvalCoord2(u1 + i*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEvalCoord2(u1 + (i+1)*(u2-u1)/nu, v1+j*(v2-v1)/nv);
glEnd();
}
例子Example 11-3展示了绘制一个贝泽尔曲面时与Example 11-2的必需的不同之处,仅仅是使用glMapGrid2()和glEvalMesh2()分割值域平面为一个统一的8x8网格。这个程序也加入了灯光和阴影,如Figuer 11-3所示。
Example 11-3 : Drawing a Lit, Shaded B閦ier Surface Using a Mesh: bezmesh.c
void initlights(void)
{
GLfloat ambient[] = { 0.2, 0.2, 0.2, 1.0 };
GLfloat position[] = { 0.0, 0.0, 2.0, 1.0 };
GLfloat mat_diffuse[] = { 0.6, 0.6, 0.6, 1.0 };
GLfloat mat_specular[] = { 1.0, 1.0, 1.0, 1.0 };
GLfloat mat_shininess[] = { 50.0 };
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
glLightfv(GL_LIGHT0, GL_POSITION, position);
glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, mat_diffuse);
glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, mat_specular);
glMaterialfv(GL_FRONT_AND_BACK,GL_SHININESS, mat_shininess);
}
void display(void)
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glPushMatrix();
glRotatef(85.0, 1.0, 1.0, 1.0);
glEvalMesh2(GL_FILL, 0, 8, 0, 8);
glPopMatrix();
glFlush();
}
void myinit(void)
{
glClearColor (0.0, 0.0, 0.0, 1.0);
glEnable(GL_DEPTH_TEST);
glMap2f(GL_MAP2_VERTEX_3, 0, 1, 3, 4,
0, 1, 12, 4, &ctrlpoints[0][0][0]);
glEnable(GL_MAP2_VERTEX_3);
glEnable(GL_AUTO_NORMAL);
glMapGrid2f(8, 0.0, 1.0, 8, 0.0, 1.0);
initlights();