【UE4】GAMES101 图形学作业4:贝塞尔曲线
总览
-
Bézier 曲线是一种用于计算机图形学的参数曲线。
-
在本次作业中,你需要实现de Casteljau 算法来绘制由4 个控制点表示的Bézier 曲线(当你正确实现该算法时,你可以支持绘制由更多点来控制的Bézier 曲线)。
-
你需要修改的函数在提供的main.cpp 文件中。
bezier
:该函数实现绘制Bézier 曲线的功能。
它使用一个控制点序列和一个OpenCV::Mat 对象作为输入,没有返回值。它会使t 在0 到1 的范围内进行迭代,并在每次迭代中使t 增加一个微小值。对于每个需要计算的t,将调用另一个函数recursive_bezier,然后该函数将返回在Bézier 曲线上t处的点。最后,将返回的点绘制在OpenCV ::Mat 对象上。- recursive_bezier:该函数使用一个控制点序列和一个浮点数t 作为输入,实现de Casteljau 算法来返回Bézier 曲线上对应点的坐标。
实现
-
版本 4.26.2
-
naive_bezier
-
数学公式
-
代码
void AActor_BezierCuve::naive_bezier() { FVector& p_0 = m_points[0]; FVector& p_1 = m_points[1]; FVector& p_2 = m_points[2]; FVector& p_3 = m_points[3]; FVector& p_4 = m_points[4]; for (double t = 0.0; t <= 1.0; t += 0.001) { auto point = std::pow(1 - t, 4) * p_0 + 4 * t * std::pow(1 - t, 3) * p_1 + 6 * std::pow(t, 2) * std::pow((1 - t), 2) * p_2 + 4 * std::pow(t, 3) * (1 - t) * p_3 + std::pow(t, 4) * p_4; DrawDebugPoint(GetWorld(), point, 2.0f, FColor::Green,true,5.0f); //UKismetSystemLibrary::PrintString(GetWorld(), point.ToString()); } }
-
-
recursive_bezier
-
De Casteljau 算法说明如下:
- 考虑一个p0, p1, ... pn 为控制点序列的Bézier 曲线。首先,将相邻的点连接起来以形成线段。
- 用t : (1 − t) 的比例细分每个线段,并找到该分割点。
- 得到的分割点作为新的控制点序列,新序列的长度会减少一。
- 如果序列只包含一个点,则返回该点并终止。否则,使用新的控制点序列并转到步骤1。使用[0,1] 中的多个不同的t 来执行上述算法,你就能得到相应的Bézier 曲线。
-
代码
void AActor_BezierCuve::bezier() { for (double t = 0.0; t <= 1.0; t += 0.001) { FVector point = recursive_bezier(m_points, t); DrawDebugPoint(GetWorld(), point, 2.0f, FColor(10,214,255,255),true,5.0f); } } // De Casteljau 算法,递归 FVector AActor_BezierCuve::recursive_bezier(TArray<FVector>& points, float t) { if (points.Num() < 3) { return (1 - t) * points[0] + t * points[1]; } TArray<FVector> newPoint; for (int i = 0; i < points.Num() - 1; i++) { newPoint.Add((1 - t) * points[i] + t * points[i + 1]); } return recursive_bezier(newPoint, t); }
-
-
最终效果
附录
所有代码
-
Actor_BezierCuve.h
点击查看代码
```cpp UCLASS() class GAMES101_API AActor_BezierCuve : public AActor { GENERATED_BODY() public: // Sets default values for this actor's properties AActor_BezierCuve(); protected: // Called when the game starts or when spawned virtual void BeginPlay() override; public: // Called every frame virtual void Tick(float DeltaTime) override; UFUNCTION(BlueprintCallable) void naive_bezier(); UFUNCTION(BlueprintCallable) void bezier(); UFUNCTION(BlueprintCallable) FVector recursive_bezier(TArray<FVector>& points,float t); public: UPROPERTY(VisibleAnywhere) USceneComponent* root; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* point0; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* point1; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* point2; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* point3; UPROPERTY(VisibleAnywhere) UStaticMeshComponent* point4; UPROPERTY(); TArray<FVector> m_points; UPROPERTY(EditAnywhere); bool m_bUseRecursiveBezier; }; ```
-
AActor_BezierCuve.cpp
点击查看代码
#include "Actor_BezierCuve.h" #include "DrawDebugHelpers.h" #include <cmath> #include "Kismet/KismetSystemLibrary.h" // Sets default values AActor_BezierCuve::AActor_BezierCuve() { // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. PrimaryActorTick.bCanEverTick = true; root = CreateDefaultSubobject<USceneComponent>(TEXT("root")); SetRootComponent(root); point0 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point0")); point1 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point1")); point2 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point2")); point3 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point3")); point4 = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("point4")); point0->SetupAttachment(root); point1->SetupAttachment(root); point2->SetupAttachment(root); point3->SetupAttachment(root); point4->SetupAttachment(root); m_points.Init(FVector::ZeroVector, 5); m_bUseRecursiveBezier = false; } // Called when the game starts or when spawned void AActor_BezierCuve::BeginPlay() { Super::BeginPlay(); m_points[0] = point0->GetComponentLocation(); m_points[1] = point1->GetComponentLocation(); m_points[2] = point2->GetComponentLocation(); m_points[3] = point3->GetComponentLocation(); m_points[4] = point4->GetComponentLocation(); if (!m_bUseRecursiveBezier) naive_bezier(); else bezier(); } // Called every frame void AActor_BezierCuve::Tick(float DeltaTime) { Super::Tick(DeltaTime); } // 多项式 void AActor_BezierCuve::naive_bezier() { FVector& p_0 = m_points[0]; FVector& p_1 = m_points[1]; FVector& p_2 = m_points[2]; FVector& p_3 = m_points[3]; FVector& p_4 = m_points[4]; for (double t = 0.0; t <= 1.0; t += 0.001) { auto point = std::pow(1 - t, 4) * p_0 + 4 * t * std::pow(1 - t, 3) * p_1 + 6 * std::pow(t, 2) * std::pow((1 - t), 2) * p_2 + 4 * std::pow(t, 3) * (1 - t) * p_3 + std::pow(t, 4) * p_4; DrawDebugPoint(GetWorld(), point, 2.0f, FColor::Green,true,5.0f); //UKismetSystemLibrary::PrintString(GetWorld(), point.ToString()); } } void AActor_BezierCuve::bezier() { for (double t = 0.0; t <= 1.0; t += 0.001) { FVector point = recursive_bezier(m_points, t); DrawDebugPoint(GetWorld(), point, 2.0f, FColor(10,214,255,255),true,5.0f); } } // De Casteljau 算法,递归 FVector AActor_BezierCuve::recursive_bezier(TArray<FVector>& points, float t) { if (points.Num() < 3) { return (1 - t) * points[0] + t * points[1]; } TArray<FVector> newPoint; for (int i = 0; i < points.Num() - 1; i++) { newPoint.Add((1 - t) * points[i] + t * points[i + 1]); } return recursive_bezier(newPoint, t); }
作者:砥才人
出处:https://www.cnblogs.com/shiroe
本系列文章为笔者整理原创,只发表在博客园上,欢迎分享本文链接,如需转载,请注明出处!