一、一次贝塞尔曲线
一次贝塞尔曲线是最简单的贝塞尔曲线形式,它实际上就是两点之间的线性插值。一次贝塞尔曲线由两个点定义:起始点 P0 和结束点 P1。曲线上的任意一点 B(t) 可以通过以下公式计算:
B(t) = (1 - t) * P0 + t * P1
其中 t 是参数,取值范围是 [0, 1]。推导过程非常简单,它是基于线性插值的概念。线性插值是指在两个已知点之间找到一个新的点,这个新点的位置取决于参数 t 的值。当 t = 0 时,B(t) = P0,当 t = 1 时,B(t) = P1,当 t 在 0 和 1 之间时,B(t) 在 P0 和 P1 之间移动。这个过程可以用一个简单的例子来理解。假设你有一个从 A 点到 B 点的直线,你想要在这两点之间找到一点 C。如果你知道 C 点距离 A 点和 B 点的相对距离,你就可以通过线性插值来找到 C 点的位置。这个相对距离就是参数 t,当 t = 0.5 时,C 点正好在 A 点和 B 点的正中间。一次贝塞尔曲线的推导并不涉及复杂的数学运算,它是一个非常直观的过程,基于我们对于线性插值的直观理解。这种曲线在计算机图形学中非常有用,因为它提供了一种简单的方式来平滑地过渡两个点之间的形状或位置。
为了可视化贝塞尔曲线,我们可以使用C语言结合图形库,比如SDL(Simple DirectMedia Layer)。SDL是一个跨平台的开源图形渲染库,可以用来创建窗口、渲染图像和处理事件。首先,你需要安装SDL库。然后,我们可以编写一个简单的程序来绘制一次贝塞尔曲线。以下是一个使用SDL2库的示例程序:
#include <SDL.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
// 定义一个结构体来表示一个点
typedef struct {
float x;
float y;
} Point;
// 计算一次贝塞尔曲线上的点
Point calculateBezierPoint(Point p0, Point p1, float t) {
Point p;
p.x = (1 - t) * p0.x + t * p1.x;
p.y = (1 - t) * p0.y + t * p1.y;
return p;
}
int main(int argc, char* args[]) {
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Bezier Curve Visualization", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == NULL) {
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
return 1;
}
SDL_Event e;
bool quit = false;
while (!quit) {
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) {
quit = true;
}
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
// 定义两个控制点
Point p0 = {100.0, 500.0};
Point p1 = {700.0, 100.0};
// 绘制控制点
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderDrawPoint(renderer, (int)p0.x, (int)p0.y);
SDL_RenderDrawPoint(renderer, (int)p1.x, (int)p1.y);
// 绘制一次贝塞尔曲线
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
Point prev = p0;
for (float t = 0.01; t <= 1.0; t += 0.01) {
Point current = calculateBezierPoint(p0, p1, t);
SDL_RenderDrawLine(renderer, (int)prev.x, (int)prev.y, (int)current.x, (int)current.y);
prev = current;
}
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
在这个程序中,我们首先初始化SDL,创建一个窗口和渲染器。然后,我们进入一个事件循环,监听退出事件。在每次循环中,我们清除屏幕,绘制两个控制点,并使用calculateBezierPoint函数绘制一次贝塞尔曲线。calculateBezierPoint函数接收两个点p0和p1以及一个参数t,然后返回在曲线上对应于该参数t的点。main函数定义了两个控制点,并使用一个循环来计算并打印曲线上的一系列点。编译并运行这个程序,你将得到一系列沿着从p0到p1直线的点。
二、二次贝塞尔曲线
二次贝塞尔曲线的推导可以从线性插值(一次贝塞尔曲线)的概念出发。首先,我们来看一次贝塞尔曲线,它是最简单的贝塞尔曲线形式,由两个点定义,可以直接通过这两个点进行线性插值。
一次贝塞尔曲线的方程是:
B(t) = (1 - t) * P0 + t * P1
其中,t 是参数,取值范围是 [0, 1],P0 和 P1 是两个点。这个方程表示的是,当 t 从 0 增加到 1 时,点 B(t) 从 P0 线性移动到 P1。现在,我们想要构造一个曲线,它不仅通过起点和终点,而且还受到一个额外的控制点的影响。我们可以考虑使用一次贝塞尔曲线来构造这个更复杂的曲线。具体来说,我们可以先在起点 P0 和控制点 P1 之间构造一条一次贝塞尔曲线,然后在控制点 P1 和终点 P2 之间构造另一条一次贝塞尔曲线。接着,我们在这两条曲线之间进行线性插值。设 t 为参数,我们可以定义两个中间点 Q0 和 Q1,它们分别是在 t = 0 和 t = 1 时,上述两条一次贝塞尔曲线上的点:
Q0 = (1 - t) * P0 + t * P1
Q1 = (1 - t) * P1 + t * P2
现在,我们需要在 Q0 和 Q1 之间进行线性插值,以得到最终的二次贝塞尔曲线上的点 B(t):
B(t) = (1 - t) * Q0 + t * Q1
将 Q0 和 Q1 的表达式代入上式,得到:
B(t) = (1 - t) * [(1 - t) * P0 + t * P1] + t * [(1 - t) * P1 + t * P2]
展开并整理上式,得到二次贝塞尔曲线的标准形式:
B(t) = (1 - t)^2 * P0 + 2 * (1 - t) * t * P1 + t^2 * P2
这就是二次贝塞尔曲线的推导过程。通过这种方式,我们可以看到二次贝塞尔曲线是如何从一次贝塞尔曲线的基础上构造出来的,同时也揭示了控制点 P1 如何影响曲线的形状。
二次贝塞尔曲线由三个点P0, P1, 和P2定义,给定参数t(t属于[0,1]);以下是一个使用SDL2库的C语言程序,它实现了这个公式,并可以可视化二次贝塞尔曲线:
#include <SDL.h>
#include <stdio.h>
#include <math.h>
#include <stdbool.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
// 定义一个结构体来表示一个点
typedef struct {
float x;
float y;
} Point;
// 计算二次贝塞尔曲线上的点
Point calculateBezierPoint(Point p0, Point p1, Point p2, float t) {
Point p;
float u = 1 - t;
float tt = t * t;
float uu = u * u;
p.x = uu * p0.x + 2 * u * t * p1.x + tt * p2.x;
p.y = uu * p0.y + 2 * u * t * p1.y + tt * p2.y;
return p;
}
int main(int argc, char* args[]) {
SDL_Window* window = NULL;
SDL_Renderer* renderer = NULL;
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
printf("SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return 1;
}
window = SDL_CreateWindow("Bezier Curve Visualization", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN);
if (window == NULL) {
printf("Window could not be created! SDL_Error: %s\n", SDL_GetError());
return 1;
}
renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
if (renderer == NULL) {
printf("Renderer could not be created! SDL_Error: %s\n", SDL_GetError());
return 1;
}
SDL_Event e;
bool quit = false;
while (!quit) {
while (SDL_PollEvent(&e) != 0) {
if (e.type == SDL_QUIT) {
quit = true;
}
}
SDL_SetRenderDrawColor(renderer, 255, 255, 255, 255);
SDL_RenderClear(renderer);
// 定义三个控制点
Point p0 = {100.0, 500.0};
Point p1 = {400.0, 100.0};
Point p2 = {700.0, 500.0};
// 绘制控制点
SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
SDL_RenderDrawPoint(renderer, (int)p0.x, (int)p0.y);
SDL_RenderDrawPoint(renderer, (int)p1.x, (int)p1.y);
SDL_RenderDrawPoint(renderer, (int)p2.x, (int)p2.y);
// 绘制二次贝塞尔曲线
SDL_SetRenderDrawColor(renderer, 0, 0, 255, 255);
Point prev = p0;
for (float t = 0.01; t <= 1.0; t += 0.01) {
Point current = calculateBezierPoint(p0, p1, p2, t);
SDL_RenderDrawLine(renderer, (int)prev.x, (int)prev.y, (int)current.x, (int)current.y);
prev = current;
}
SDL_RenderPresent(renderer);
}
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
在这个程序中,我们首先初始化SDL,创建一个窗口和渲染器。然后,我们进入一个事件循环,监听退出事件。在每次循环中,我们清除屏幕,绘制三个控制点,并使用calculateBezierPoint函数绘制二次贝塞尔曲线。