多段Bézier曲线变换效果

TheBooksofShaders ShaderToy
OpenGL Tutorial
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

多段Bézier曲线变换效果

帖子 523066680 »

/*
MultiBezier.C
Code by 523066680, 2016-09-27
523066680@163.com
*/

#include <GL/glut.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <math.h>

//窗口尺寸
#define SIZE_X 500
#define SIZE_Y 500

const double PI = 3.14159265;
const double PI2 = 3.14159265 * 2;

#define LINES 20
#define PARTS 50
#define CP_PARTS 50
#define CP_STEP 1.0/CP_PARTS

// LINES 积累的线的数量,而非细分数量
// PARTS 单条曲线细分的数量
// CP_PARTS 控制点移动轨迹的细分量

typedef struct
{
float x;
float y;
}
point;

typedef struct
{
point cp[4];
float index;
}
ControlPoint;

point MultiLine[LINES][PARTS];

int winID;

// PointOnCubicBeizer Function, copy from:
// https://zh.wikipedia.org/wiki/貝茲曲線
point PointOnCubicBeizer(point cp[4], float t)
{
float cx, bx, ax, cy, by, ay;
float tSquared, tCubed;
point result;

cx = 3.0 * (cp[1].x - cp[0].x);
bx = 3.0 * (cp[2].x - cp[1].x) - cx;
ax = cp[3].x - cp[0].x - cx - bx;

cy = 3.0 * (cp[1].y - cp[0].y);
by = 3.0 * (cp[2].y - cp[1].y) - cy;
ay = cp[3].y - cp[0].y - cy - by;

tSquared = t * t;
tCubed = tSquared * t;

result.x = ((ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0].x);
result.y = ((ay * tCubed) + (by * tSquared) + (cy * t) + cp[0].y);
return result;
}

void display(void)
{
glBegin(GL_POINTS);
glColor4f(0.3, 0.5, 0.8, 0.4);

for (int i = 0; i < LINES; i++)
{
for (int j = 0; j < PARTS; j++)
{
glVertex3f(MultiLine[i][j].x, MultiLine[i][j].y, 0.0);
}
}
glEnd();

glutSwapBuffers();
}

//闲时回调函数,主要负责计算、延时
void idle(void)
{
//临时坐标变量
point coord;

//主控制点,通过PointOnCubicBeizer函数和cp[4]数据获取坐标
point MainCp[4];

static int MainIdx = 0;

//ControlPoint 结构包含 控制点、曲线进度信息
static ControlPoint cp[4] =
{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0,
};

glBlendFunc(GL_SRC_ALPHA, GL_DST_ALPHA);

glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
glPushMatrix();

//4个控制点的变化和进度
for (int i = 0; i < 4; i++)
{
cp[i].index += CP_STEP;

//index超过1.0时重新设定控制点
if (cp[i].index >= 1.0)
{
//上一个终点作为新的起点
cp[i].cp[0].x = cp[i].cp[3].x;
cp[i].cp[0].y = cp[i].cp[3].y;

//使第一控制点与上次曲线对称(减少尖突的情况)
cp[i].cp[1].x = cp[i].cp[3].x + (cp[i].cp[3].x - cp[i].cp[2].x);
cp[i].cp[1].y = cp[i].cp[3].y + (cp[i].cp[3].y - cp[i].cp[2].y);

//后面两个点随机
for (int ii = 2; ii < 4; ii++)
{
cp[i].cp[ii].x = (float) ( rand() % 500 - 250);
cp[i].cp[ii].y = (float) ( rand() % 500 - 250);
}

//从1单位的CP_STEP开始,而非0.0,避免重合显示
cp[i].index = CP_STEP;
}

coord = PointOnCubicBeizer( cp[i].cp, cp[i].index );

MainCp[i].x = coord.x;
MainCp[i].y = coord.y;
}

//当前曲线坐标写入数组
for (int i = 0; i < PARTS; i++)
{
coord = PointOnCubicBeizer(MainCp , (float)i / (float)PARTS );
MultiLine[MainIdx][i].x = coord.x;
MultiLine[MainIdx][i].y = coord.y;
}

//线条序号,达到数组末端时回到 0
if ( MainIdx >= (LINES - 1) )
{
MainIdx = 0;
}
else
{
MainIdx++;
}

usleep(30000);
glutPostRedisplay();
}

//窗口事件响应函数
void reshape(int Width,int Height)
{
const float fa = 32.0;
const float half = 250.0;

if (Width == Height)
{
//视口范围
glViewport(0, 0, Width, Height);
}
else if ( Width > Height )
{
glViewport(0, 0, Height, Height);
}
else
{
glViewport(0, 0, Width, Width);
}

//投影视图矩阵
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(-half, half, -half, half, 0.0, 100.0);

// 模型视图矩阵
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0,0.0,fa, 0.0,0.0,0.0, 0.0,1.0,fa);
// 观察点, 朝向的坐标, 正上方朝向
}

//按键响应
void keypress(unsigned char key, int mousex, int mousey)
{
switch (key)
{
case 'q':
glutDestroyWindow(winID);
exit(0);
break;
case 'Q':
glutDestroyWindow(winID);
exit(0);
break;
}
}

void init(void)
{
glClearColor(0.0, 0.0, 0.0, 0.0);
glLineWidth( 2.0 );
glPointSize( 6.0 );
//glEnable(GL_DEPTH_TEST); //开启深度缓冲
glEnable(GL_BLEND); //开启颜色混合
glEnable(GL_POINT_SMOOTH); //点平滑
glEnable(GL_LINE_SMOOTH); //线平滑

srand(time(NULL));

for (int i = 0; i < 10; i++)
{
for (int j = 0; j < 10; j++)
{
MultiLine[i][j].x = 0.0;
MultiLine[i][j].y = 0.0;
}
}

}

void main(int argc, char *argv[])
{
glutInit(&argc, argv);
//显示模式 双缓冲 RGBA 深度缓冲
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH );
glutInitWindowSize(SIZE_X, SIZE_Y); //窗口大小
glutInitWindowPosition(200, 200); //位置
winID = glutCreateWindow("MultiBezier"); //窗口句柄
init();
glutDisplayFunc(display); //显示
glutKeyboardFunc(keypress); //按键事件响应
glutReshapeFunc(reshape); //窗口事件响应
glutIdleFunc(idle); //闲时回调函数
glutMainLoop(); //开始主循环
}
环境配置WIN7, MinGW+FreeGLUT, 参考:http://www.code-by.org/viewtopic.php?f=43&t=107

编译指令:
gcc -std=c11 "%1" -o "%~n1" ^
-ID:\OGL\freeglut-MinGW-3.0.0-1.mp\include ^
-LD:\OGL\freeglut-MinGW-3.0.0-1.mp\lib\x64 ^
-lfreeglut -lopengl32 -lglu32
图片
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

设计思路

帖子 523066680 »

首先从 http://zh.wikipedia.org/wiki/貝茲曲線 抄了一段函数
/* 產生三次方貝茲曲線的程式碼 */
typedef struct
{
float x;
float y;
}
Point2D;

/*
cp在此是四個元素的陣列:
cp[0]為起始點,或上圖中的P0
cp[1]為第一個控制點,或上圖中的P1
cp[2]為第二個控制點,或上圖中的P2
cp[3]為結束點,或上圖中的P3
t為參數值,0 <= t <= 1
*/

Point2D PointOnCubicBezier( Point2D* cp, float t )
{
float ax, bx, cx;
float ay, by, cy;
float tSquared, tCubed;
Point2D result;

/*計算多項式係數*/

cx = 3.0 * (cp[1].x - cp[0].x);
bx = 3.0 * (cp[2].x - cp[1].x) - cx;
ax = cp[3].x - cp[0].x - cx - bx;

cy = 3.0 * (cp[1].y - cp[0].y);
by = 3.0 * (cp[2].y - cp[1].y) - cy;
ay = cp[3].y - cp[0].y - cy - by;

/*計算位於參數值t的曲線點*/

tSquared = t * t;
tCubed = tSquared * t;

result.x = (ax * tCubed) + (bx * tSquared) + (cx * t) + cp[0].x;
result.y = (ay * tCubed) + (by * tSquared) + (cy * t) + cp[0].y;

return result;
}

/*
ComputeBezier以控制點cp所產生的曲線點,填入Point2D結構的陣列。
呼叫者必須分配足夠的記憶體以供輸出結果,其為<sizeof(Point2D) numberOfPoints>
*/

void ComputeBezier( Point2D* cp, int numberOfPoints, Point2D* curve )
{
float dt;
int i;

dt = 1.0 / ( numberOfPoints - 1 );

for( i = 0; i < numberOfPoints; i++)
curve[i] = PointOnCubicBezier( cp, i*dt );
}
我们往函数传入曲线的四个控制点,以及t值,t的值为0-1之间的一个分数,
其实t传递的是位置和“进度”,分母表示这个曲线分成几部分,分子表示要获取的是第几个点的坐标

有了这个函数,只要这样就可以画一组Bezier曲线上的点了
Point2D dot[4] = {-200.0,0.0, -50.0,50.0,  50.0,-50.0,  200.0,0.0 };
Point2D coord;
for (n = 0.0; n < 1.0; n += 0.1)
{
coord = PointOnCubicBeizer( dot, n);
DrawPoint(coord.x, coord.y, 0.0);
}
另外,有一个递归版的例子 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条路径移动,抵达目标点时进入随机计算下一组控制点坐标,
如此循环。
//ControlPoint 结构包含 控制点、进度信息
static ControlPoint cp[4] =
{
0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 1.0,
0.0, 0.0, 0.0, 0.0, 1.0,
};
根据这些后台的Control Point,找出主线的4个主线控制点坐标。
//主控制点,通过PointOnCubicBeizer函数和cp[4]数据获取坐标
point MainCp[4];
//省略部分代码
//4个控制点的变化和进度
for (int i = 0; i < 4; i++)
{
//省略部分代码
coord = PointOnCubicBeizer( cp[i].cp, cp[i].index );

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

区别:
psb.png
(3.69 KiB) 已下载 2956 次
psb2.png
(4.88 KiB) 已下载 2956 次
ccc
初来炸道
初来炸道
帖子: 7
注册时间: 2016年09月18日 21:50
联系:

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

帖子 ccc »

做屏保很不错,3D 建模中的建模和数据转化算法不知道能不能用到
happy886rr
渐入佳境
渐入佳境
帖子: 45
注册时间: 2016年09月27日 16:11
联系:

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

帖子 happy886rr »

线条非常平滑,我都被震撼到了。第一次来此,发现都是专业性的文章。论坛UI体验非常好,色调简洁,明快。 干脆叫opengl之家吧,O(∩_∩)O~
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

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

帖子 523066680 »

happy886rr 写了:线条非常平滑,我都被震撼到了。第一次来此,发现都是专业性的文章。论坛UI体验非常好,色调简洁,明快。 干脆叫opengl之家吧,O(∩_∩)O~
针对GL的论坛之前开过,open-gl.org,但感觉比较失败
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

幻彩效果

帖子 523066680 »

要使颜色变得丰富,可以在绘点之前添加颜色设置。会多一些运算消耗。
glColor4f 的4个参数分别为: R,G,B, ALPHA,取值范围从0.0-1.0
glBegin(GL_POINTS);
for (int i = 0; i < LINES; i++)
{
for (int j = 0; j < PARTS; j++)
{
glColor4f( fabs((float)i - LINES/2)/(float)LINES , (float)j/(float)PARTS, 0.8, 0.4);
glVertex3f(MultiLine[i][j].x, MultiLine[i][j].y, 0.0);
}
}
glEnd();
效果
图片
happy886rr
渐入佳境
渐入佳境
帖子: 45
注册时间: 2016年09月27日 16:11
联系:

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

帖子 happy886rr »

不错,色彩过渡相当柔和! :applaud1
头像
523066680
Administrator
Administrator
帖子: 573
注册时间: 2016年07月19日 12:14
联系:

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

帖子 523066680 »

修正一处错误,原代码此处的公式并没有获取真正的对称点:
//使第一控制点与上次曲线对称(减少尖突的情况)
cp[i].cp[1].x = cp[i].cp[3].x - cp[i].cp[2].x;
cp[i].cp[1].y = cp[i].cp[3].y - cp[i].cp[2].x;
应改为:
//使第一控制点与上次曲线对称(减少尖突的情况)
cp[i].cp[1].x = cp[i].cp[3].x + (cp[i].cp[3].x - cp[i].cp[2].x);
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
联系:

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

帖子 happy886rr »

523066680 写了:修正一处错误,原代码此处的公式并没有获取真正的对称点:
//使第一控制点与上次曲线对称(减少尖突的情况)
cp[i].cp[1].y = cp[i].cp[3].y + (cp[i].cp[3].y - cp[i].cp[2].y);
right ,y=(cp.cp[1].y+cp.cp[2].y)/2,定比分点。
24game
渐入佳境
渐入佳境
帖子: 54
注册时间: 2016年09月02日 22:09
联系:

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

帖子 24game »

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

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

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

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

在线用户

正浏览此版面之用户: 没有注册用户 和 0 访客