一、三次贝塞尔曲线
三次贝塞尔曲线是更复杂的曲线形式,它由四个点定义:起始点 P0、两个控制点 P1 和 P2 以及结束点 P3。曲线上的任意一点 B(t) 可以通过以下公式计算:
B(t) = (1 - t)^3 * P0 + 3 * (1 - t)^2 * t * P1 + 3 * (1 - t) * t^2 * P2 + t^3 * P3
其中 t 是参数,取值范围是 [0, 1]。
推导三次贝塞尔曲线的过程可以从一次和二次贝塞尔曲线的推广出发。我们已经知道,一次贝塞尔曲线是两点之间的线性插值,而二次贝塞尔曲线是两点之间受一个控制点影响的曲线。三次贝塞尔曲线可以看作是两点之间受两个控制点影响的曲线。
为了推导三次贝塞尔曲线,我们可以采用递归的思想,即使用二次贝塞尔曲线来构造三次贝塞尔曲线。具体步骤如下:
- 在起始点 P0 和控制点 P1 之间构造一条二次贝塞尔曲线。
- 在控制点 P1 和控制点 P2 之间构造一条二次贝塞尔曲线。
- 在控制点 P2 和结束点 P3 之间构造一条二次贝塞尔曲线。
- 然后在这些二次贝塞尔曲线之间进行插值。
设 t 为参数,我们可以定义三个中间点 R0、R1 和 R2,它们分别是在 t = 0、t = 0.5 和 t = 1 时,上述三条二次贝塞尔曲线上的点。然后,我们在 R0 和 R1 之间、R1 和 R2 之间分别构造一次贝塞尔曲线。
最后,我们在这些一次贝塞尔曲线之间进行插值,得到最终的三次贝塞尔曲线上的点 B(t)。通过这种方式,我们可以得到三次贝塞尔曲线的公式:
B(t) = (1 - t)^3 * P0 + 3 * (1 - t)^2 * t * P1 + 3 * (1 - t) * t^2 * P2 + t^3 * P3
这就是三次贝塞尔曲线的推导过程。通过这种方式,我们可以看到三次贝塞尔曲线是如何从一次和二次贝塞尔曲线的基础上构造出来的,同时也揭示了控制点 P1 和 P2 如何影响曲线的形状。三次贝塞尔曲线在计算机图形学中非常有用,因为它提供了一种灵活的方式来绘制复杂的曲线形状。
以下是一个使用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, Point p3, float t) {
Point p;
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
p.x = uuu * p0.x + 3 * uu * t * p1.x + 3 * u * tt * p2.x + ttt * p3.x;
p.y = uuu * p0.y + 3 * uu * t * p1.y + 3 * u * tt * p2.y + ttt * p3.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 = {300.0, 100.0};
Point p2 = {500.0, 500.0};
Point p3 = {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_RenderDrawPoint(renderer, (int)p2.x, (int)p2.y);
SDL_RenderDrawPoint(renderer, (int)p3.x, (int)p3.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, p3, 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函数绘制三次贝塞尔曲线。
二、四次贝塞尔曲线
四次贝塞尔曲线是由五个点定义的曲线,这五个点包括一个起始点、三个控制点和一个结束点。四次贝塞尔曲线的方程是:
B(t) = (1 - t)^4 * P0 + 4 * (1 - t)^3 * t * P1 + 6 * (1 - t)^2 * t^2 * P2 + 4 * (1 - t) * t^3 * P3 + t^4 * P4
其中 t 是参数,取值范围是 [0, 1],P0 是起始点,P1、P2 和 P3 是控制点,P4 是结束点。B(t) 表示曲线上 t 时刻的点的坐标。
四次贝塞尔曲线的推导过程可以通过递归的思想来进行,即使用更低次的贝塞尔曲线来构造更高次的曲线。具体来说,我们可以使用三次贝塞尔曲线来构造四次贝塞尔曲线。
假设我们有四个三次贝塞尔曲线,它们的控制点分别是:
- P0, P1, P2, P3
- P1, P2, P3, P4
- P2, P3, P4, Q (一个临时点)
- P3, P4, Q, Q
我们可以看到,这四个三次贝塞尔曲线共享了 P2 和 P3 这两个控制点。我们可以在这四个三次贝塞尔曲线之间进行插值,以得到最终的四次贝塞尔曲线。
设 t 为参数,我们可以定义三个中间点 R0、R1 和 R2,它们分别是在 t = 0、t = 1/3 和 t = 2/3 时,上述四个三次贝塞尔曲线上的点。然后,我们在 R0 和 R1 之间、R1 和 R2 之间、R2 和 Q 之间分别构造二次贝塞尔曲线。
最后,我们在这些二次贝塞尔曲线之间进行插值,得到最终的四次贝塞尔曲线上的点 B(t)。通过这种方式,我们可以得到四次贝塞尔曲线的公式:
B(t) = (1 - t)^4 * P0 + 4 * (1 - t)^3 * t * P1 + 6 * (1 - t)^2 * t^2 * P2 + 4 * (1 - t) * t^3 * P3 + t^4 * P4
这就是四次贝塞尔曲线的推导过程。通过这种方式,我们可以看到四次贝塞尔曲线是如何从三次贝塞尔曲线的基础上构造出来的,同时也揭示了控制点 P1、P2 和 P3 如何影响曲线的形状。四次贝塞尔曲线在计算机图形学中非常有用,因为它提供了一种灵活的方式来绘制复杂的曲线形状。
#include <SDL.h>
#include <stdio.h>
#include <math.h>
#define SCREEN_WIDTH 800
#define SCREEN_HEIGHT 600
// 定义一个结构体来表示一个点
typedef struct {
float x;
float y;
} Point;
// 计算四次贝塞尔曲线上的点
Point calculateBezierPoint(Point p0, Point p1, Point p2, Point p3, Point p4, float t) {
Point p;
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
p.x = uuu * u * p0.x + 4 * uuu * t * p1.x + 6 * uu * tt * p2.x + 4 * u * ttt * p3.x + ttt * t * p4.x;
p.y = uuu * u * p0.y + 4 * uuu * t * p1.y + 6 * uu * tt * p2.y + 4 * u * ttt * p3.y + ttt * t * p4.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 = {200.0, 100.0};
Point p2 = {400.0, 100.0};
Point p3 = {600.0, 500.0};
Point p4 = {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_RenderDrawPoint(renderer, (int)p2.x, (int)p2.y);
SDL_RenderDrawPoint(renderer, (int)p3.x, (int)p3.y);
SDL_RenderDrawPoint(renderer, (int)p4.x, (int)p4.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, p3, p4, 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;
}
三、五次贝塞尔曲线
五次贝塞尔曲线是由六个点定义的曲线,这六个点包括一个起始点、四个控制点和一个结束点。五次贝塞尔曲线的方程是:
B(t) = (1 - t)^5 * P0 + 5 * (1 - t)^4 * t * P1 + 10 * (1 - t)^3 * t^2 * P2 + 10 * (1 - t)^2 * t^3 * P3 + 5 * (1 - t) * t^4 * P4 + t^5 * P5
其中 t 是参数,取值范围是 [0, 1],P0 是起始点,P1、P2、P3 和 P4 是控制点,P5 是结束点。B(t) 表示曲线上 t 时刻的点的坐标。
五次贝塞尔曲线的推导过程可以通过递归的思想来进行,即使用更低次的贝塞尔曲线来构造更高次的曲线。具体来说,我们可以使用四次贝塞尔曲线来构造五次贝塞尔曲线。
假设我们有五个四次贝塞尔曲线,它们的控制点分别是:
- P0, P1, P2, P3, P4
- P1, P2, P3, P4, P5
- P2, P3, P4, P5, Q (一个临时点)
- P3, P4, P5, Q, Q
- P4, P5, Q, Q, Q
我们可以看到,这五个四次贝塞尔曲线共享了 P3 和 P4 这两个控制点。我们可以在这五个四次贝塞尔曲线之间进行插值,以得到最终的五次贝塞尔曲线。
设 t 为参数,我们可以定义四个中间点 R0、R1、R2 和 R3,它们分别是在 t = 0、t = 1/4、t = 1/2 和 t = 3/4 时,上述五个四次贝塞尔曲线上的点。然后,我们在 R0 和 R1 之间、R1 和 R2 之间、R2 和 R3 之间、R3 和 Q 之间分别构造三次贝塞尔曲线。
最后,我们在这些三次贝塞尔曲线之间进行插值,得到最终的五次贝塞尔曲线上的点 B(t)。通过这种方式,我们可以得到五次贝塞尔曲线的公式:
B(t) = (1 - t)^5 * P0 + 5 * (1 - t)^4 * t * P1 + 10 * (1 - t)^3 * t^2 * P2 + 10 * (1 - t)^2 * t^3 * P3 + 5 * (1 - t) * t^4 * P4 + t^5 * P5
这就是五次贝塞尔曲线的推导过程。通过这种方式,我们可以看到五次贝塞尔曲线是如何从四次贝塞尔曲线的基础上构造出来的,同时也揭示了控制点 P1、P2、P3 和 P4 如何影响曲线的形状。五次贝塞尔曲线在计算机图形学中非常有用,因为它提供了一种灵活的方式来绘制非常复杂的曲线形状。
#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, Point p3, Point p4, Point p5, float t) {
Point p;
float u = 1 - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float uuuu = uuu * u;
float ttt = tt * t;
float tttt = ttt * t;
p.x = uuuu * u * u * p0.x + 5 * uuuu * u * t * t * p1.x + 10 * uuu * u * tt * t * p2.x + 10 * uu * uu * ttt * p3.x + 5 * uu * u * tttt * p4.x + tttt * t * t * p5.x;
p.y = uuuu * u * u * p0.y + 5 * uuuu * u * t * t * p1.y + 10 * uuu * u * tt * t * p2.y + 10 * uu * uu * ttt * p3.y + 5 * uu * u * tttt * p4.y + tttt * t * t * p5.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 = {200.0, 400.0};
Point p2 = {300.0, 300.0};
Point p3 = {400.0, 200.0};
Point p4 = {500.0, 100.0};
Point p5 = {600.0, 50.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_RenderDrawPoint(renderer, (int)p3.x, (int)p3.y);
SDL_RenderDrawPoint(renderer, (int)p4.x, (int)p4.y);
SDL_RenderDrawPoint(renderer, (int)p5.x, (int)p5.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, p3, p4, p5, 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;
}
在这个程序中,calculateBezierPoint函数接受一个包含六个点的数组和一个参数t,然后使用五次贝塞尔曲线的公式来计算曲线上对应于t的点。main函数定义了六个控制点,并使用一个循环来计算并打印曲线上的一系列点。