多段Bézier曲线变换效果

头像
523066680
Administrator
Administrator
帖子: 333
注册时间: 2016年07月19日 12:14
拥有现金: 锁定
储蓄: 锁定
Has thanked: 29 times
Been thanked: 22 times
联系:

多段Bézier曲线变换效果

帖子 #1 523066680 » 2016年09月27日 12:18

Code: [全选] [展开/收缩] [Download] (MultiBezier.cpp)
  1. /*
  2.     MultiBezier.C
  3.     Code by 523066680, 2016-09-27
  4.     523066680@163.com
  5. */
  6.  
  7. #include <GL/glut.h>
  8. #include <stdio.h>
  9. #include <time.h>
  10. #include <unistd.h>
  11. #include <math.h>
  12.  
  13. //窗口尺寸
  14. #define SIZE_X 500
  15. #define SIZE_Y 500
  16.  
  17. const double PI  = 3.14159265;
  18. const double PI2 = 3.14159265 * 2;
  19.  
  20. #define LINES    20
  21. #define PARTS    50
  22. #define CP_PARTS 50
  23. #define CP_STEP  1.0/CP_PARTS
  24.  
  25. // LINES 积累的线的数量,而非细分数量
  26. // PARTS 单条曲线细分的数量
  27. // CP_PARTS 控制点移动轨迹的细分量
  28.  
  29. typedef struct
  30. {
  31.     float x;
  32.     float y;
  33. }
  34. point;
  35.  
  36. typedef struct
  37. {
  38.     point cp[4];
  39.     float index;
  40. }
  41. ControlPoint;
  42.  
  43. point MultiLine[LINES][PARTS];
  44.  
  45. int winID;
  46.  
  47. // PointOnCubicBeizer Function, copy from:
  48. // https://zh.wikipedia.org/wiki/貝茲曲線
  49. point PointOnCubicBeizer(point cp[4], float t)
  50. {
  51.     float cx, bx, ax, cy, by, ay;
  52.     float tSquared, tCubed;
  53.     point result;
  54.  
  55.     cx = 3.0 * (cp[1].x - cp[0].x);
  56.     bx = 3.0 * (cp[2].x - cp[1].x) - cx;
  57.     ax = cp[3].x - cp[0].x - cx - bx;
  58.  
  59.     cy = 3.0 * (cp[1].y - cp[0].y);
  60.     by = 3.0 * (cp[2].y - cp[1].y) - cy;
  61.     ay = cp[3].y - cp[0].y - cy - by;
  62.  
  63.     tSquared = t * t;
  64.     tCubed = tSquared * t;
  65.  
  66.     result.x = ((ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0].x);
  67.     result.y = ((ay * tCubed) + (by * tSquared) + (cy * t) + cp[0].y);
  68.     return result;
  69. }
  70.  
  71. void display(void)
  72. {  
  73.     glBegin(GL_POINTS);
  74.     glColor4f(0.3, 0.5, 0.8, 0.4);
  75.  
  76.     for (int i = 0; i < LINES; i++)
  77.     {
  78.         for (int j = 0; j < PARTS; j++)
  79.         {
  80.             glVertex3f(MultiLine[i][j].x, MultiLine[i][j].y, 0.0);
  81.         }
  82.     }
  83.     glEnd();
  84.  
  85.     glutSwapBuffers();
  86. }
  87.  
  88. //闲时回调函数,主要负责计算、延时
  89. void idle(void)
  90. {
  91.     //临时坐标变量
  92.     point coord;
  93.    
  94.     //主控制点,通过PointOnCubicBeizer函数和cp[4]数据获取坐标
  95.     point MainCp[4];
  96.  
  97.     static int MainIdx = 0;
  98.  
  99.     //ControlPoint 结构包含 控制点、曲线进度信息
  100.     static ControlPoint cp[4] =
  101.     {
  102.         0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
  103.         0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
  104.         0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
  105.         0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
  106.     };
  107.  
  108.     glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);
  109.  
  110.     glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  111.     glPushMatrix();
  112.  
  113.     //4个控制点的变化和进度
  114.     for (int i = 0; i < 4; i++)
  115.     {
  116.         cp[i].index += CP_STEP;
  117.  
  118.         //index超过1.0时重新设定控制点
  119.         if (cp[i].index >= 1.0)
  120.         {
  121.             //上一个终点作为新的起点
  122.             cp[i].cp[0].x = cp[i].cp[3].x;  
  123.             cp[i].cp[0].y = cp[i].cp[3].y;
  124.  
  125.             //使第一控制点与上次曲线对称(减少尖突的情况)
  126.             cp[i].cp[1].x = cp[i].cp[3].x + (cp[i].cp[3].x - cp[i].cp[2].x);  
  127.             cp[i].cp[1].y = cp[i].cp[3].y + (cp[i].cp[3].y - cp[i].cp[2].y);
  128.  
  129.             //后面两个点随机
  130.             for (int ii = 2; ii < 4; ii++)
  131.             {
  132.                 cp[i].cp[ii].x = (float) ( rand() % 500 - 250);
  133.                 cp[i].cp[ii].y = (float) ( rand() % 500 - 250);
  134.             }
  135.  
  136.             //从1单位的CP_STEP开始,而非0.0,避免重合显示
  137.             cp[i].index = CP_STEP;
  138.         }
  139.  
  140.         coord = PointOnCubicBeizer( cp[i].cp, cp[i].index );
  141.  
  142.         MainCp[i].x = coord.x;
  143.         MainCp[i].y = coord.y;
  144.     }
  145.    
  146.     //当前曲线坐标写入数组
  147.     for (int i = 0; i < PARTS; i++)
  148.     {
  149.         coord = PointOnCubicBeizer(MainCp , (float)i / (float)PARTS );
  150.         MultiLine[MainIdx][i].x = coord.x;
  151.         MultiLine[MainIdx][i].y = coord.y;
  152.     }
  153.  
  154.     //线条序号,达到数组末端时回到 0
  155.     if ( MainIdx >= (LINES - 1) )
  156.     {
  157.         MainIdx = 0;
  158.     }
  159.     else
  160.     {
  161.         MainIdx++;
  162.     }
  163.  
  164.     usleep(30000);
  165.     glutPostRedisplay();
  166. }
  167.  
  168. //窗口事件响应函数
  169. void reshape(int Width,int Height)
  170. {
  171.     const float fa = 32.0;
  172.     const float half = 250.0;
  173.  
  174.     if (Width == Height)
  175.     {
  176.         //视口范围
  177.         glViewport(0, 0, Width, Height);
  178.     }
  179.     else if ( Width > Height )
  180.     {
  181.         glViewport(0, 0, Height, Height);
  182.     }
  183.     else
  184.     {
  185.         glViewport(0, 0, Width, Width);
  186.     }
  187.  
  188.     //投影视图矩阵
  189.     glMatrixMode(GL_PROJECTION);              
  190.     glLoadIdentity();
  191.     glOrtho(-half, half, -half, half, 0.0, 100.0);
  192.  
  193.     // 模型视图矩阵
  194.     glMatrixMode(GL_MODELVIEW);
  195.     glLoadIdentity();
  196.     gluLookAt(0.0,0.0,fa, 0.0,0.0,0.0, 0.0,1.0,fa);
  197.             // 观察点,   朝向的坐标, 正上方朝向
  198. }
  199.  
  200. //按键响应
  201. void keypress(unsigned char key, int mousex, int mousey)
  202. {
  203.     switch (key)
  204.     {
  205.         case 'q':
  206.             glutDestroyWindow(winID);
  207.             exit(0);
  208.             break;
  209.         case 'Q':
  210.             glutDestroyWindow(winID);
  211.             exit(0);
  212.             break;
  213.     }
  214. }
  215.  
  216. void init(void)
  217. {
  218.     glClearColor(0.0, 0.0, 0.0, 0.0);
  219.     glLineWidth( 2.0 );
  220.     glPointSize( 6.0 );
  221.     //glEnable(GL_DEPTH_TEST);    //开启深度缓冲
  222.     glEnable(GL_BLEND);           //开启颜色混合
  223.     glEnable(GL_POINT_SMOOTH);    //点平滑
  224.     glEnable(GL_LINE_SMOOTH);     //线平滑
  225.  
  226.     srand(time(NULL));
  227.  
  228.     for (int i = 0; i < 10; i++)
  229.     {
  230.         for (int j = 0; j < 10; j++)
  231.         {
  232.             MultiLine[i][j].x = 0.0;
  233.             MultiLine[i][j].y = 0.0;
  234.         }
  235.     }
  236.  
  237. }
  238.  
  239. void main(int argc, char *argv[])
  240. {
  241.     glutInit(&argc, argv);
  242.             //显示模式   双缓冲         RGBA        深度缓冲
  243.     glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
  244.     glutInitWindowSize(SIZE_X, SIZE_Y);       //窗口大小
  245.     glutInitWindowPosition(200, 200);         //位置
  246.     winID = glutCreateWindow("MultiBezier");  //窗口句柄
  247.     init();
  248.     glutDisplayFunc(display);          //显示
  249.     glutKeyboardFunc(keypress);        //按键事件响应
  250.     glutReshapeFunc(reshape);          //窗口事件响应
  251.     glutIdleFunc(idle);                //闲时回调函数
  252.     glutMainLoop();                    //开始主循环
  253. }

环境配置WIN7, MinGW+FreeGLUT, 参考:http://www.code-by.org/viewtopic.php?f=43&t=107

编译指令:
Code: [全选] [展开/收缩] [Download] (Untitled.bsh)
  1. gcc -std=c11 "%1" -o "%~n1" ^
  2.                    -ID:\OGL\freeglut-MinGW-3.0.0-1.mp\include ^
  3.                    -LD:\OGL\freeglut-MinGW-3.0.0-1.mp\lib\x64 ^
  4.                    -lfreeglut -lopengl32 -lglu32


图片

头像
523066680
Administrator
Administrator
帖子: 333
注册时间: 2016年07月19日 12:14
拥有现金: 锁定
储蓄: 锁定
Has thanked: 29 times
Been thanked: 22 times
联系:

设计思路

帖子 #2 523066680 » 2016年09月27日 13:15

首先从 http://zh.wikipedia.org/wiki/貝茲曲線 抄了一段函数

Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. /* 產生三次方貝茲曲線的程式碼 */
  2. typedef struct
  3. {
  4.     float x;
  5.     float y;
  6. }
  7. Point2D;
  8.  
  9. /*
  10.  cp在此是四個元素的陣列:
  11.  cp[0]為起始點,或上圖中的P0
  12.  cp[1]為第一個控制點,或上圖中的P1
  13.  cp[2]為第二個控制點,或上圖中的P2
  14.  cp[3]為結束點,或上圖中的P3
  15.  t為參數值,0 <= t <= 1
  16. */
  17.  
  18. Point2D PointOnCubicBezier( Point2D* cp, float t )
  19. {
  20.     float   ax, bx, cx;
  21.     float   ay, by, cy;
  22.     float   tSquared, tCubed;
  23.     Point2D result;
  24.  
  25.     /*計算多項式係數*/
  26.  
  27.     cx = 3.0 * (cp[1].x - cp[0].x);
  28.     bx = 3.0 * (cp[2].x - cp[1].x) - cx;
  29.     ax = cp[3].x - cp[0].x - cx - bx;
  30.  
  31.     cy = 3.0 * (cp[1].y - cp[0].y);
  32.     by = 3.0 * (cp[2].y - cp[1].y) - cy;
  33.     ay = cp[3].y - cp[0].y - cy - by;
  34.  
  35.     /*計算位於參數值t的曲線點*/
  36.  
  37.     tSquared = t * t;
  38.     tCubed = tSquared * t;
  39.  
  40.     result.x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0].x;
  41.     result.y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp[0].y;
  42.  
  43.     return result;
  44. }
  45.  
  46. /*
  47.  ComputeBezier以控制點cp所產生的曲線點,填入Point2D結構的陣列。
  48.  呼叫者必須分配足夠的記憶體以供輸出結果,其為<sizeof(Point2D) numberOfPoints>
  49. */
  50.  
  51. void ComputeBezier( Point2D* cp, int numberOfPoints, Point2D* curve )
  52. {
  53.     float   dt;
  54.     int    i;
  55.  
  56.     dt = 1.0 / ( numberOfPoints - 1 );
  57.  
  58.     for( i = 0; i < numberOfPoints; i++)
  59.         curve[i] = PointOnCubicBezier( cp, i*dt );
  60. }

我们往函数传入曲线的四个控制点,以及t值,t的值为0-1之间的一个分数,
其实t传递的是位置和“进度”,分母表示这个曲线分成几部分,分子表示要获取的是第几个点的坐标

有了这个函数,只要这样就可以画一组Bezier曲线上的点了
Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. Point2D dot[4] = {-200.0,0.0, -50.0,50.0,  50.0,-50.0,  200.0,0.0 };
  2. Point2D coord;
  3. for (n = 0.0; n < 1.0; n += 0.1)
  4. {
  5.     coord = PointOnCubicBeizer( dot, n);
  6.     DrawPoint(coord.x, coord.y, 0.0);
  7. }

另外,有一个递归版的例子 http://zh.wikipedia.org/wiki/德卡斯特里奥算法

先从绘制Bezier曲线开始,让控制点随机变化以得到不断变化的曲线,但这通常不是平滑过渡的。控制线的两端,
让其中两个控制点平滑移动,橙点和蓝点。现在它们是直线移动的,起点坐标是(src.x, src.y) 终点坐标从随机数获取
(rand(100.0), rand(100.0)),用 sqr(a^2+b^2) = c 的公式取得长度,delta.x/长度,delta.y/长度 分别为x,y每次的增量,
使得无论斜率是多少,它们看上去都是匀速移动的。
图片


进一步调整:4个点都是随机定义并且平滑移动的,将Bezier的线绘改为点绘。现在,这个地方需要花点心思:
用一个50*50*3的三维数组去存储50条贝塞尔曲线的点坐标,每条曲线又分为50段(51个点?细节部分暂时忽略)
3表示x,y,z 比如Array[0..49]表示1-50条。累积到第"51"条时,下标又从0开始进行存储,将原来第一条曲线抛
弃。这样数组就得到循环使用,也得到了不断变化的Bezier曲线。因为点的密集程度并不均匀,使它看上去像是
三维的。
图片

还是有些不顺眼的地方,线是曲的,控制点移动轨迹却是直线的,每一个边缘变化的地方都有棱角。OK,
这四个Control Point,要让它沿贝塞尔曲线的路径移动,程序一开始就应该另外建立4组随机控制点(4*4*2),
存储4个潜在的Bezier路径信息,让Control Point沿着这4条路径移动,抵达目标点时进入随机计算下一组控制点坐标,
如此循环。
  1. //ControlPoint 结构包含 控制点、进度信息
  2.     static ControlPoint cp[4] =
  3.     {
  4.         0.0, 0.0, 0.0, 0.0, 1.0,
  5.         0.0, 0.0, 0.0, 0.0, 1.0,
  6.         0.0, 0.0, 0.0, 0.0, 1.0,
  7.         0.0, 0.0, 0.0, 0.0, 1.0,
  8.     };

根据这些后台的Control Point,找出主线的4个主线控制点坐标。
Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. //主控制点,通过PointOnCubicBeizer函数和cp[4]数据获取坐标
  2.     point MainCp[4];
  3.     //省略部分代码
  4.     //4个控制点的变化和进度
  5.     for (int i = 0; i < 4; i++)
  6.     {
  7.         //省略部分代码
  8.         coord = PointOnCubicBeizer( cp[i].cp, cp[i].index );
  9.  
  10.         MainCp[i].x = coord.x;
  11.         MainCp[i].y = coord.y;
  12.     }


当这些控制点所在的Bezier曲线t值满1,寻找新的曲线的时候,新的起点必须是上一次的终点,第二个控制点
必须和之前的第三个控制点成对称点(至少应该是三点成一条直线,而对称则更平滑)(还是画个图吧)

区别:
psb.png
psb.png (3.69 KiB) 查看 1264 次
psb.png
psb.png (3.69 KiB) 查看 1264 次

psb2.png
psb2.png (4.88 KiB) 查看 1264 次
psb2.png
psb2.png (4.88 KiB) 查看 1264 次

ccc
初来炸道
初来炸道
帖子: 7
注册时间: 2016年09月18日 21:50
拥有现金: 锁定
Been thanked: 5 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #3 ccc » 2016年09月27日 16:05

做屏保很不错,3D 建模中的建模和数据转化算法不知道能不能用到
FreeBSD :ugeek:

happy886rr
渐入佳境
渐入佳境
帖子: 45
注册时间: 2016年09月27日 16:11
拥有现金: 锁定
储蓄: 锁定
Has thanked: 14 times
Been thanked: 14 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #4 happy886rr » 2016年09月27日 16:19

线条非常平滑,我都被震撼到了。第一次来此,发现都是专业性的文章。论坛UI体验非常好,色调简洁,明快。 干脆叫opengl之家吧,O(∩_∩)O~

头像
523066680
Administrator
Administrator
帖子: 333
注册时间: 2016年07月19日 12:14
拥有现金: 锁定
储蓄: 锁定
Has thanked: 29 times
Been thanked: 22 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #5 523066680 » 2016年09月27日 16:28

happy886rr 写了:线条非常平滑,我都被震撼到了。第一次来此,发现都是专业性的文章。论坛UI体验非常好,色调简洁,明快。 干脆叫opengl之家吧,O(∩_∩)O~

针对GL的论坛之前开过,open-gl.org,但感觉比较失败

头像
523066680
Administrator
Administrator
帖子: 333
注册时间: 2016年07月19日 12:14
拥有现金: 锁定
储蓄: 锁定
Has thanked: 29 times
Been thanked: 22 times
联系:

幻彩效果

帖子 #6 523066680 » 2016年09月27日 16:41

要使颜色变得丰富,可以在绘点之前添加颜色设置。会多一些运算消耗。
glColor4f 的4个参数分别为: R,G,B, ALPHA,取值范围从0.0-1.0

Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. glBegin(GL_POINTS);
  2. for (int i = 0; i < LINES; i++)
  3. {
  4.     for (int j = 0; j < PARTS; j++)
  5.     {
  6.         glColor4f( fabs((float)i - LINES/2)/(float)LINES , (float)j/(float)PARTS, 0.8, 0.4);
  7.         glVertex3f(MultiLine[i][j].x, MultiLine[i][j].y, 0.0);
  8.     }
  9. }
  10. glEnd();

效果
图片

happy886rr
渐入佳境
渐入佳境
帖子: 45
注册时间: 2016年09月27日 16:11
拥有现金: 锁定
储蓄: 锁定
Has thanked: 14 times
Been thanked: 14 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #7 happy886rr » 2016年09月27日 17:10

不错,色彩过渡相当柔和! :applaud1

头像
523066680
Administrator
Administrator
帖子: 333
注册时间: 2016年07月19日 12:14
拥有现金: 锁定
储蓄: 锁定
Has thanked: 29 times
Been thanked: 22 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #8 523066680 » 2016年09月27日 22:02

修正一处错误,原代码此处的公式并没有获取真正的对称点:
Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. //使第一控制点与上次曲线对称(减少尖突的情况)
  2.             cp[i].cp[1].x = cp[i].cp[3].x - cp[i].cp[2].x;  
  3.             cp[i].cp[1].y = cp[i].cp[3].y - cp[i].cp[2].x;

应改为:
Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. //使第一控制点与上次曲线对称(减少尖突的情况)
  2.             cp[i].cp[1].x = cp[i].cp[3].x + (cp[i].cp[3].x - cp[i].cp[2].x);  
  3.             cp[i].cp[1].y = cp[i].cp[3].y + (cp[i].cp[3].y - cp[i].cp[2].y);


图片

happy886rr
渐入佳境
渐入佳境
帖子: 45
注册时间: 2016年09月27日 16:11
拥有现金: 锁定
储蓄: 锁定
Has thanked: 14 times
Been thanked: 14 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #9 happy886rr » 2016年09月28日 08:17

523066680 写了:修正一处错误,原代码此处的公式并没有获取真正的对称点:
Code: [全选] [展开/收缩] [Download] (Untitled.cpp)
  1. //使第一控制点与上次曲线对称(减少尖突的情况)
  2.             cp[i].cp[1].y = cp[i].cp[3].y + (cp[i].cp[3].y - cp[i].cp[2].y);


right ,y=(cp[i].cp[1].y+cp[i].cp[2].y)/2,定比分点。

24game
渐入佳境
渐入佳境
帖子: 42
注册时间: 2016年09月02日 22:09
拥有现金: 锁定
Has thanked: 3 times
Been thanked: 15 times
联系:

Re: 多段Bézier曲线变换效果

帖子 #10 24game » 2016年10月02日 12:53

采用对称点平滑过渡主控点路径时, 路径曲线的控制点坐标 定在 与 视窗 同心 宽高均为视窗宽高的 2/3 的矩形内时, 可保证绘图曲线永远不会超出 视窗 坐标范围

此结论可证明 如 二次曲线 当 P0=P2={0,0}[此设定为方便证明] 时, 当 t=0.5 时, 轨迹点离原点最远, 达 P1/2, 故相对于 控制点坐标范围, 绘图点的坐标范围设为

控制点坐标范围 * (1 + 1/2) = 3/2, 倒过来, 控制点坐标 定在 与 视窗 同心 宽高均为视窗宽高的 2/3 的矩形内

即使 主控点路径 采用 3 次 或更高次曲线, 也一样适用


回到 “OpenGL”

在线用户

用户浏览此论坛: 没有注册用户 和 0 访客